class AdditionListSlave(SearchSlave): """A slave that offers a simple list and its management. This slave also has the option to display a small message right next to the buttons """ domain = 'stoq' toplevel_name = gladefile = 'AdditionListSlave' widgets = ('add_button', 'delete_button', 'klist', 'list_vbox', 'edit_button') gsignal('on-edit-item', object) gsignal('on-add-item', object) gsignal('before-delete-items', object) gsignal('after-delete-items') def __init__(self, store, columns=None, editor_class=None, klist_objects=None, visual_mode=False, restore_name=None, tree=False): """ Creates a new AdditionListSlave object :param store: a store :param columns: column definitions :type columns: sequence of :class:`kiwi.ui.objectlist.Columns` :param editor_class: the window that is going to be open when user clicks on add_button or edit_button. :type: editor_class: a :class:`stoqlib.gui.editors.BaseEditor` subclass :param klist_objects: initial objects to insert into the list :param visual_mode: if we are working on visual mode, that means, not possible to edit the model on this object type visual_mode: bool :param restore_name: the name used to save and restore the columns on a cache system (e.g. pickle) :type restore_name: basestring :param tree: Indication of which kind of list we are adding. If `True` ObjectTree otherwise ObjectList will be added """ columns = columns or self.get_columns() SearchSlave.__init__(self, columns=columns, restore_name=restore_name, store=store) self.tree = tree self.klist = ObjectTree() if tree else ObjectList() self.list_vbox.add(self.klist) self.list_vbox.show_all() if not self.columns: raise StoqlibError("columns must be specified") self.visual_mode = visual_mode self.store = store self.set_editor(editor_class) self._can_edit = True self._callback_id = None if self.visual_mode: self.hide_add_button() self.hide_edit_button() self.hide_del_button() items = klist_objects or self.get_items() self._setup_klist(items) self._update_sensitivity() def _setup_klist(self, klist_objects): self.klist.set_columns(self.columns) self.klist.set_selection_mode(gtk.SELECTION_MULTIPLE) if self.tree: (self.klist.append(obj.parent_item, obj) for obj in klist_objects) else: self.klist.add_list(klist_objects) if self.visual_mode: self.klist.set_sensitive(False) def _update_sensitivity(self, *args): if self.visual_mode: return can_delete = _can_edit = True objs = self.get_selection() if not objs: _can_edit = can_delete = False elif len(objs) > 1: _can_edit = False self.add_button.set_sensitive(True) self.edit_button.set_sensitive(_can_edit) self.delete_button.set_sensitive(can_delete) def _edit_model(self, model=None, parent=None): edit_mode = model result = self.run_editor(model) if not result: return if edit_mode: self.emit('on-edit-item', result) self.klist.update(result) else: if self.tree: self.klist.append(parent, result) else: self.klist.append(result) # Emit the signal after we added the item to the list to be able to # check the length of the list in our validation callbacks. self.emit('on-add-item', result) # As we have a selection extended mode for kiwi list, we # need to unselect everything before select the new instance. self.klist.unselect_all() self.klist.select(result) self._update_sensitivity() def _edit(self): if not self._can_edit: return objs = self.get_selection() qty = len(objs) if qty != 1: raise SelectionError( ("Please select only one item before choosing Edit." "\nThere are currently %d items selected") % qty) self._edit_model(objs[0]) def _clear(self): objs = self.get_selection() qty = len(objs) if qty < 1: raise SelectionError('There are no objects selected') msg = stoqlib_ngettext(_('Delete this item?'), _('Delete these %d items?') % qty, qty) delete_label = stoqlib_ngettext(_("Delete item"), _("Delete items"), qty) keep_label = stoqlib_ngettext(_("Keep it"), _("Keep them"), qty) if not yesno(msg, gtk.RESPONSE_NO, delete_label, keep_label): return self.emit('before-delete-items', objs) if qty == len(self.klist): self.klist.clear() else: for obj in objs: self.klist.remove(obj) self.klist.unselect_all() self._update_sensitivity() self.emit('after-delete-items') # # Hooks # def get_items(self): return [] def get_columns(self): raise NotImplementedError("get_columns must be implemented in " "subclasses") def run_editor(self, model): """This can be overriden to provide a custom run_dialog line, or a conversion function for the model """ if self._editor_class is None: raise TypeError( "%s cannot create or edit items without the editor_class " "argument set" % (self.__class__.__name__)) self.store.savepoint('before_run_editor_addition') retval = run_dialog(self._editor_class, None, store=self.store, model=model) if not retval: self.store.rollback_to_savepoint('before_run_editor_addition') return retval def delete_model(self, model): """Deletes a model, can be overridden in subclass :param model: model to delete """ model.__class__.delete(model.id, store=self.store) # # Public API # def add_extra_button(self, label=None, stock=None): """Add an extra button on the this slave The extra button will be appended at the end of the button box, the one containing the add/edit/delete buttons :param label: label of the button, can be ``None`` if stock is passed :param stock: stock label of the button, can be ``None`` if label is passed :param returns: the button added :rtype: gtk.Button """ if label is None and stock is None: raise TypeError("You need to provide a label or a stock argument") button = gtk.Button(label=label, stock=stock) button.set_property('can_focus', True) self.button_box.pack_end(button, False, False) button.show() return button def set_message(self, message, details_callback=None): """Display a simple message on a label, next to the add, edit, delete buttons :param message: a message with properly escaped markup """ self.message_hbox.set_visible(True) self.message_details_button.set_visible(bool(details_callback)) if details_callback: if self._callback_id: self.message_details_button.disconnect(self._callback_id) self._callback_id = self.message_details_button.connect( 'clicked', details_callback) self.message_label.set_markup(message) def clear_message(self): self.message_hbox.set_visible(False) def get_selection(self): # XXX: add get_selected_rows and raise exceptions if not in the # right mode if self.klist.get_selection_mode() == gtk.SELECTION_MULTIPLE: return self.klist.get_selected_rows() selection = self.klist.get_selected() if not selection: return [] return [selection] def hide_add_button(self): self.add_button.hide() def hide_edit_button(self): self._can_edit = False self.edit_button.hide() def hide_del_button(self): self.delete_button.hide() def set_editor(self, editor_class): if editor_class and not issubclass(editor_class, (BaseEditor, BaseWizard)): raise TypeError("editor_class must be a BaseEditor subclass") self._editor_class = editor_class # # Signal handlers # def on_klist__row_activated(self, *args): self._edit() def on_klist__selection_changed(self, *args): self._update_sensitivity() def on_add_button__clicked(self, button): self._edit_model() def on_edit_button__clicked(self, button): self._edit() def on_delete_button__clicked(self, button): self._clear()
class GTKProject(SlaveDelegate): """A facade of kmcos.types.Project so that pygtk can display in a TreeView. """ def __init__(self, parent, menubar): self.project_data = ObjectTree([ Column('name', use_markup=True, data_type=str, sorted=True), Column('info') ]) self.project_data.connect('row-activated', self.on_row_activated) self.model_tree = Project() self._set_treeview_hooks() self.menubar = menubar self.set_parent(parent) self.filename = '' self.undo_stack = UndoStack(self.model_tree.__repr__, self.import_file, self.project_data.select, menubar, self.meta, 'Initialization') SlaveDelegate.__init__(self, toplevel=self.project_data) def _set_treeview_hooks(self): """Fudge function to import to access function to kmcos.types.Project to kmcos.gui.GTKProject. """ self.project_data.clear() # Meta self.meta = self.project_data.append(None, self.model_tree.meta) self.model_tree.meta = self.meta # Layer List self.model_tree.add_layer = self.add_layer self.layer_list = self.project_data.append(None, self.model_tree.layer_list) self.get_layers = lambda: \ sorted(self.project_data.get_descendants(self.layer_list), key=lambda x: x.name) self.model_tree.get_layers = self.get_layers self.lattice = self.layer_list # Parameter List self.parameter_list = self.project_data.append( None, self.model_tree.parameter_list) self.add_parameter = lambda parameter: \ self.project_data.append(self.parameter_list, parameter) self.model_tree.add_parameter = self.add_parameter self.get_parameters = lambda: \ sorted(self.project_data.get_descendants(self.parameter_list), key=lambda x: x.name) self.model_tree.get_parameters = self.get_parameters # Species List self.species_list = self.project_data.append( None, self.model_tree.species_list) self.add_species = lambda species: \ self.project_data.append(self.species_list, species) self.model_tree.add_species = self.add_species self.get_speciess = lambda: \ sorted(self.project_data.get_descendants(self.species_list), key=lambda x: x.name) self.model_tree.get_speciess = self.get_speciess # Process List self.process_list = self.project_data.append( None, self.model_tree.process_list) self.add_process = lambda process:\ self.project_data.append(self.process_list, process) self.model_tree.add_process = self.add_process self.get_processes = lambda: \ sorted(self.project_data.get_descendants(self.process_list), key=lambda x: x.name) self.model_tree.get_processes = self.get_processes # Output List self.output_list = self.project_data.append( None, self.model_tree.output_list) self.add_output = lambda output:\ self.project_data.append(self.output_list, output) self.model_tree.add_output = self.add_output self.get_outputs = lambda: \ sorted(self.project_data.get_descendants(self.output_list), key=lambda x: x.name) self.model_tree.get_outputs = self.get_outputs def add_layer(self, layer): self.project_data.append(self.layer_list, layer) if len(self.get_layers()) == 1: self.set_default_layer(layer.name) self.set_substrate_layer(layer.name) return layer def set_default_species(self, species): self.model_tree.species_list.default_species = species def set_substrate_layer(self, layer): self.model_tree.layer_list.substrate_layer = layer def set_default_layer(self, layer): self.model_tree.layer_list.default_layer = layer def update(self, model): """Update the object tree.""" self.project_data.update(model) def on_row_activated(self, _tree, data): if isinstance(data, Layer): data.active = not data.active def get_name(self): """Return project name.""" if self.filename: return os.path.basename(self.filename) else: return 'Untitled' def __repr__(self): return str(self.model_tree) def import_file(self, filename): """Import XML project file into editor GUI, unfolding the object tree. """ self.filename = filename self.model_tree.import_file(filename) self.expand_all() def expand_all(self): """Expand all list of the project tree """ self.project_data.expand(self.species_list) self.project_data.expand(self.layer_list) self.project_data.expand(self.parameter_list) self.project_data.expand(self.process_list) self.project_data.expand(self.output_list) def on_key_press(self, _, event): """When the user hits the keyboard focusing the treeview this event is triggered. Right now the only supported function is to deleted the selected item """ selection = self.project_data.get_selected() if gtk.gdk.keyval_name(event.keyval) == 'Delete': if (isinstance(selection, Species) or isinstance(selection, Process) or isinstance(selection, Parameter) or isinstance(selection, Layer)): if kiwi.ui.dialogs.yesno( "Do you really want to delete '%s'?" \ % selection.name) == gtk.RESPONSE_YES: self.project_data.remove(selection) def on_project_data__selection_changed(self, _, elem): """When a new item is selected in the treeview this function loads the main area of the window with the corresponding form and data. """ slave = self.get_parent().get_slave('workarea') if slave: self.get_parent().detach_slave('workarea') if isinstance(elem, Layer): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Edit Layer %s' % elem.name, elem) form = LayerEditor(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Meta): self.undo_stack.start_new_action('Edit Meta', elem) meta_form = MetaForm(self.meta, self) self.get_parent().attach_slave('workarea', meta_form) meta_form.focus_toplevel() meta_form.focus_topmost() elif isinstance(elem, OutputList): self.undo_stack.start_new_action('Edit Output', elem) form = OutputForm(self.output_list, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Parameter): self.undo_stack.start_new_action('Edit Parameter %s' % elem.name, elem) form = ParameterForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Process): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Edit Process %s' % elem.name, elem) form = ProcessForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, ProcessList): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Batch process editing', elem) form = BatchProcessForm(self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Species): self.undo_stack.start_new_action('Edit species', elem) form = SpeciesForm(elem, self.project_data) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, SpeciesList): self.undo_stack.start_new_action('Edit default species', elem) form = SpeciesListForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, LayerList): self.undo_stack.start_new_action('Edit lattice', elem) dimension = self.meta.model_dimension form = LatticeForm(elem, dimension, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() else: self.get_parent().toast('Not implemented, yet(%s).' % type(elem))
class DetailsTab(Gtk.VBox): details_dialog_class = None def __init__(self, model, parent): super(DetailsTab, self).__init__() self.model = model self._parent = parent self.set_spacing(6) self.set_border_width(6) self.klist = ObjectTree(self.get_columns()) self.populate() self.pack_start(self.klist, True, True, 0) self.klist.show() if len(self.klist) and self.get_details_dialog_class(): self.button_box = Gtk.HButtonBox() self.button_box.set_layout(Gtk.ButtonBoxStyle.START) details_button = Gtk.Button.new_with_label(self.details_lbl) self.button_box.pack_start(details_button, True, True, 0) details_button.set_sensitive(bool(self.klist.get_selected())) details_button.show() self.pack_end(self.button_box, False, False, 0) self.button_box.show() self.button_box.details_button = details_button details_button.connect('clicked', self._on_details_button__clicked) self.klist.connect('row-activated', self._on_klist__row_activated) self.klist.connect('selection-changed', self._on_klist__selection_changed) self.setup_widgets() def refresh(self): """Refreshes the list of respective tab.""" self.klist.clear() self.klist.add_list(self.populate()) def get_columns(self): """Returns a list of columns this tab should show.""" raise NotImplementedError def show_details(self): """Called when the details button is clicked. Displays the details of the selected object in the list.""" model = self.get_details_model(self.klist.get_selected()) run_dialog(self.get_details_dialog_class(), parent=self._parent, store=self._parent.store, model=model, visual_mode=True) def get_label(self): """Returns the name of the tab.""" label = Gtk.Label(label=self.labels[1]) return label def get_details_model(self, model): """Subclassses can overwrite this method if the details dialog class needs a model different than the one on the list.""" return model def get_details_dialog_class(self): """Subclasses must return the dialog that should be displayed for more information about the item on the list""" return self.details_dialog_class def setup_widgets(self): """Override this if tab needs to do some custom widget setup.""" # # Callbacks # def _on_details_button__clicked(self, button): self.show_details() def _on_klist__row_activated(self, klist, item): self.show_details() def _on_klist__selection_changed(self, klist, data): self.button_box.details_button.set_sensitive(bool(data))
class AdditionListSlave(SearchSlave): """A slave that offers a simple list and its management. This slave also has the option to display a small message right next to the buttons """ domain = 'stoq' toplevel_name = gladefile = 'AdditionListSlave' widgets = ('add_button', 'delete_button', 'klist', 'list_vbox', 'edit_button') gsignal('before-edit-item', object, retval=object) gsignal('on-edit-item', object) gsignal('on-add-item', object) gsignal('before-delete-items', object) gsignal('after-delete-items') def __init__(self, store, columns=None, editor_class=None, klist_objects=None, visual_mode=False, restore_name=None, tree=False): """ Creates a new AdditionListSlave object :param store: a store :param columns: column definitions :type columns: sequence of :class:`kiwi.ui.objectlist.Columns` :param editor_class: the window that is going to be open when user clicks on add_button or edit_button. :type: editor_class: a :class:`stoqlib.gui.editors.BaseEditor` subclass :param klist_objects: initial objects to insert into the list :param visual_mode: if we are working on visual mode, that means, not possible to edit the model on this object type visual_mode: bool :param restore_name: the name used to save and restore the columns on a cache system (e.g. pickle) :type restore_name: basestring :param tree: Indication of which kind of list we are adding. If `True` ObjectTree otherwise ObjectList will be added """ columns = columns or self.get_columns() SearchSlave.__init__(self, columns=columns, restore_name=restore_name, store=store) self.tree = tree self.klist = ObjectTree() if tree else ObjectList() self.list_vbox.add(self.klist) self.list_vbox.show_all() if not self.columns: raise StoqlibError("columns must be specified") self.visual_mode = visual_mode self.store = store self.set_editor(editor_class) self._can_edit = True self._callback_id = None if self.visual_mode: self.hide_add_button() self.hide_edit_button() self.hide_del_button() items = klist_objects or self.get_items() self._setup_klist(items) self._update_sensitivity() def _setup_klist(self, klist_objects): self.klist.set_columns(self.columns) self.klist.set_selection_mode(gtk.SELECTION_MULTIPLE) if self.tree: (self.klist.append(obj.parent_item, obj) for obj in klist_objects) else: self.klist.add_list(klist_objects) if self.visual_mode: self.klist.set_sensitive(False) def _update_sensitivity(self, *args): if self.visual_mode: return can_delete = _can_edit = True objs = self.get_selection() if not objs: _can_edit = can_delete = False elif len(objs) > 1: _can_edit = False self.add_button.set_sensitive(True) self.edit_button.set_sensitive(_can_edit) self.delete_button.set_sensitive(can_delete) def _edit_model(self, model=None, parent=None): edit_mode = model result = self.emit('before-edit-item', model) if result is None: result = self.run_editor(model) if not result: return if edit_mode: self.emit('on-edit-item', result) self.klist.update(result) else: if self.tree: self.klist.append(parent, result) else: self.klist.append(result) # Emit the signal after we added the item to the list to be able to # check the length of the list in our validation callbacks. self.emit('on-add-item', result) # As we have a selection extended mode for kiwi list, we # need to unselect everything before select the new instance. self.klist.unselect_all() self.klist.select(result) self._update_sensitivity() def _edit(self): if not self._can_edit: return objs = self.get_selection() qty = len(objs) if qty != 1: raise SelectionError( ("Please select only one item before choosing Edit." "\nThere are currently %d items selected") % qty) self._edit_model(objs[0]) def _clear(self): objs = self.get_selection() qty = len(objs) if qty < 1: raise SelectionError('There are no objects selected') msg = stoqlib_ngettext( _('Delete this item?'), _('Delete these %d items?') % qty, qty) delete_label = stoqlib_ngettext( _("Delete item"), _("Delete items"), qty) keep_label = stoqlib_ngettext( _("Keep it"), _("Keep them"), qty) if not yesno(msg, gtk.RESPONSE_NO, delete_label, keep_label): return self.emit('before-delete-items', objs) if qty == len(self.klist): self.klist.clear() else: for obj in objs: self.klist.remove(obj) self.klist.unselect_all() self._update_sensitivity() self.emit('after-delete-items') # # Hooks # def get_items(self): return [] def get_columns(self): raise NotImplementedError("get_columns must be implemented in " "subclasses") def run_editor(self, model): """This can be overriden to provide a custom run_dialog line, or a conversion function for the model """ if self._editor_class is None: raise TypeError( "%s cannot create or edit items without the editor_class " "argument set" % (self.__class__.__name__)) self.store.savepoint('before_run_editor_addition') retval = run_dialog(self._editor_class, None, store=self.store, model=model) if not retval: self.store.rollback_to_savepoint('before_run_editor_addition') return retval def delete_model(self, model): """Deletes a model, can be overridden in subclass :param model: model to delete """ model.__class__.delete(model.id, store=self.store) # # Public API # def add_extra_button(self, label=None, stock=None): """Add an extra button on the this slave The extra button will be appended at the end of the button box, the one containing the add/edit/delete buttons :param label: label of the button, can be ``None`` if stock is passed :param stock: stock label of the button, can be ``None`` if label is passed :param returns: the button added :rtype: gtk.Button """ if label is None and stock is None: raise TypeError("You need to provide a label or a stock argument") button = gtk.Button(label=label, stock=stock) button.set_property('can_focus', True) self.button_box.pack_end(button, False, False) button.show() return button def set_message(self, message, details_callback=None): """Display a simple message on a label, next to the add, edit, delete buttons :param message: a message with properly escaped markup """ self.message_hbox.set_visible(True) self.message_details_button.set_visible(bool(details_callback)) if details_callback: if self._callback_id: self.message_details_button.disconnect(self._callback_id) self._callback_id = self.message_details_button.connect( 'clicked', details_callback) self.message_label.set_markup(message) def clear_message(self): self.message_hbox.set_visible(False) def get_selection(self): # XXX: add get_selected_rows and raise exceptions if not in the # right mode if self.klist.get_selection_mode() == gtk.SELECTION_MULTIPLE: return self.klist.get_selected_rows() selection = self.klist.get_selected() if not selection: return [] return [selection] def hide_add_button(self): self.add_button.hide() def hide_edit_button(self): self._can_edit = False self.edit_button.hide() def hide_del_button(self): self.delete_button.hide() def set_editor(self, editor_class): if editor_class and not issubclass(editor_class, (BaseEditor, BaseWizard)): raise TypeError("editor_class must be a BaseEditor subclass") self._editor_class = editor_class # # Signal handlers # def on_klist__row_activated(self, *args): self._edit() def on_klist__selection_changed(self, *args): self._update_sensitivity() def on_add_button__clicked(self, button): self._edit_model() def on_edit_button__clicked(self, button): self._edit() def on_delete_button__clicked(self, button): self._clear()
class DetailsTab(gtk.VBox): details_dialog_class = None def __init__(self, model, parent): super(DetailsTab, self).__init__() self.model = model self._parent = parent self.set_spacing(6) self.set_border_width(6) self.klist = ObjectTree(self.get_columns()) self.populate() self.pack_start(self.klist) self.klist.show() if len(self.klist) and self.get_details_dialog_class(): self.button_box = gtk.HButtonBox() self.button_box.set_layout(gtk.BUTTONBOX_START) details_button = gtk.Button(self.details_lbl) self.button_box.pack_start(details_button) details_button.set_sensitive(bool(self.klist.get_selected())) details_button.show() self.pack_end(self.button_box, False, False) self.button_box.show() self.button_box.details_button = details_button details_button.connect('clicked', self._on_details_button__clicked) self.klist.connect('row-activated', self._on_klist__row_activated) self.klist.connect('selection-changed', self._on_klist__selection_changed) self.setup_widgets() def refresh(self): """Refreshes the list of respective tab.""" self.klist.clear() self.klist.add_list(self.populate()) def get_columns(self): """Returns a list of columns this tab should show.""" raise NotImplementedError def show_details(self): """Called when the details button is clicked. Displays the details of the selected object in the list.""" model = self.get_details_model(self.klist.get_selected()) run_dialog(self.get_details_dialog_class(), parent=self._parent, store=self._parent.store, model=model, visual_mode=True) def get_label(self): """Returns the name of the tab.""" label = gtk.Label(self.labels[1]) return label def get_details_model(self, model): """Subclassses can overwrite this method if the details dialog class needs a model different than the one on the list.""" return model def get_details_dialog_class(self): """Subclasses must return the dialog that should be displayed for more information about the item on the list""" return self.details_dialog_class def setup_widgets(self): """Override this if tab needs to do some custom widget setup.""" # # Callbacks # def _on_details_button__clicked(self, button): self.show_details() def _on_klist__row_activated(self, klist, item): self.show_details() def _on_klist__selection_changed(self, klist, data): self.button_box.details_button.set_sensitive(bool(data))
class NoteUI(GladeSlaveDelegate): def __init__(self, parent, mo): self.parent = parent self.mo = mo self.factory = Factory() # Set up the user interface GladeSlaveDelegate.__init__(self, gladefile="mo_tab_notes", toplevel_name="window_main") noteColumns = [ Column("summary", title='Title', data_type=str), ] #self.treeview_note = ObjectList(noteColumns) self.treeview_note = ObjectTree(noteColumns) self.vbox_notelist.add(self.treeview_note) # Connect signals self.treeview_note.connect('row-activated', self.treeview_note__row_activated) self.treeview_note.connect('selection-changed', self.treeview_note__selection_changed) self.treeview_note.connect('key-press-event', self.treeview_note__key_press_event) self.refresh() def refresh(self): self.treeview_note.clear() for journal in self.mo.cal_model.get_journals(): parent = self.mo.cal_model.get_model_by_uid(journal.get_related_to()) self.treeview_note.append(parent, journal) def on_toolbutton_add__clicked(self, *args): journal = self.factory.journal() journal = miniorganizer.ui.NoteEditUI(self.mo, journal).run() if journal: self.mo.cal_model.add(journal) self.treeview_note.append(None, journal) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_addsub__clicked(self, *args): parent_journal = self.treeview_note.get_selected() journal = self.factory.journal(parent_journal) if miniorganizer.ui.NoteEditUI(self.mo, journal).run(): self.mo.cal_model.add(journal) self.treeview_note.append(parent_journal, journal) self.treeview_note.expand(parent_journal) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_edit__clicked(self, *args): sel_note = self.treeview_note.get_selected() self.treeview_note__row_activated(self.treeview_note, sel_note) def on_toolbutton_remove__clicked(self, *args): sel_note = self.treeview_note.get_selected() if sel_note: children = self.mo.cal_model.get_models_related_by_uid(sel_note.get_uid()) if children: response = dialogs.warning('This note contains sub-notes. Removing it will also remove the sub-notes. Is this what you want?', buttons=gtk.BUTTONS_YES_NO) if response != gtk.RESPONSE_YES: return self.mo.cal_model.delete(sel_note, True) self.treeview_note.remove(sel_note, True) self.parent.menuitem_save.set_sensitive(True) def treeview_note__selection_changed(self, list, selection): has_selection = selection is not None self.toolbutton_addsub.set_sensitive(has_selection) self.toolbutton_remove.set_sensitive(has_selection) self.toolbutton_edit.set_sensitive(has_selection) def treeview_note__row_activated(self, list, object): sel_note = self.treeview_note.get_selected() note = miniorganizer.ui.NoteEditUI(self.mo, sel_note).run() def treeview_note__key_press_event(self, treeview, event): if event.keyval == gtk.keysyms.Delete: self.on_toolbutton_remove__clicked()
class GTKProject(SlaveDelegate): """A facade of kmos.types.Project so that pygtk can display in a TreeView. """ def __init__(self, parent, menubar): self.project_data = ObjectTree([Column('name', use_markup=True, data_type=str, sorted=True), Column('info')]) self.project_data.connect('row-activated', self.on_row_activated) self.model_tree = Project() self._set_treeview_hooks() self.menubar = menubar self.set_parent(parent) self.filename = '' self.undo_stack = UndoStack( self.model_tree.__repr__, self.import_xml_file, self.project_data.select, menubar, self.meta, 'Initialization') SlaveDelegate.__init__(self, toplevel=self.project_data) def _set_treeview_hooks(self): """Fudge function to import to access function to kmos.types.Project to kmos.gui.GTKProject. """ self.project_data.clear() # Meta self.meta = self.project_data.append(None, self.model_tree.meta) self.model_tree.meta = self.meta # Layer List self.model_tree.add_layer = self.add_layer self.layer_list = self.project_data.append(None, self.model_tree.layer_list) self.get_layers = lambda :\ sorted(self.project_data.get_descendants(self.layer_list), key=lambda x: x.name) self.model_tree.get_layers = self.get_layers self.lattice = self.layer_list # Parameter List self.parameter_list = self.project_data.append(None, self.model_tree.parameter_list) self.add_parameter = lambda parameter :\ self.project_data.append(self.parameter_list, parameter) self.model_tree.add_parameter = self.add_parameter self.get_parameters = lambda :\ sorted(self.project_data.get_descendants(self.parameter_list), key=lambda x: x.name) self.model_tree.get_parameters = self.get_parameters # Species List self.species_list = self.project_data.append(None, self.model_tree.species_list) self.add_species = lambda species :\ self.project_data.append(self.species_list, species) self.model_tree.add_species = self.add_species self.get_speciess = lambda :\ sorted(self.project_data.get_descendants(self.species_list), key=lambda x: x.name) self.model_tree.get_speciess = self.get_speciess # Process List self.process_list = self.project_data.append(None, self.model_tree.process_list) self.add_process = lambda process:\ self.project_data.append(self.process_list, process) self.model_tree.add_process = self.add_process self.get_processes = lambda :\ sorted(self.project_data.get_descendants(self.process_list), key=lambda x: x.name) self.model_tree.get_processes = self.get_processes # Output List self.output_list = self.project_data.append(None, self.model_tree.output_list) self.add_output = lambda output:\ self.project_data.append(self.output_list, output) self.model_tree.add_output = self.add_output self.get_outputs = lambda : \ sorted(self.project_data.get_descendants(self.output_list), key=lambda x: x.name) self.model_tree.get_outputs = self.get_outputs def add_layer(self, layer): self.project_data.append(self.layer_list, layer) if len(self.get_layers()) == 1 : self.set_default_layer(layer.name) self.set_substrate_layer(layer.name) def set_default_species(self, species): self.model_tree.species_list.default_species = species def set_substrate_layer(self, layer): self.model_tree.layer_list.substrate_layer = layer def set_default_layer(self, layer): self.model_tree.layer_list.default_layer = layer def update(self, model): self.project_data.update(model) def on_row_activated(self, tree, data): if isinstance(data, Layer): data.active = not data.active def get_name(self): if self.filename: return os.path.basename(self.filename) else: return 'Untitled' def __repr__(self): return str(self.model_tree) def import_xml_file(self, filename): self.model_tree.import_xml_file(filename) self.expand_all() def expand_all(self): """Expand all list of the project tree """ self.project_data.expand(self.species_list) self.project_data.expand(self.layer_list) self.project_data.expand(self.parameter_list) self.project_data.expand(self.process_list) self.project_data.expand(self.output_list) def on_key_press(self, _, event): """When the user hits the keyboard focusing the treeview this event is triggered. Right now the only supported function is to deleted the selected item """ selection = self.project_data.get_selected() if gtk.gdk.keyval_name(event.keyval) == 'Delete': if(isinstance(selection, Species) or isinstance(selection, Process) or isinstance(selection, Parameter) or isinstance(selection, Layer)): if kiwi.ui.dialogs.yesno( "Do you really want to delete '%s'?" \ % selection.name) == gtk.RESPONSE_YES: self.project_data.remove(selection) def on_project_data__selection_changed(self, _, elem): """When a new item is selected in the treeview this function loads the main area of the window with the corresponding form and data. """ slave = self.get_parent().get_slave('workarea') if slave: self.get_parent().detach_slave('workarea') if isinstance(elem, Layer): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Edit Layer %s' % elem.name, elem) form = LayerEditor(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Meta): self.undo_stack.start_new_action('Edit Meta', elem) meta_form = MetaForm(self.meta, self) self.get_parent().attach_slave('workarea', meta_form) meta_form.focus_toplevel() meta_form.focus_topmost() elif isinstance(elem, OutputList): self.undo_stack.start_new_action('Edit Output', elem) form = OutputForm(self.output_list, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Parameter): self.undo_stack.start_new_action('Edit Parameter %s' % elem.name, elem) form = ParameterForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Process): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Edit Process %s' % elem.name, elem) form = ProcessForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, ProcessList): if self.meta.model_dimension in [1, 3]: self.get_parent().toast('Only 2d supported') return self.undo_stack.start_new_action('Batch process editing', elem) form = BatchProcessForm(self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, Species): self.undo_stack.start_new_action('Edit species', elem) form = SpeciesForm(elem, self.project_data) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, SpeciesList): self.undo_stack.start_new_action('Edit default species', elem) form = SpeciesListForm(elem, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() elif isinstance(elem, LayerList): self.undo_stack.start_new_action('Edit lattice', elem) dimension = self.meta.model_dimension form = LatticeForm(elem, dimension, self) self.get_parent().attach_slave('workarea', form) form.focus_topmost() else: self.get_parent().toast('Not implemented, yet(%s).' % type(elem))
class Window(object): """This is the browser application.""" def __init__(self): ## FIXME: The GUI should appear straight away, connecting should ## happen after! # Set up the main window self.window = gtk.Window() self.window.set_title("PubSub Browser") self.window.set_default_size(400, 400) self.window.connect("destroy", self.quit) # Divide it vertically self.vbox = gtk.VBox() self.window.add(self.vbox) # This holds the location entry and the Get button self.top_box = gtk.HBox() self.vbox.pack_start(self.top_box, expand=False) self.location_label = gtk.Label("Server:") self.top_box.pack_start(self.location_label, expand=False) # This is where the server location is given self.location_entry = gtk.Entry() self.top_box.pack_start(self.location_entry) # This button run get_button_released to fetch the server's nodes self.get_button = gtk.Button(label="Get") self.get_button.connect("released", self.get_button_released) self.top_box.pack_end(self.get_button, expand=False) # Draw the tree using Kiwi, since plain GTK is a pain :P # The attribute is the data, ie. a Column looking for attribute # "foo", when appended by an object bar, will show bar.foo # We can put multiple things into a column (eg. an icon and a # label) by making 2 columns and passing the first to the second self.tree_columns = [\ Column(attribute='icon', title='Nodes', use_stock=True, \ justify=gtk.JUSTIFY_LEFT, icon_size=gtk.ICON_SIZE_MENU), \ Column(attribute='name', justify=gtk.JUSTIFY_LEFT, column='icon')] self.tree_columns[0].expand = False self.tree_columns[1].expand = False self.tree_view = ObjectTree(self.tree_columns) self.tree_view.connect("selection-changed", self.selection_changed) self.vbox.pack_start(self.tree_view) # This holds the Add button and the Delete button self.bottom_box = gtk.HBox() self.vbox.pack_end(self.bottom_box, expand=False) # Make the Delete button, which runs the delete_button_released method self.delete_button = gtk.Button(stock=gtk.STOCK_REMOVE) try: # Attempt to change the label of the stock button delete_label = self.delete_button.get_children()[0] delete_label = delete_label.get_children()[0].get_children()[1] delete_label = delete_label.set_label("Delete Selection") except: # If it fails then just go back to the default self.delete_button = gtk.Button(stock=gtk.STOCK_REMOVE) self.delete_button.connect("released", self.delete_button_released) self.delete_button.set_sensitive(False) self.bottom_box.pack_start(self.delete_button, expand=True) # Make the Properties button, which runs the properties_button_released method self.properties_button = gtk.Button(stock=gtk.STOCK_PROPERTIES) try: # Attempt to change the label of the stock button properties_label = self.properties_button.get_children()[0] properties_label = properties_label.get_children()[0].get_children()[1] properties_label = properties_label.set_label("Node Properties...") except: # If it fails then just go back to the default self.properties_button = gtk.Button(stock=gtk.STOCK_PROPERTIES) self.properties_button.connect("released", self.properties_button_released) self.properties_button.set_sensitive(False) self.bottom_box.pack_start(self.properties_button, expand=True) # Make the Add button, which runs the add_button_released method self.add_button = gtk.Button(stock=gtk.STOCK_ADD) try: # Attempt to change the label of the stock button add_label = self.add_button.get_children()[0] add_label = add_label.get_children()[0].get_children()[1] add_label = add_label.set_label("Add Child...") except: # If it fails then just go back to the default self.add_button = gtk.Button(stock=gtk.STOCK_ADD) self.add_button.connect("released", self.add_button_released) self.add_button.set_sensitive(False) self.bottom_box.pack_end(self.add_button, expand=True) # This handles our XMPP connection. Feel free to change the JID # and password self.client = pubsubclient.PubSubClient("test1@localhost", "test") # Using the tree to store everything seems to have a few # glitches, so we use a regular list as our definitive memory self.known = [] def selection_changed(self, list, object): self.add_button.set_sensitive(True) if type(object) == type(Node()): self.delete_button.set_sensitive(True) self.properties_button.set_sensitive(True) elif type(object) == type(Server()): self.delete_button.set_sensitive(False) self.properties_button.set_sensitive(False) def get_button_released(self, arg): """This is run when the Get button is pressed. It adds the given server to the tree and runs get_nodes with that server.""" listed_server = False # Assume this server is not listed # Check every row of the tree to see if it matches given server for known_server in self.tree_view: if type(known_server) == type(Server()) and \ self.location_entry.get_text() == str(known_server): listed_server = True # True if server is listed # If we didn't find the server in the tree then add it if not listed_server: server = Server(name=self.location_entry.get_text()) server.icon = gtk.STOCK_NETWORK self.tree_view.append(None, server) self.known.append(server) # Get the PubSub nodes on this server self.client.get_nodes(self.location_entry.get_text(), None, return_function=self.handle_incoming) def handle_incoming(self, nodes): # Go through each new node for node in nodes: # Assume we do not already know about this node node_is_known = False # Go through each node that we know about for known_entry in self.known: # See if this node is the same as the known node being checked if known_entry.name == node.name: ## FIXME: Needs to check server node_is_known = True if not node_is_known: parent = None for known_entry in self.known: if known_entry.name == node.parent.name: ## FIXME: Needs to check server parent = known_entry self.known.append(node) self.tree_view.append(parent, node) node.get_information(self.client, self.handle_information) node.get_sub_nodes(self.client, self.handle_incoming) def handle_information(self, node): known = False if node.type == 'leaf': if node.name is not None and node.server is not None: for known_entry in self.known: if known_entry.name == node.name: known_entry.set_type('leaf') known_entry.icon = gtk.STOCK_FILE elif node.type == 'collection': if node.name is not None and node.server is not None: for known_entry in self.known: if known_entry.name == node.name and known_entry.server == node.server: known_entry.set_type('collection') known_entry.icon = gtk.STOCK_DIRECTORY node.get_sub_nodes(self.client, self.handle_incoming) def handle_node_creation(self, return_value): if return_value == 0: self.tree_view.get_selected().get_sub_nodes(self.client, self.handle_incoming) else: print return_value def delete_button_released(self, args): to_delete = self.tree_view.get_selected() if type(to_delete) == type(Node()): self.client.delete_a_node(to_delete.server, str(to_delete), return_function=self.handle_deleted) def handle_deleted(self, return_value): if return_value == 0: deleted = self.tree_view.get_selected() self.tree_view.remove(deleted) self.known.remove(deleted) else: print return_value def add_button_released(self, args): self.add_window = {} self.add_window["parent"] = self.tree_view.get_selected() self.add_window["window"] = gtk.Window() self.add_window["window"].set_title("Add new node to " + self.add_window["parent"].name) self.add_window["vbox"] = gtk.VBox() self.add_window["window"].add(self.add_window["vbox"]) self.add_window["top_box"] = gtk.HBox() self.add_window["vbox"].pack_start(self.add_window["top_box"], expand=False) self.add_window["name_label"] = gtk.Label("Name:") self.add_window["name_entry"] = gtk.Entry() self.add_window["top_box"].pack_start(self.add_window["name_label"], expand=False) self.add_window["top_box"].pack_end(self.add_window["name_entry"], expand=True) self.add_window["bottom_box"] = gtk.HBox() self.add_window["vbox"].pack_end(self.add_window["bottom_box"], expand=False) self.add_window["add_button"] = gtk.Button(stock=gtk.STOCK_ADD) self.add_window["add_button"].connect("released", self.add_add_released) self.add_window["bottom_box"].pack_end(self.add_window["add_button"], expand=False) self.add_window["middle_box"] = gtk.HBox() self.add_window["vbox"].pack_end(self.add_window["middle_box"], expand=False) self.add_window["type_label"] = gtk.Label("Type:") self.add_window["type_select"] = gtk.combo_box_new_text() self.add_window["type_select"].append_text("Leaf") self.add_window["type_select"].append_text("Collection") self.add_window["middle_box"].pack_start(self.add_window["type_label"], expand=False) self.add_window["middle_box"].pack_end(self.add_window["type_select"], expand=True) self.add_window["window"].show_all() def add_add_released(self, args): name = self.add_window["name_entry"].get_text() node_type = self.add_window["type_select"].get_active_text() parent = self.add_window["parent"] self.add_node(name, node_type, parent) self.add_window["window"].destroy() del(self.add_window) def add_node(self, name, node_type, parent): """Request a new child node of parent. For top-level a node parent should be a Server. node_type is either "leaf" or "collection".""" # This half runs if the parent is a Server if type(parent) == type(pubsubclient.Server()): # Request a top-level leaf node if node_type == 'Leaf': self.client.get_new_leaf_node(parent, \ self.add_window["name_entry"].get_text(), None, None, return_function=self.handle_node_creation) # Request a top-level collection node elif node_type == 'Collection': self.client.get_new_collection_node(parent, \ self.add_window["name_entry"].get_text(), None, None, return_function=self.handle_node_creation) # This half runs if the parent is a Node elif type(parent) == type(pubsubclient.Node()): # Request a child leaf node if node_type == 'Leaf': self.client.get_new_leaf_node(parent.server, \ self.add_window["name_entry"].get_text(), parent, None, return_function=self.handle_node_creation) # Request a child collection node elif node_type == 'Collection': self.client.get_new_collection_node(parent.server, \ self.add_window["name_entry"].get_text(), parent, None, return_function=self.handle_node_creation) def properties_button_released(self, args): ## FIXME: This should allow multiple properties windows to be open at once # Setup a window containing a notebook self.properties_window = {} self.properties_window["node"] = self.tree_view.get_selected() self.properties_window["window"] = gtk.Window() self.properties_window["window"].set_title("Properties of " + str(self.properties_window["node"])) self.properties_window["window"].set_default_size(350, 450) self.properties_window["notebook"] = gtk.Notebook() self.properties_window["window"].add(self.properties_window["notebook"]) # Add the Metadata page self.properties_window["metadata_page"] = gtk.VBox() self.properties_window["metadata_label"] = gtk.Label("Metadata") self.properties_window["notebook"].append_page(self.properties_window["metadata_page"], tab_label=self.properties_window["metadata_label"]) # Add the Affiliations page self.properties_window["affiliations_page"] = gtk.VBox() self.properties_window["affiliations_label"] = gtk.Label("Affiliations") self.properties_window["notebook"].append_page(self.properties_window["affiliations_page"], tab_label=self.properties_window["affiliations_label"]) node = self.tree_view.get_selected() node.get_information(self.client, self.information_received) node.request_all_affiliated_entities(self.client, return_function=self.properties_received) def information_received(self, info_dict): # Generate the contents of the Metadata page ## FIXME: It would be awesome if this were generated automatically from what is found :) # The Name entry self.properties_window["name_box"] = gtk.HBox() self.properties_window["metadata_page"].pack_start(self.properties_window["name_box"], expand=False) self.properties_window["name_label"] = gtk.Label("Name:") self.properties_window["name_box"].pack_start(self.properties_window["name_label"], expand=False) self.properties_window["name_entry"] = gtk.Entry() self.properties_window["name_entry"].set_text(str(self.properties_window["node"])) # Default to Node's name self.properties_window["name_box"].pack_start(self.properties_window["name_entry"], expand=True) self.properties_window["name_set"] = gtk.Button(label="Set") self.properties_window["name_set"].connect("released", self.set_name) self.properties_window["name_box"].pack_end(self.properties_window["name_set"], expand=False) # The Title entry ## FIXME: This should default to the node's title self.properties_window["title_box"] = gtk.HBox() self.properties_window["metadata_page"].pack_start(self.properties_window["title_box"], expand=False) self.properties_window["title_label"] = gtk.Label("Title:") self.properties_window["title_box"].pack_start(self.properties_window["title_label"], expand=False) self.properties_window["title_entry"] = gtk.Entry() self.properties_window["title_box"].pack_start(self.properties_window["title_entry"], expand=True) self.properties_window["title_set"] = gtk.Button(label="Set") self.properties_window["title_set"].connect("released", self.set_title) self.properties_window["title_box"].pack_end(self.properties_window["title_set"], expand=False) def properties_received(self, affiliation_dictionary): # Add a warning about affiliation status self.properties_window["warning_label"] = gtk.Label("Note that a Jabber ID can only be in one state at a time. Adding a Jabber ID to a category will remove it from the others. There must always be at least one owner.") self.properties_window["warning_label"].set_line_wrap(True) self.properties_window["affiliations_page"].pack_start(self.properties_window["warning_label"], expand=False) # Owners frame self.properties_window["owners_frame"] = gtk.Frame("Owners") self.properties_window["affiliations_page"].pack_start(self.properties_window["owners_frame"], expand=True) self.properties_window["owners_box"] = gtk.HBox() self.properties_window["owners_frame"].add(self.properties_window["owners_box"]) # Owners list self.properties_window["owners_column"] = Column(attribute="name", title="Jabber ID") self.properties_window["owners"] = ObjectList([self.properties_window["owners_column"]]) self.properties_window["owners_box"].pack_start(self.properties_window["owners"], expand=True) # Add Owner button self.properties_window["owners_buttons"] = gtk.VBox() self.properties_window["owners_box"].pack_end(self.properties_window["owners_buttons"], expand=False) self.properties_window["add_owner"] = gtk.Button(stock=gtk.STOCK_ADD) try: # Attempt to change the label of the stock button label = self.properties_window["add_owner"].get_children()[0] label = label.get_children()[0].get_children()[1] label = label.set_label("Add...") except: # If it fails then just go back to the default self.properties_window["add_owner"] = gtk.Button(stock=gtk.STOCK_ADD) self.properties_window["add_owner"].connect("released", self.add_owner) self.properties_window["owners_buttons"].pack_start(self.properties_window["add_owner"], expand=False) # Remove Owner button self.properties_window["remove_owner"] = gtk.Button(stock=gtk.STOCK_REMOVE) self.properties_window["remove_owner"].connect("released", self.remove_owner) self.properties_window["owners_buttons"].pack_end(self.properties_window["remove_owner"], expand=False) # Publishers frame self.properties_window["publishers_frame"] = gtk.Frame("Publishers") self.properties_window["affiliations_page"].pack_start(self.properties_window["publishers_frame"], expand=True) self.properties_window["publishers_box"] = gtk.HBox() self.properties_window["publishers_frame"].add(self.properties_window["publishers_box"]) # Add Publisher button self.properties_window["publishers_buttons"] = gtk.VBox() self.properties_window["publishers_box"].pack_end(self.properties_window["publishers_buttons"], expand=False) self.properties_window["add_publisher"] = gtk.Button(stock=gtk.STOCK_ADD) try: # Attempt to change the label of the stock button label = self.properties_window["add_publisher"].get_children()[0] label = label.get_children()[0].get_children()[1] label = label.set_label("Add...") except: # If it fails then just go back to the default self.properties_window["add_publisher"] = gtk.Button(stock=gtk.STOCK_ADD) self.properties_window["add_publisher"].connect("released", self.add_publisher) self.properties_window["publishers_buttons"].pack_start(self.properties_window["add_publisher"], expand=False) # Remove Publisher button self.properties_window["remove_publisher"] = gtk.Button(stock=gtk.STOCK_REMOVE) self.properties_window["remove_publisher"].connect("released", self.remove_publisher) self.properties_window["publishers_buttons"].pack_end(self.properties_window["remove_publisher"], expand=False) # Publishers list self.properties_window["publishers_column"] = Column(attribute="name", title="Jabber ID") self.properties_window["publishers"] = ObjectList([self.properties_window["publishers_column"]]) self.properties_window["publishers_box"].pack_start(self.properties_window["publishers"], expand=True) # Outcasts frame self.properties_window["outcasts_frame"] = gtk.Frame("Outcasts") self.properties_window["affiliations_page"].pack_start(self.properties_window["outcasts_frame"], expand=True) self.properties_window["outcasts_box"] = gtk.HBox() self.properties_window["outcasts_frame"].add(self.properties_window["outcasts_box"]) # Add Outcast button self.properties_window["outcasts_buttons"] = gtk.VBox() self.properties_window["outcasts_box"].pack_end(self.properties_window["outcasts_buttons"], expand=False) self.properties_window["add_outcast"] = gtk.Button(stock=gtk.STOCK_ADD) try: # Attempt to change the label of the stock button label = self.properties_window["add_outcast"].get_children()[0] label = label.get_children()[0].get_children()[1] label = label.set_label("Add...") except: # If it fails then just go back to the default self.properties_window["add_outcast"] = gtk.Button(stock=gtk.STOCK_ADD) self.properties_window["add_outcast"].connect("released", self.add_outcast) self.properties_window["outcasts_buttons"].pack_start(self.properties_window["add_outcast"], expand=False) # Remove Outcast button self.properties_window["remove_outcast"] = gtk.Button(stock=gtk.STOCK_REMOVE) self.properties_window["remove_outcast"].connect("released", self.remove_outcast) self.properties_window["outcasts_buttons"].pack_end(self.properties_window["remove_outcast"], expand=False) # Outcasts list self.properties_window["outcasts_column"] = Column(attribute="name", title="Jabber ID") self.properties_window["outcasts"] = ObjectList([self.properties_window["outcasts_column"]]) self.properties_window["outcasts_box"].pack_start(self.properties_window["outcasts"], expand=True) self.properties_window["window"].show_all() if "owner" in affiliation_dictionary.keys(): for jid in affiliation_dictionary["owner"]: self.properties_window["owners"].append(jid) if "publisher" in affiliation_dictionary.keys(): for jid in affiliation_dictionary["publisher"]: self.properties_window["publishers"].append(jid) if "outcast" in affiliation_dictionary.keys(): for jid in affiliation_dictionary["outcast"]: self.properties_window["outcasts"].append(jid) def set_name(self, args): pass def set_title(self, args): pass def add_owner(self, args): self.add_owner_window = {} self.add_owner_window["window"] = gtk.Window() self.add_owner_window["window"].set_title("Add owner to " + str(self.tree_view.get_selected())) self.add_owner_window["box"] = gtk.HBox() self.add_owner_window["window"].add(self.add_owner_window["box"]) self.add_owner_window["jid_label"] = gtk.Label("Jabber ID:") self.add_owner_window["box"].pack_start(self.add_owner_window["jid_label"], expand=False) self.add_owner_window["jid_entry"] = gtk.Entry() self.add_owner_window["box"].pack_start(self.add_owner_window["jid_entry"], expand=False) self.add_owner_window["add_button"] = gtk.Button(stock=gtk.STOCK_ADD) self.add_owner_window["add_button"].connect("released", self.add_owner_send) self.add_owner_window["box"].pack_end(self.add_owner_window["add_button"], expand=False) self.add_owner_window["window"].show_all() def add_owner_send(self, args): self.add_owner_window["jid"] = JID(self.add_owner_window["jid_entry"].get_text()) self.tree_view.get_selected().modify_affiliations(self.client, {self.add_owner_window["jid"]:"owner"}, self.owner_added) def owner_added(self, reply): if reply == 0: self.properties_window["owners"].append(self.add_owner_window["jid"]) for jid in self.properties_window["publishers"]: if str(jid) == str(self.add_owner_window["jid"]): self.properties_window["publishers"].remove(jid) for jid in self.properties_window["outcasts"]: if str(jid) == str(self.add_owner_window["jid"]): self.properties_window["outcasts"].remove(jid) else: print "Error" self.add_owner_window["window"].destroy() del self.add_owner_window def remove_owner(self, args): self.tree_view.get_selected().modify_affiliations(self.client, {self.properties_window["owners"].get_selected():"none"}, self.owner_removed) def owner_removed(self, reply): if reply == 0: self.properties_window["owners"].remove(self.properties_window["owners"].get_selected()) else: print "Error" def add_publisher(self, args): self.add_publisher_window = {} self.add_publisher_window["window"] = gtk.Window() self.add_publisher_window["window"].set_title("Add publisher to " + str(self.tree_view.get_selected())) self.add_publisher_window["hbox"] = gtk.HBox() self.add_publisher_window["window"].add(self.add_publisher_window["hbox"]) self.add_publisher_window["jid_label"] = gtk.Label("Jabber ID:") self.add_publisher_window["hbox"].pack_start(self.add_publisher_window["jid_label"], expand=False) self.add_publisher_window["jid_entry"] = gtk.Entry() self.add_publisher_window["hbox"].pack_start(self.add_publisher_window["jid_entry"], expand=False) self.add_publisher_window["add_button"] = gtk.Button(stock=gtk.STOCK_ADD) self.add_publisher_window["add_button"].connect("released", self.add_publisher_send) self.add_publisher_window["hbox"].pack_end(self.add_publisher_window["add_button"], expand=False) self.add_publisher_window["window"].show_all() def add_publisher_send(self, args): self.add_publisher_window["jid"] = JID(self.add_publisher_window["jid_entry"].get_text()) self.tree_view.get_selected().modify_affiliations(self.client, {self.add_publisher_window["jid"]:"publisher"}, self.publisher_added) def publisher_added(self, reply): if reply == 0: self.properties_window["publishers"].append(self.add_publisher_window["jid"]) for jid in self.properties_window["owners"]: if str(jid) == str(self.add_publisher_window["jid"]): self.properties_window["owners"].remove(jid) for jid in self.properties_window["outcasts"]: if str(jid) == str(self.add_publisher_window["jid"]): self.properties_window["outcasts"].remove(jid) else: print "Error" self.add_publisher_window["window"].destroy() del self.add_publisher_window def remove_publisher(self, args): self.tree_view.get_selected().modify_affiliations(self.client, {self.properties_window["publishers"].get_selected():"none"}, self.publisher_removed) def publisher_removed(self, reply): if reply == 0: self.properties_window["publishers"].remove(self.properties_window["publishers"].get_selected()) else: print "Error" def add_outcast(self, args): self.add_outcast_window = {} self.add_outcast_window["window"] = gtk.Window() self.add_outcast_window["window"].set_title("Add outcast to " + str(self.tree_view.get_selected())) self.add_outcast_window["hbox"] = gtk.HBox() self.add_outcast_window["window"].add(self.add_outcast_window["hbox"]) self.add_outcast_window["jid_label"] = gtk.Label("Jabber ID:") self.add_outcast_window["hbox"].pack_start(self.add_outcast_window["jid_label"], expand=False) self.add_outcast_window["jid_entry"] = gtk.Entry() self.add_outcast_window["hbox"].pack_start(self.add_outcast_window["jid_entry"], expand=False) self.add_outcast_window["add_button"] = gtk.Button(stock=gtk.STOCK_ADD) self.add_outcast_window["add_button"].connect("released", self.add_outcast_send) self.add_outcast_window["hbox"].pack_end(self.add_outcast_window["add_button"], expand=False) self.add_outcast_window["window"].show_all() def add_outcast_send(self, args): self.add_outcast_window["jid"] = JID(self.add_outcast_window["jid_entry"].get_text()) self.tree_view.get_selected().modify_affiliations(self.client, {self.add_outcast_window["jid"]:"outcast"}, self.outcast_added) def outcast_added(self, reply): if reply == 0: self.properties_window["outcasts"].append(self.add_outcast_window["jid"]) for jid in self.properties_window["publishers"]: if str(jid) == str(self.add_outcast_window["jid"]): self.properties_window["publishers"].remove(jid) for jid in self.properties_window["owners"]: if str(jid) == str(self.add_outcast_window["jid"]): self.properties_window["owners"].remove(jid) else: print "Error" self.add_outcast_window["window"].destroy() del self.add_outcast_window def remove_outcast(self, args): self.tree_view.get_selected().modify_affiliations(self.client, {self.properties_window["outcasts"].get_selected():"none"}, self.outcast_removed) def outcast_removed(self, reply): if reply == 0: self.properties_window["outcasts"].remove(self.properties_window["outcasts"].get_selected()) else: print "Error" def main(self): self.window.show_all() self.client.connect() #gobject.idle_add(self.idle_process, priority=gobject.PRIORITY_LOW) gobject.timeout_add(250, self.idle_process) gtk.main() def idle_process(self): """A PubSubClient needs its process method run regularly. This method can be used to do that in gobject.timeout or idle_add.""" self.client.process() return True def quit(self, arg): """Exits the application. Runs when a signal to quit is received.""" gtk.main_quit()
class EventUI(GladeSlaveDelegate): show_ranges = ['day', 'week', 'month', 'year'] def __init__(self, parent, mo): self.log = logging.getLogger('MINICAL') self.parent = parent self.mo = mo self.factory = Factory() self.__stop_auto_highlight = False # Disable automatic highlighting of events. self.__stop_auto_dayjump = False # Disable automatically jumping to the start of the event on selection. self.__stop_auto_treeview_update = False # FIXME GladeSlaveDelegate.__init__(self, gladefile='mo_tab_events', toplevel_name='window_main') # Set up the user interface eventColumns = [ Column('start', title='Start', data_type=datetime.datetime, sorted=True), Column('end', title='End', data_type=datetime.datetime), Column('summaryformat', title='Summary', use_markup=True), Column('duration', title='Duration', justify=gtk.JUSTIFY_RIGHT) ] self.treeview_event = ObjectTree(eventColumns) self.vbox_eventslist.add(self.treeview_event) self.combobox_display_range.set_active(self.show_ranges.index(self.mo.config['events.default_show'].lower())) cal_options = gtk.CALENDAR_WEEK_START_MONDAY if self.mo.config['events.cal_show_weeknr']: cal_options |= gtk.CALENDAR_SHOW_WEEK_NUMBERS self.calendar.set_display_options((self.calendar.get_display_options() | cal_options)) # Connect signals self.treeview_event.connect('selection-changed', self.treeview_event__selection_changed) self.treeview_event.connect('row-activated', self.treeview_event__row_activated) self.treeview_event.connect('key-press-event', self.treeview_event__key_press_event) self.on_toolbutton_today__clicked() def refresh(self): """ Refresh the entire events tab. This clears everything and rebuilds it. Call this when events are removed outside of this class. """ self.treeview_event.clear() self.calendar.clear_marks() self.on_calendar__month_changed(self.calendar) self.treeview_event__update() def on_toolbutton_add__clicked(self, *args): now = datetime.datetime.now() sel_day = self.calendar.get_date() start = datetime.datetime(sel_day[0], sel_day[1]+1, sel_day[2], now.hour, now.minute) end = start + datetime.timedelta(hours=+1) event = self.factory.event(start, end) event = miniorganizer.ui.EventEditUI(self.mo, event).run() if event: self.mo.cal_model.add(event) self.treeview_event.append(None, event) self.on_calendar__month_changed(self.calendar) self.on_calendar__day_selected(self.calendar) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_remove__clicked(self, *args): sel_event = self.treeview_event.get_selected() sel_real_event = getattr(sel_event, 'real_event', sel_event) # Delete real event instead of recurring event if sel_event != sel_real_event: response = dialogs.yesno('This is a recurring event. Deleting it will delete all recurrences. Are you sure you want to delete it?') if response == gtk.RESPONSE_NO: return else: sel_event = sel_real_event if sel_event: self.mo.cal_model.delete(sel_event) self.treeview_event.remove(sel_event) self.on_calendar__month_changed(self.calendar) self.on_calendar__day_selected(self.calendar) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_edit__clicked(self, *args): sel_event = self.treeview_event.get_selected() self.treeview_event__row_activated(self.treeview_event, sel_event) def on_toolbutton_today__clicked(self, *args): today_dt = datetime.date.today() self.calendar.select_month(today_dt.month - 1, today_dt.year) self.calendar.select_day(today_dt.day) def on_calendar__month_changed(self, calendar, *args): self.calendar.clear_marks() sel_date = self.calendar.get_date() month_start = datetime.datetime(sel_date[0], sel_date[1]+1, 1) month_end = month_start + relativedelta(months=+1, seconds=-1) events = self.mo.cal_model.get_events() + self.mo.cal_model.get_events_recurring(month_start, month_end) for event in events: event_start = event.get_start() event_end = event.get_end() self.log.debug('Event %s, start: %s, end %s' % (event.get_summary(), event_start, event_end)) # If the event falls in the month, mark the days the event spans in # the calendar. if (month_start >= event_start and month_start <= event_end) or \ (month_end >= event_start and month_end <= event_end) or \ (event_start >= month_start and event_end <= month_end): # Walk through the days of the event, marking them. delta_iter = datetime.datetime(*event_start.timetuple()[0:3]) while True: if delta_iter.year == month_start.year and delta_iter.month == month_start.month: self.calendar.mark_day(delta_iter.day) delta_iter = delta_iter + datetime.timedelta(days=+1) if delta_iter >= event_end: break def on_calendar__day_selected(self, calendar, *args): # Make sure the correct display range is shown. self.on_combobox_display_range__changed() # Retrieve the day the user selected. sel_day = self.calendar.get_date() day_start = datetime.datetime(sel_day[0], sel_day[1]+1, sel_day[2]) day_end = day_start + datetime.timedelta(days=+1) display_month = datetime.datetime(day_start.year, day_start.month, 1) # Highlight an event if it starts on the selected day. highlight_events = [] events = [event for event in self.treeview_event] for event in events: event_start = event.get_start() event_end = event.get_end() # If this is the first event that starts on the day the user # selected, highlight the item in the list of events. if event_start >= day_start and event_start < day_end: highlight_events.insert(0, event) # If the selected day occurs during an event, highlight it. We # append it to the list of events to be highlighted, so it'll only # be highlighted if no event actually starts on that day. elif (day_start > event_start and day_start < event_end) or \ (day_end > event_start and day_end < event_end) or \ (event_start > day_start and event_end < day_end): highlight_events.append(event) # Highlight the first event on the day the user selected, unless the # user manually selected an event. if not self.__stop_auto_highlight: if highlight_events and highlight_events[0] in self.treeview_event: self.__stop_auto_dayjump = True self.treeview_event.select(highlight_events[0], True) self.__stop_auto_dayjump = False else: self.treeview_event.unselect_all() def on_calendar__day_selected_double_click(self, *args): self.on_toolbutton_add__clicked() def on_combobox_display_range__changed(self, *args): # Get the currently selected date in the calendar. sel_date = self.calendar.get_date() sel_dt_start = datetime.datetime(sel_date[0], sel_date[1]+1, sel_date[2]) sel_dt_end = sel_dt_start + datetime.timedelta(days=+1) # Determine the start and end of the period that needs to be shown. display_range = self.combobox_display_range.get_active_text() if display_range == 'Day': display_start = sel_dt_start display_end = display_start + datetime.timedelta(days=+1, seconds=-1) text = '%s' % (display_start.strftime('%a %b %d %Y')) elif display_range == 'Week': display_start = sel_dt_start + datetime.timedelta(days=-sel_dt_start.weekday()) display_end = display_start + datetime.timedelta(weeks=+1, seconds=-1) text = '%s - %s' % (display_start.strftime('%a %b %d %Y'), display_end.strftime('%a %b %d %Y')) elif display_range == 'Month': display_start = sel_dt_start + datetime.timedelta(days=-(sel_dt_start.day - 1)) display_end = display_start + relativedelta(months=+1, seconds=-1) text = '%s' % (display_start.strftime('%b %Y')) elif display_range == 'Year': display_start = datetime.datetime(sel_dt_start.year, 1, 1) display_end = display_start + relativedelta(years=+1, seconds=-1) text = '%s' % (display_start.strftime('%Y')) else: raise Exception('No selected display range!') # Update the displayed range self.displayed_range.set_text(text) self.display_start = display_start self.display_end = display_end self.treeview_event__update() def treeview_event__update(self): if self.__stop_auto_treeview_update: return # First, remove all the recurring events, because they're generated on # the fly, so we can't know which ones in the list we need to remove. # Therefor we remove them every time. events_rm = [] for event in self.treeview_event: if hasattr(event, 'real_event'): events_rm.append(event) for event in events_rm: self.treeview_event.remove(event) # Add the events for the displayed range to the list events = self.mo.cal_model.get_events() + self.mo.cal_model.get_events_recurring(self.display_start, self.display_end) for event in events: event_start = event.get_start() event_end = event.get_end() # If the currently displayed range includes an event, add it to the list. if (self.display_start >= event_start and self.display_start < event_end) or \ (self.display_end >= event_start and self.display_end < event_end) or \ (event_start >= self.display_start and event_end < self.display_end): if not event in self.treeview_event: self.treeview_event.append(None, event) # Otherwise, we remove it from the list, if it's present. else: if event in self.treeview_event: self.treeview_event.remove(event) def treeview_event__row_activated(self, list, object): # FIXME: This might be more complicated than it needs to be. See todo.py's row_activated. sel_event = self.treeview_event.get_selected() sel_event = getattr(sel_event, 'real_event', sel_event) # Edit real event instead of recurring event event = miniorganizer.ui.EventEditUI(self.mo, sel_event).run() self.on_calendar__month_changed(self.calendar) self.on_calendar__day_selected(self.calendar) if sel_event in self.treeview_event: self.treeview_event.select(sel_event, True) self.parent.menuitem_save.set_sensitive(True) def treeview_event__selection_changed(self, list, selection): # Stop the treeview from automatically updating itself because that # will remove the recurring events and regenerate them (with different # instance IDs) which means the selection may be invalid. self.__stop_auto_treeview_update = True sel_event = self.treeview_event.get_selected() has_selection = sel_event is not None # Enable / disable toolbuttons self.toolbutton_remove.set_sensitive(has_selection) self.toolbutton_edit.set_sensitive(has_selection) # Do not jump to the day of the event. This is needed because an event # can be automatically selected even if it doesn't start on a # particular day. if self.__stop_auto_dayjump: self.__stop_auto_treeview_update = False return # Stop this selection from being overwritten. self.__stop_auto_highlight = True if has_selection: # Make the calendar jump to the day on which this event begins. sel_event_start = sel_event.get_start() self.calendar.select_month(sel_event_start.month - 1, sel_event_start.year) self.calendar.select_day(sel_event_start.day) # Enable automatic highlighting of items self.__stop_auto_highlight = False self.__stop_auto_treeview_update = False def treeview_event__key_press_event(self, treeview, event): if event.keyval == gtk.keysyms.Delete: self.on_toolbutton_remove__clicked()
class TodoUI(GladeSlaveDelegate): def __init__(self, parent, mo): self.parent = parent self.mo = mo self.factory = Factory() GladeSlaveDelegate.__init__(self, gladefile="mo_tab_todo", toplevel_name="window_main") # Set up the user interface todoColumns = [ Column("done", title='Done', data_type=bool, editable=True), Column("summaryformat", title='Summary', use_markup=True), Column('priority', title='Priority', sorted=True, order=gtk.SORT_DESCENDING), ColoredColumn('due', title='Due', data_type=datetime.datetime, color='red', data_func=self.color_due), Column('created', title='Created', data_type=datetime.datetime) ] self.treeview_todo = ObjectTree(todoColumns) self.vbox_todolist.add(self.treeview_todo) # Connect signals self.treeview_todo.connect('row-activated', self.treeview_todo__row_activated) self.treeview_todo.connect('selection-changed', self.treeview_todo__selection_changed) self.treeview_todo.connect('key-press-event', self.treeview_todo__key_press_event) self.refresh() def refresh(self): """ Refresh the entire todo tab. This clears everything and rebuilds it. Call this when todos are removed outside of this class. """ self.treeview_todo.clear() # Fill the user interface with information for todo in self.mo.cal_model.get_todos(): parent = self.mo.cal_model.get_model_by_uid(todo.get_related_to()) self.treeview_todo.append(parent, todo) def color_due(self, value): if not value: return(False) return(value < datetime.datetime.now()) def on_toolbutton_add__clicked(self, *args): todo = self.factory.todo() todo = miniorganizer.ui.TodoEditUI(self.mo, todo).run() if todo: self.mo.cal_model.add(todo) self.treeview_todo.append(None, todo) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_edit__clicked(self, *args): sel_todo = self.treeview_todo.get_selected() self.treeview_todo__row_activated(self.treeview_todo, sel_todo) def on_toolbutton_remove__clicked(self, *args): sel_todo = self.treeview_todo.get_selected() if sel_todo: children = self.mo.cal_model.get_models_related_by_uid(sel_todo.get_uid()) if children: response = dialogs.warning('This Todo contains sub-todos. Removing it will also remove the sub-todos. Is this what you want?', buttons=gtk.BUTTONS_YES_NO) if response != gtk.RESPONSE_YES: return self.treeview_todo.remove(sel_todo, True) self.mo.cal_model.delete(sel_todo, True) self.parent.menuitem_save.set_sensitive(True) def on_toolbutton_addsub__clicked(self, *args): parent_todo = self.treeview_todo.get_selected() todo = self.factory.todo(parent_todo) if miniorganizer.ui.TodoEditUI(self.mo, todo).run(): self.mo.cal_model.add(todo) self.treeview_todo.append(parent_todo, todo) self.treeview_todo.expand(parent_todo) self.parent.menuitem_save.set_sensitive(True) def treeview_todo__selection_changed(self, list, selection): has_selection = selection is not None self.toolbutton_addsub.set_sensitive(has_selection) self.toolbutton_remove.set_sensitive(has_selection) self.toolbutton_edit.set_sensitive(has_selection) def treeview_todo__row_activated(self, list, object): todo = miniorganizer.ui.TodoEditUI(self.mo, object).run() self.parent.menuitem_save.set_sensitive(True) def treeview_todo__key_press_event(self, treeview, event): if event.keyval == gtk.keysyms.Delete: self.on_toolbutton_remove__clicked()