#!/usr/bin/env python
#
#   ConVirt   -  Copyright (c) 2008 Convirture Corp.
#   ======
#
# ConVirt is a Virtualization management tool with a graphical user
# interface that allows for performing the standard set of VM operations
# (start, stop, pause, kill, shutdown, reboot, snapshot, etc...). It
# also attempts to simplify various aspects of VM lifecycle management.
#
#
# This software is subject to the GNU General Public License, Version 2 (GPLv2)
# and for details, please consult it at:
#
#    http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
# 
#
# author : Jd <jd_jedi@users.sourceforge.net>
#
import os, traceback, types
import gtk, gtk.glade, gobject
from convirt.core.utils.utils import dynamic_map
from convirt.client.dialogs import showmsg, file_selection

def get_widget(wtree,widget_name):
    widget = wtree.get_widget(widget_name)
    if widget is None:
        print "WARNING : Widget %s not found." % widget_name
    return widget

def set_value(widget, value):
    if value is None:
        return # should we clear ?
    if type(widget) in (gtk.Label, gtk.Entry):
        widget.set_text(str(value))
    elif type(widget) in (gtk.SpinButton,):
        widget.set_value(int(value))
    elif type(widget) in (gtk.CheckButton,gtk.RadioButton):
        if type(value) == str:
            print "STRING VALUE " , str
            widget.set_active(eval(value))
        elif type(value) == bool:
            widget.set_active(value)
        else:
            raise Exception("Can not handle %s for checkButton", type(value))
    elif type(widget) in (gtk.ComboBox, gtk.ComboBoxEntry,):
        set_combo_value(widget, value)
    else:
        raise Exception("set_value can not handle %s ", type(widget))

def get_value(widget):
    if type(widget) in (gtk.Label, gtk.Entry):
        return widget.get_text()
    elif type(widget) in (gtk.SpinButton,):
        return widget.get_value()
    elif type(widget) in (gtk.CheckButton,gtk.RadioButton):
        return widget.get_active()
    elif type(widget) in (gtk.ComboBoxEntry,gtk.ComboBox):
        return get_combo_value(widget)
    else:
        raise Exception("get_value can not handle %s ", type(widget))

# assumes atlease 2 column iter for combobox entry.
def init_combo(combo, map, order=None):
    model = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_PYOBJECT)
    #model.set_sort_column_id(0, gtk.SORT_ASCENDING)
    if order:
        values = order
    else:
        values = map.keys()
        values.sort()

    for v in values:
        k = map[v]
        model.append([v,k])
    combo.set_model(model)


def set_combo_value(widget, value):
    model = widget.get_model()
    single_column = model.get_n_columns() < 2
    if single_column:
        data_column = 0
    else:
        data_column = 1
        
    iter = model.get_iter_first()
    while iter:
        val = model.get_value(iter, data_column)
        if val == value:
            widget.set_active_iter(iter)
            return
        iter= model.iter_next(iter)

    if isinstance(widget, gtk.ComboBoxEntry) or isinstance(widget,gtk.ComboBox):
        if single_column:
            iter = model.append(value)
        else:
            if isinstance(value, tuple):
                display = value[0]
            else:
                display = value
            iter = model.append([display, value])
        widget.set_active_iter(iter)
        return


def get_combo_value(widget):
    model = widget.get_model()
    single_column = model.get_n_columns() < 2
    if single_column:
        data_column = 0
    else:
        data_column = 1

    iter = widget.get_active_iter()
    if iter:
        return model.get_value(iter, data_column)
    else:
        if isinstance(widget, gtk.ComboBoxEntry):
            return widget.child.get_text()

# show a file selection dialog and update the widget with
# user selection.
def file_selector(widget, managed_node, field,
                  fstype="open", parentwin=None):
    """Handles the pressing of the file selection buttons"""

    filename = field.get_text()

    init_folder = os.path.dirname(filename)
    if fstype == "open":
        init_file = None
    else:
        init_file = filename

    result, filename = file_selection(managed_node,
                                      "Select file", fstype, init_folder,
                                      init_file, parentwin)
    if result:
        field.set_text(filename)


# show validation msgs.
def show_validation_msgs(msgs, parent= None):
    if msgs is None:
        return
    display_str = ""
    if isinstance(msgs, list):
        for msg in msgs:
            display_str += msg + "\n"
    else:
        display_str = str(msg)
        
    if display_str:
        showmsg(display_str, parent)


# base classes

## represents container containing widgets that can be hidden or
## disabled in a given mode.
class Container:
    def __init__(self):
        self.widgets = dynamic_map()

    def init_widgets(self, wtree, widget_names):
        for name in widget_names:
            self.widgets[name] = get_widget(wtree, name)
            #print name, self.widgets[name].__class__
            
    def get_widgets(self):
        return [w for w in self.widgets.itervalues()]
    
    def get_widgets_to_hide(self, mode):
        return []
    def get_widgets_to_disable(self, mode):
        return []
    def update_widgets(self, mode):
        all_widgets = self.get_widgets()
        widgets_to_hide = self.get_widgets_to_hide(mode)
        widgets_to_disable = self.get_widgets_to_disable(mode)
        for widget in all_widgets:
            widget.set_property("visible", not (widget in widgets_to_hide))
            widget.set_property("sensitive", not (widget in widgets_to_disable))
            #disable more pleasant way.. not working :(
            """
            if type(widget) in (gtk.Entry, gtk.ComboBoxEntry):
                w = widget
                if type(widget) in (gtk.ComboBoxEntry,):
                    w = widget.child
                    widget.set_property("sensitive", 
                                        not (widget in widgets_to_disable))
                w.set_property("sensitive", True)
                w.set_property("editable", not (widget in widgets_to_disable))
            else:
                widget.set_property("sensitive", not (widget in widgets_to_disable))
            """

    def scatter(self, context):
        pass

    def gather(self, context):
        pass

    def validate(self):
        return None # all well

# Dialog class handles the run method.
class Dlg(Container):
    def __init__(self):
        Container.__init__(self)
        
    ## TBD : Validate the dialogbox pattern : This should move to ui_utils in 
    ## container or container derived dlg class
    def run_dlg(self, context):
        ret = None
        if self.context.parent:
            self.dialog.set_transient_for(self.context.parent)
        self.update_widgets(context.mode)
        self.scatter(context)
        while ret != gtk.RESPONSE_DELETE_EVENT :
            ret = self.dialog.run()
            if ret in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_OK, 
                       gtk.RESPONSE_CLOSE):
                if (ret in (gtk.RESPONSE_OK, gtk.RESPONSE_CLOSE)):
                    try:
                        msgs = self.validate()
                        if msgs is None or len(msgs) == 0:
                            self.gather(context)
                            self.save(context)
                            break
                        else:
                            show_validation_msgs(msgs, self.dialog)
                    except Exception, ex:
                        traceback.print_exc()
                        showmsg("Ex :" + str(ex), self.dialog)
                else:
                    break

        self.dialog.destroy()
        return ret


## reporesents a page in a nodebook.
class Page(Container):
    def __init__(self):
        Container.__init__(self)
    # return the top level widgets that represents tab and page content.
    def get_page_widgets():
        return (None, None)


## represents a class for the Notebook.
class Notebook(Container):
    def __init__(self, notebook_widget):
        Container.__init__(self)
        self.notebook_widget = notebook_widget
        self.pages = self.get_pages()

    
    # let the derived class adjust list of initial pages.
    def get_pages(self):
        raise Exception("get_pages should be implemented by derived class")
    
    def append_page(self, page):
        if page not in self.pages:
            self.pages.append(page)
            page_name, page_obj = page
            (tab, tab_content) = page_obj.get_page_widgets()
            self.notebook_widget.append_page(tab_content, tab)

    def insert_page(self, page, position = 0):
        if page not in self.pages:
            self.pages.insert(position, page)
            page_name, page_obj = page
            (tab, tab_content) = page_obj.get_page_widgets()
            self.notebook_widget.insert_page(tab_content, tab, position)
            
            
    def remove_page(self, page_name):
        for page in self.pages:
            p_name, p_obj = page
            if p_name == page_name:
                self.notebook_widget.remove_page(self.pages.index(page))
                self.pages.remove(page)
                break
    def update_widgets(self, mode):
        for page in self.pages :
            p_name, p_obj = page
            p_obj.update_widgets(mode)

    def scatter(self, context):
        for page in self.pages:
            p_name, p_obj = page
            p_obj.scatter(context)

    def gather(self, context):
        for page in self.pages:
            p_name, p_obj = page
            p_obj.gather(context)

    def validate(self):
        errs = None
        for page in self.pages:
            p_name, p_obj = page
            e = p_obj.validate()
            if e and len(e) > 0:
                if not errs:
                    errs = e
                else:
                    errs.extend(e)
        return errs

# Base Property editor

# NOTE : Assumes that the model structure as follows
# col 0 STRING for name
# col 1 STRING for value,
# col 2 BOOLEAN name cell editable ?
# col 3 BOOLEAN value cell editable ?
# col 4 BOOLEAN disabled look for the cell/row

class PropertyEditorView:
    def __init__(self, treeview):
        self.treeview = treeview
        self.model = self.create_model()
        self.treeview.set_model(self.model)
        self.init_view(self.treeview)



    def init_view(self, treeview):
        treeview.set_rules_hint(True)
        #treeview.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
        #treeview.set_enable_grid_lines(true)
        model = treeview.get_model()

        # Name
        renderer = gtk.CellRendererText()
        renderer.connect("edited",
                         self.__on_props_cell_edited,
                         model)
        renderer.connect("editing-started",
                         self.__on_props_cell_start_editing,
                         model)


        renderer.set_data("column", 0)
        renderer.set_property("foreground","gray")
        renderer.set_property("weight",700)
        renderer.set_property("weight-set",True)
        column = gtk.TreeViewColumn("Attribute",
                                    renderer,
                                    text=0, editable = 2, foreground_set=4)
        treeview.append_column(column)

        # Value
        renderer = gtk.CellRendererText()
        renderer.connect("edited",
                         self.__on_props_cell_edited,
                         model)
        renderer.connect("editing-started",
                         self.__on_props_cell_start_editing,
                         model)

        renderer.set_data("column", 1)
        renderer.set_property("foreground","gray")
        column = gtk.TreeViewColumn("Value",
                                    renderer,
                                    text=1, editable=3, foreground_set=4)
                                    
        treeview.append_column(column)

    def get_model(self):
        return self.model
        
    # override this if u want to have more content in the model.
    def create_model(self):
        return gtk.ListStore(gobject.TYPE_STRING, # Name
                             gobject.TYPE_STRING, # Value
                             gobject.TYPE_BOOLEAN,# Name cell editable ?
                             gobject.TYPE_BOOLEAN,# Value cell editable ?
                             gobject.TYPE_BOOLEAN)# should cell/row user disabled look
    
    # hook to catch the context of the cell and set signal handlers for
    # the gtk.Entry.
    def __on_props_cell_start_editing(self,cell,editable, path_string, model):
        self.escape = False
        iter = model.get_iter_from_string(path_string)
        path = model.get_path(iter)[0]
        column = cell.get_data("column")
        editable.connect("focus-out-event", self.__on_focus_out,
                         (path_string,column,model))
        editable.connect("key-press-event", self.__on_key_released)

    def __on_props_cell_edited(self,cell,path_string,new_text, model):
        iter = model.get_iter_from_string(path_string)
        path = model.get_path(iter)[0]
        column = cell.get_data("column")
        model.set(iter, column, new_text)

    # Take care of the ESCAPE key. set the flag and return False for the
    # event system to continue processing.
    def __on_key_released(self, widget, event):
        self.escape =  (event.keyval == 65307) # escape key
        return False

    def __on_focus_out(self,editable, event, context):
        if self.escape:
            return
        (path_string, column, model) = context
        iter = model.get_iter_from_string(path_string)
        new_text = editable.get_text()
        model.set(iter, column, new_text)


    def __on_props_cell_focus_out(self,cell,path_string,new_text, model):
        iter = model.get_iter_from_string(path_string)
        path = model.get_path(iter)[0]
        column = cell.get_data("column")
        model.set(iter, column, new_text)

    def on_props_add(self, widget):
        model = self.treeview.get_model()
        iter = model.append()
        model.set(iter,
                  0, "",
                  1, "",
                  2, True,
                  3, True,
                  4, False)
        # set the higlight to newly added row.
        path = model.get_path(iter)
        self.treeview.set_cursor(path,self.treeview.get_column(0),True)

    def on_props_remove(self, widget):
        selection = self.treeview.get_selection()
        if selection:
            model, iter = selection.get_selected()
            if iter:
                path = model.get_path(iter)[0]
                model.remove(iter)


### Utility functions for displaying N/V or table of information.
### Useful in implementing VMInfoHelpe
### Note : Assumes 3 col model.
def populateTreeWithDictInfo(model,iter, value_dict, display_dict):
  display_name = ''
  for name in display_dict :
    value = value_dict.get(name)
    if not value:
      continue
    display_name = display_dict[name]
    if type(value) == int:
      value = str(value)
    value = value.rstrip().lstrip()    
    model.append(iter, [display_name,value, "", False])

def populateTreeWithListInfo(model,iter, value_list, display_dict):
   display_list = ['','','']
   i = 0
   for name in display_dict:
     display_list[i] = display_dict[name]
     i = i + 1

   model.append(iter, [display_list[0],display_list[1], display_list[2], True])

   for i in range(len(value_list)):
     value_dict = value_list[i]
     column_value = ['','','']
     j = 0
     for name in value_dict:
       value = value_dict[name]
       if type(value) == int:
         value = str(value)
       column_value[j] = value.rstrip().lstrip()
       j = j+1
     model.append(iter, [column_value[0],column_value[1], column_value[2],False])

# Callback wrapper : Invokes an event handler function and shows details
#                    in a dialogbox 
def cb_wrapper(*args, **cargs):
    new_args = args
    l = len(args)
    ndx = 0
    for a in args:
        if isinstance(a, types.MethodType):
            new_args = args[0:ndx]
            if ndx < len:
                new_args = new_args + args[ndx+1:]
            try:
                val =  a(*new_args, **cargs)
                return val
            except Exception ,ex:
                traceback.print_exc()
                showmsg("Exception : " + str(ex), **cargs)
                return

        ndx = ndx+1
                        


