class DirCombo(HBox): def __init__(self, dir=None): HBox.__init__(self, False, 2) buttonImage = Image() buttonImage.set_from_stock(STOCK_REFRESH, ICON_SIZE_MENU) self.combo = ComboBox() self.refreshButton = Button() self.refreshButton.set_image(buttonImage) self.refreshButton.connect('clicked', self.refreshButton_clicked, None) self.model = ListStore(str) self.combo.set_model(self.model) self.dir = dir self.pack_start(self.combo, False, False, 0) self.pack_start(self.refreshButton, False, False, 0) if self.dir != None and exists(self.dir): self.refresh() def refresh(self): self.combo.clear() self.model.clear() list = listdir(self.dir) cell = CellRendererText() self.combo.pack_start(cell, True) self.combo.add_attribute(cell, 'text', 0) for i in list: if i[0] != '.' and isdir(join(self.dir, i)) == True: self.model.append([i]) def refreshButton_clicked(self, widget, data): self.refresh()
class warning_dialog(Dialog): def __init__(self, parent, pacs, icon): Dialog.__init__( self, _("Warning!"), parent, DIALOG_MODAL | DIALOG_DESTROY_WITH_PARENT, (STOCK_YES, RESPONSE_YES, STOCK_NO, RESPONSE_REJECT), ) self.set_icon(pixbuf_new_from_file(icon)) self._setup_tree(pacs) self._setup_layout() def _setup_layout(self): self.set_default_size(-1, 250) label = Label( _( "This packages requires one of the packages you've selected for removal.\nDo you want to remove them all?" ) ) label.show() scr = ScrolledWindow() scr.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) scr.add(self.tree) self.vbox.pack_start(label, False, False, 0) self.vbox.pack_start(scr, True, True, 0) self.vbox.show_all() return def _setup_tree(self, pacs): self.tree = TreeView() self.model = ListStore(str, str, str) self.tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id=0) self.tree.insert_column_with_attributes(-1, "", CellRendererText(), text=1) self.tree.insert_column_with_attributes(-1, "", CellRendererText(), text=2) for pac in pacs: if pac.isold: image = "yellow" elif pac.installed: image = "green" else: image = "red" self.model.append([image, pac.name, pac.inst_ver]) continue self.tree.set_model(self.model) self.tree.show_all() return
class upgrade_confirm_dialog(Dialog): def __init__(self, parent, to_upgrade, icon): Dialog.__init__(self, _("Confirm Upgrade"), parent, DIALOG_MODAL | DIALOG_DESTROY_WITH_PARENT, (STOCK_OK, RESPONSE_ACCEPT, STOCK_CANCEL, RESPONSE_REJECT)) self.set_icon(pixbuf_new_from_file(icon)) self._setup_tree(to_upgrade) self._setup_layout() def _setup_tree(self, pacs): self.model = ListStore(str, str, str) for pac in pacs: self.model.append(["yellow", pac.name, pac.version]) continue self.tree = TreeView() self.tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id = 0) self.tree.insert_column_with_attributes(-1, "Package", CellRendererText(), text = 1) self.tree.insert_column_with_attributes(-1, "Version", CellRendererText(), text = 2) self.tree.set_model(self.model) self.tree.show() def _setup_layout(self): self.label = Label(_("Are you sure yo want to upgrade those packages?\n")) self.label.show() self.set_default_size (300, 300) scr = ScrolledWindow() scr.set_policy("automatic", "automatic") scr.add(self.tree) scr.show() self.vbox.pack_start(self.label, False, False, 0) self.vbox.pack_start(scr, True, True, 0) def run(self): retcode = Dialog.run(self) self.destroy() if retcode == RESPONSE_ACCEPT: return True else: return False
class PresenceServiceNameWatcher(VBox): def __init__(self, bus): VBox.__init__(self) self.bus = bus logger.debug('Running...') self.label = Label('Looking for Presence Service...') self.errors = ListStore(str) errors_tree = TreeView(model=self.errors) errors_tree.insert_column_with_attributes(0, 'Log', CellRendererText(), text=0) scroller = ScrolledWindow() scroller.add(errors_tree) self.paned = VPaned() self.paned.pack1(scroller) self.pack_start(self.label, False, False) self.pack_end(self.paned) bus.watch_name_owner(PS_NAME, self.on_name_owner_change) self.ps_watcher = Label('-') self.paned.pack2(self.ps_watcher) self.show_all() def log(self, format, *args): self.errors.append((format % args, )) def on_name_owner_change(self, owner): try: if owner: self.label.set_text( 'Presence Service running: unique name %s' % owner) if self.ps_watcher is not None: self.paned.remove(self.ps_watcher) self.ps_watcher = PresenceServiceWatcher( self.bus, owner, self.log) self.paned.pack2(self.ps_watcher) self.show_all() else: self.label.set_text('Presence Service not running') if self.ps_watcher is not None: self.paned.remove(self.ps_watcher) self.ps_watcher = Label('-') self.paned.pack2(self.ps_watcher) except Exception, e: self.log('ERROR: %s', e)
def __populate_possible_plugins(self): """Populates the GUI with the possible front and backend plugins.""" available_plugin_liststore = ListStore(str) for plugin_name in get_available_frontend_plugins(): available_plugin_liststore.append([plugin_name]) self.plugin_add_entry_combo.set_model(available_plugin_liststore) self.plugin_add_entry_combo.set_text_column(0) available_backend_plugin_liststore = ListStore(str) for backend_plugin_name in get_available_backend_plugins(): available_backend_plugin_liststore.append([backend_plugin_name]) self.backend_plugin_entry_combo.set_model(available_backend_plugin_liststore) self.backend_plugin_entry_combo.set_text_column(0)
class MemberFeeCollectionEditor(object): def __init__(self, trans, transid, plugin, gui_parent, change_register_function): self.fee_trans = trans self.transid = transid self.memberfee_plugin = plugin self.change_register_function = change_register_function load_glade_file_get_widgets_and_connect_signals( get_memberfee_glade_file(), 'window1', self, self) self.vbox1.reparent(gui_parent) self.window1.hide() self.fee_spread_liststore = ListStore(str, str) self.fee_spread_list.set_model(self.fee_spread_liststore) column_one = TreeViewColumn('Date') column_two = TreeViewColumn('Amount') self.fee_spread_list.append_column(column_one) self.fee_spread_list.append_column(column_two) for i, column in enumerate((column_one, column_two)): cell = CellRendererText() column.pack_start(cell, False) column.add_attribute(cell, 'text', i) self.populate_fee_spread_liststore() def populate_fee_spread_liststore(self): self.fee_spread_liststore.clear() for month in gen_n_months_starting_from(first_of(date.today()), 4): self.fee_spread_liststore.append((month, '10')) def detach(self): self.vbox1.reparent(self.window1) def day_changed(self, *args): print('day_changed') def amount_collected_changed(self, *args): print('amount_collected_changed') def member_changed(self, *args): print('member_changed')
class HttpHandler(object): """Has callbacks that are invoked by the event system. It only accepts packets if: * They are HTTP requests * send or received by the IP given. If none is given, every IP is accepted. Subclass and overwrite handle for great good.""" def __init__(self, ip=None): self.ip = ip self.gtk_list_store = ListStore(str, str) def accept(self, pkg): logger.debug("Accepting? %s", pkg.summary()) if not pkg.haslayer(TCP): return False tcp_pkg = pkg[TCP] if not (tcp_pkg.dport == 80 or tcp_pkg.sport == 80): return False if isinstance(tcp_pkg.payload, NoPayload): return False if not GET.match(str(tcp_pkg.payload)): return False if self.ip is not None: return pkg[IP].src == self.ip or pkg[IP].dst == self.ip else: return True def handle(self, pkg): self.print(pkg) def print(self, pkg): logger.debug("got a package %s to print", pkg.summary()) time = datetime.fromtimestamp(pkg.time) logger.debug("initially at time={}".format(time)) tcp_pkg = pkg[TCP] server_name = reverse_dns(pkg[IP].dst) match = GET.match(str(tcp_pkg.payload)) try: entry = "http://{}{}".format(str(server_name.result(5.0)), match.group(1)) except TimeoutError: entry = "http://{}{}".format(str(pkg[IP].dst), match.group(1)) logger.info(entry) self.gtk_list_store.append([str(time), entry])
class HttpHandler(object): """Has callbacks that are invoked by the event system. It only accepts packets if: * They are HTTP requests * send or received by the IP given. If none is given, every IP is accepted. Subclass and overwrite handle for great good.""" def __init__(self, ip = None): self.ip = ip self.gtk_list_store = ListStore(str, str) def accept(self, pkg): logger.debug("Accepting? %s", pkg.summary()) if not pkg.haslayer(TCP): return False tcp_pkg = pkg[TCP] if not (tcp_pkg.dport == 80 or tcp_pkg.sport == 80): return False if isinstance(tcp_pkg.payload, NoPayload): return False if not GET.match(str(tcp_pkg.payload)): return False if self.ip is not None: return pkg[IP].src == self.ip or pkg[IP].dst == self.ip else: return True def handle(self, pkg): self.print(pkg) def print(self, pkg): logger.debug("got a package %s to print", pkg.summary()) time = datetime.fromtimestamp(pkg.time) logger.debug("initially at time={}".format(time)) tcp_pkg = pkg[TCP] server_name = reverse_dns(pkg[IP].dst) match = GET.match(str(tcp_pkg.payload)) try: entry = "http://{}{}".format(str(server_name.result(5.0)), match.group(1)) except TimeoutError: entry = "http://{}{}".format(str(pkg[IP].dst), match.group(1)) logger.info(entry) self.gtk_list_store.append([str(time), entry])
def create_prop_store(self, extra_props=[]): assert (self.component is not None) from gtk import ListStore store = ListStore(object, str, object) # use private properties so we connect to the actual object stores and not the inherited ones for atom in self.component._layer_atoms: store.append([atom, "pn", lambda o: o.name]) for atom in self.component._interlayer_atoms: store.append([atom, "pn", lambda o: o.name]) for prop in extra_props: store.append(prop) return store
def create_prop_store(self, extra_props=[]): assert(self.component is not None) from gtk import ListStore store = ListStore(object, str, object) # use private properties so we connect to the actual object stores and not the inherited ones for atom in self.component._layer_atoms: store.append([atom, "pn", lambda o: o.name]) for atom in self.component._interlayer_atoms: store.append([atom, "pn", lambda o: o.name]) for prop in extra_props: store.append(prop) return store
class confirm_dialog(Dialog): def __init__(self, parent, queues, icon): Dialog.__init__(self, _("Confirm"), parent, DIALOG_MODAL | DIALOG_DESTROY_WITH_PARENT, (STOCK_OK, RESPONSE_ACCEPT, STOCK_CANCEL, RESPONSE_REJECT)) self.set_icon(pixbuf_new_from_file(icon)) self._setup_trees(queues) self._setup_layout() def _setup_trees(self, queues): self._setup_install_tree(queues["add"]) self._setup_remove_tree (queues["remove"]) def _setup_install_tree(self, queue): self.install_tree = TreeView() self.install_model = ListStore(str, str, str) self.install_tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id=0) self.install_tree.insert_column_with_attributes(-1, _("Package"), CellRendererText(), text=1) self.install_tree.insert_column_with_attributes(-1, _("Version"), CellRendererText(), text=2) for pac in queue: if pac.isold: image = "yellow" elif pac.installed: image = "green" else: image = "red" self.install_model.append([image, pac.name, pac.version]) continue self.install_tree.set_model(self.install_model) return def _setup_remove_tree(self, queue): self.remove_tree = TreeView() self.remove_model = ListStore(str, str, str) self.remove_tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id=0) self.remove_tree.insert_column_with_attributes(-1, _("Package"), CellRendererText(), text=1) self.remove_tree.insert_column_with_attributes(-1, _("Version"), CellRendererText(), text=2) for pac in queue: if pac.isold: image = "yellow" elif pac.installed: image = "green" else: image = "red" self.remove_model.append([image, pac.name, pac.version]) continue self.remove_tree.set_model(self.remove_model) return def _setup_layout(self): hpaned = HPaned() label = Label(_("Are you sure you want to install/remove those packages?")) label.show() inst_frame = Frame(_("Packages to install")) rem_frame = Frame(_("Packages to remove")) inst_frame.add(self.install_tree) rem_frame.add(self.remove_tree) hpaned.add1(inst_frame) hpaned.add2(rem_frame) hpaned.show_all() self.vbox.pack_start(label, False, False, 0) self.vbox.pack_start(hpaned, True, True, 0) return def run(self): response = Dialog.run(self) self.destroy() if response == RESPONSE_ACCEPT: return True else: return False
class upgrade_dialog(Window): def __init__(self, to_upgrade, icon): Window.__init__(self, WINDOW_TOPLEVEL) self.set_property("skip-taskbar-hint", True) self.set_property("modal", True) self.set_property("destroy-with-parent", True) self.set_position(WIN_POS_CENTER) self.set_default_size (300, 300) self.set_icon(pixbuf_new_from_file(icon)) self._setup_tree(to_upgrade) self._setup_layout() def _setup_layout(self): self.vbox = VBox(False, 0) self.vbox.show() self.terminal = terminal() self.terminal.connect("child-exited", lambda _: self.close_button.show()) self.expander = expander_new_with_mnemonic(_("_Terminal")) self.expander.set_expanded(False) self.expander.add(self.terminal) self.expander.show_all() self.close_button = Button(stock=STOCK_CLOSE) self.close_button.connect("clicked", lambda _: self.destroy()) scr = ScrolledWindow() scr.set_policy ("automatic", "automatic") scr.add (self.tree) scr.show() vpaned = VPaned() vpaned.add1(scr) vpaned.add2(self.expander) vpaned.set_position (260) vpaned.show() self.vbox.pack_start(vpaned, True, True, 0) self.vbox.pack_start(self.close_button, False, False, 0) self.add(self.vbox) return def _setup_tree(self, pacs): self.model = ListStore(str, str, str) for pac in pacs: self.model.append(["yellow", pac.name, pac.version]) continue self.tree = TreeView() self.tree.show() self.tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id = 0) self.tree.insert_column_with_attributes(-1, "Package", CellRendererText(), text = 1) self.tree.insert_column_with_attributes(-1, "Version", CellRendererText(), text = 2) self.tree.set_model(self.model) return def run(self): self.show() self.terminal.do_upgrade()
class do_dialog(Window): def __init__(self, queues, icon): Window.__init__(self, WINDOW_TOPLEVEL) self.set_property("skip-taskbar-hint", True) self.set_property("destroy-with-parent", True) self.set_modal(True) self.connect("delete-event", self._stop_closing) self.set_position(WIN_POS_CENTER) self.set_icon(pixbuf_new_from_file(icon)) self._setup_trees(queues) self._setup_layout() self.queues = queues def _setup_trees(self, queues): self._setup_install_tree(queues["add"]) self._setup_remove_tree(queues["remove"]) def _setup_install_tree(self, add_queue): self.inst_model = ListStore(str, str, str) for pac in add_queue: if pac.isold: image = "yellow" elif pac.installed: image = "green" else: image = "red" self.inst_model.append([image, pac.name, pac.version]) continue self.inst_tree = TreeView() self.inst_tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id = 0) self.inst_tree.insert_column_with_attributes(-1, _("Package"), CellRendererText(), text = 1) self.inst_tree.insert_column_with_attributes(-1, _("Version"), CellRendererText(), text = 2) self.inst_tree.set_model(self.inst_model) def _setup_remove_tree(self, remove_queue): self.rem_model = ListStore(str, str, str) for pac in remove_queue: if pac.isold: image = "yellow" elif pac.installed: image = "green" else: image = "red" self.rem_model.append([image, pac.name, pac.inst_ver]) continue self.rem_tree = TreeView() self.rem_tree.insert_column_with_attributes(-1, "", CellRendererPixbuf(), stock_id = 0) self.rem_tree.insert_column_with_attributes(-1, _("Package"), CellRendererText(), text = 1) self.rem_tree.insert_column_with_attributes(-1, _("Installed Version"), CellRendererText(), text = 2) self.rem_tree.set_model(self.rem_model) def _set_size (self, widget, event, data=None): if self.expander.get_expanded(): self.size = self.get_size() self.expander.add(self.terminal) self.terminal.show() else: self.expander.remove(self.terminal) self.resize(self.size[0], self.size[1]) self.show_all() def _setup_layout(self): self.hpaned = HPaned() self.hpaned.add1(self.inst_tree) self.hpaned.add2(self.rem_tree) self.hpaned.show_all() self.close_button = Button(stock=STOCK_CLOSE) self.close_button.connect("clicked", lambda _: self.destroy()) self.terminal = terminal() self.terminal.connect("child-exited", lambda _: self.close_button.show()) self.expander = Expander(_("Terminal")) self.expander.connect("notify::expanded", self._set_size) self.expander.show() self.vbox = VBox(False, 0) self.vbox.show() self.vbox.pack_start(self.hpaned, False, False, 0) self.vbox.pack_start(self.expander, False, False, 0) self.vbox.pack_start(self.close_button, False, False, 0) self.add(self.vbox) def run(self): self.show() self.terminal.do(self.queues) return def _stop_closing(self, widget, event): self.stop_emission("delete-event") return True
def __on_plugin_directories_button_click(self, button): """Present a dialog to the user for selecting extra plugin directories and process the request.""" dia = Dialog('Plugin Directories', None, DIALOG_MODAL, (STOCK_OK, RESPONSE_OK, STOCK_CANCEL, RESPONSE_CANCEL ) ) dia.resize(500, 300) dia.vbox.set_spacing(8) # Setup the tree view of plugin directories. model = ListStore(str) # each row contains a single string tv = TreeView(model) cell = CellRendererText() column = TreeViewColumn('Directory', cell, text = 0) tv.append_column(column) dia.vbox.pack_start(tv) # Populate the tree view. plugin_directories = \ get_plugins_directories_from_config(self.config, self.config_path) for plugin_directory in plugin_directories: row = (plugin_directory,) model.append(row) modify_box = HBox(spacing = 8) # Setup the remove directory button. remove_button = Button('Remove') remove_button.set_sensitive(False) # no directory selected initially remove_button.connect('clicked', self.__on_remove, tv) modify_box.pack_end(remove_button, expand = False) tv.connect('cursor-changed', self.__on_select, remove_button) # Setup the add directory button. add_button = Button('Add') add_button.connect('clicked', self.__on_add, tv) modify_box.pack_end(add_button, expand = False) dia.vbox.pack_start(modify_box, expand = False) # Setup the "already included directories" label. included_label = Label('Plugins in the PYTHONPATH are already ' + 'available to BoKeep.') # Use a horizontal box to left-justify the label. For some reason, # the label's set_justification property doesn't work for me. label_box = HBox() label_box.pack_start(included_label, expand = False) dia.vbox.pack_start(label_box, expand = False) dia.show_all() dia_result = dia.run() if dia_result == RESPONSE_OK: # Remove the old plugin directories from the program's path. plugin_directories = \ get_plugins_directories_from_config(self.config, self.config_path) for plugin_directory in plugin_directories: path.remove(plugin_directory) # Get the new plugin directories from the dialog. plugin_directories = [] for row in model: plugin_directory = row[0] plugin_directories.append(plugin_directory) # Update the BoKeep PYTHONPATH so that new plugins can be loaded and # populate the list of possible new plugins. for plugin_directory in plugin_directories: path.append(plugin_directory) self.__populate_possible_plugins() # Save the new plugin directories in the configuration file. set_plugin_directories_in_config(self.config, self.config_path, plugin_directories) dia.destroy()
class MainWindow(object): """Represents the main window for the default BoKeep shell. This class has a lot of imperitive/procedural code with plenty of side effects, and the cost of them has started to show in subtle bugs. It would be a lost worse if the state machine BoKeepGuiState wasn't used. Could be a lot better if an even higher level state machine or something a lot more functional and a lot less imperitive were used. Oh well, in the meantime, here's a depth call graph with a little pseudo- code of the most intertangled functions from this module and major calls to other modules that alter the state of the interface. This is done starting from most significant entry points into this class's code. Please keep it up to date and also try to reflect the interdependencies in the in per function docstrings. This is pretty much the only way one can get a big picture overview of how much of the code here is interrelated __init__ (called by shell_startup prior to gtk.main() ) build_gui set_sensitivities_and_status set_backend_error_indicator set_transid_label startup_event_handler (first thing called by gtk.main() ) after_background_load if no book selected and at least one available guistate.do_action(BOOK_CHANGE, first book) else if a book selected matches one that exists guistate.do_action(RESET) else if no book selected that matches, or none even available guistate.do_action(BOOK_CHANGE, None) books_combobox_model.append books_combobox.set_active if a current book in the combo can not be determined set_book_from_combo guistate.do_action(BOOK_CHANGE, current_book_selected) refresh_trans_types_and_set_sensitivities_and_status refresh_trans_types trans_type_model.clear trans_type_model.append if a transaction type was found matching current set_transcombo_index reset_trans_view self.hide_transaction self.current_editor set with new editor instance else no transaction type found hide_transaction END refresh_trans_types as called by.. refresh_trans_types_and_set_sensitivities_and_status set_sensitivities_and_status set_backend_error_indicator set_transid_label END refresh_trans_types_and_set_sensitivities_and_status as called by after_background_load END after_background_load as called by startup_event_handler END startup_event_handler on_books_combobox_changed (called by gtk gui thread when book combo changed) set_book_from_combo guistate.do_action(BOOK_CHANGE, newly selected book) refresh_trans_types_and_set_sensitivities_and_status refresh_trans_types trans_type_model.clear trans_type_model.append if a transaction type was found matching current set_transcombo_index reset_trans_view self.hide_transaction self.current_editor set with new editor instance else no transaction type found hide_transaction END refresh_trans_types as called by.. refresh_trans_types_and_set_sensitivities_and_status set_sensitivities_and_status set_backend_error_indicator set_transid_label END refresh_trans_types_and_set_sensitivities_and_status as called by on_books_combobox_changed END on_books_combobox_changed new_button_clicked (called by gtk gui thread when new button clicked) guistate.do_action(NEW) set_trans_type_combo_to_current_and_reset_view set_transcombo_index reset_trans_view self.hide_transaction self.current_editor set with new editor instance set_sensitivities_and_status set_backend_error_indicator set_transid_label delete_button_clicked (called by gtk gui thread when delete button clicked) guistate.do_action(DELETE) if there is no transaction left set_transcombo_index hide_transaction else there is a transaction left set_trans_type_combo_to_current_and_reset_view set_transcombo_index reset_trans_view self.hide_transaction self.current_editor set with new editor instance set_sensitivities_and_status set_backend_error_indicator set_transid_label trans_type_changed (called by gtk gui thread when trans type combo changed) guistate.do_action(TYPE_CHANGE, active transaction type selection) self.reset_trans_view self.hide_transaction self.current_editor set with new editor instance set_sensitivities_and_status set_backend_error_indicator set_transid_label on_forward_button_clicked() and on_back_button_clicked() (called by gtk gui thread when back and forward buttons are clicked) guistate.do_action(FORWARD) set_trans_type_combo_to_current_and_reset_view() set_transcombo_index( based on transaction being moved too) reset_trans_view() self.hide_transaction() self.current_editor set with new editor instance set_sensitivities_and_status() all buttons, combos, and plugin menu are set_sensitive based on guistate, thanks to gui_built having just become True set_backend_error_indicator() gui_built is definitely true so this updates backend error label and related widgets set_transid_label() gui_built is definitely True, this is set based on current transaction on_configuration1_activate called by gtk gui thread when config item in menu is activated closedown_for_config() gui_built = False guistate.do_action(CLOSE) books_combobox.set_active(COMBO_SELECTION_NONE) on_books_combobox_changed is called due to above, but the gui_built = False line prevents it from doing anything books_combobox_model.clear() gui_built = False also stops the event handler for combo change here set_transcombo_index(COMBO_SELECTION_NONE) self.programmatic_transcombo_index = True self.trans_type_model.clear() event handler runs, but goes nowhere due to self.programmatic_transcombo_index flag self.programmatic_transcombo_index = False bookset.close() bookset = run config dialog, get new bookset from it after_background_load() self.guistate loaded from database (or established if new) if no book selected and at least one available guistate.do_action(BOOK_CHANGE, first book) else if a book selected matches one that exists guistate.do_action(RESET) else if no book selected that matches, or none even available guistate.do_action(BOOK_CHANGE, None) books_combobox_model filled in with possible books books_combobox set to the current book, or one selected if needed if a current book in the combo can not be determined books_combobox.set_active(first book) set_book_from_combo() guistate.do_action(BOOK_CHANGE, current_book_selected) else books_combobox.set_active( selected book as per self.guistate) self.gui_built = True refresh_trans_types_and_set_sensitivities_and_status() refresh_trans_types() exit if no book selected trans_type_model is cleared and reconstucted from all active plugins, attempt is made to identify transaction type matching current transaction (if any) trans_type_combo is setup with new model if a transaction type was found matching current set_transcombo_index() reset_trans_view() self.hide_transaction() self.current_editor set with new editor instance else no transaction type found hide_transaction() END refresh_trans_types as called by.. refresh_trans_types_and_set_sensitivities_and_status set_sensitivities_and_status() all buttons, combos, and plugin menu are set_sensitive based on guistate, thanks to gui_built having just become True set_backend_error_indicator() with gui_built now being true, this updates backend error label and related widgets set_transid_label() with gui_built now being True, this is set based on current transaction END set_sensitivities_and_status() as called by... refresh_trans_types_and_set_sensitivities_and_status END refresh_trans_types_and_set_sensitivities_and_status as called by after_background_load END after_background_load as called by on_configuration1_activate END on_configuration1_activate on_configure_plugin1_activate Run the configuration dialog for the active plugin reset_trans_view() self.hide_transaction() self.current_editor set with new editor instance END on_configure_plugin1_activate """ # Functions for window initialization def on_quit_activate(self, args): self.application_shutdown() def __init__(self, config_path, config, bookset, startup_callback): self.gui_built = False self.current_editor = None self.set_config_path_and_config(config_path, config) self.set_bookset(bookset) self.build_gui() self.programmatic_transcombo_index = False self.__startup_callback = startup_callback # program in an event that will only run once on startup # the startup_event_handler function will use # self.startup_event_handler to disconnect itself self.startup_event_handler = self.mainwindow.connect( "window-state-event", self.startup_event_handler ) # should we do an emit to ensure it happens, or be satisfyed # that it always happens in tests? def set_config_path_and_config(self, config_path, config): self.__config_path = config_path self.__config = config def set_bookset(self, bookset): """Sets the set of BoKeep books.""" self.bookset = bookset def flush_book_backend(self, book): """Save the BoKeep book.""" book.get_backend_plugin().flush_backend() transaction.get().commit() def close_book_backend(self, book): """Close the backend used for saving the BoKeep book.""" book.get_backend_plugin().close() transaction.get().commit() def application_shutdown(self): if hasattr(self, 'guistate'): self.guistate.do_action(CLOSE) if hasattr(self, 'bookset') and self.bookset != None: for bookname, book in self.bookset.iterbooks(): book.get_backend_plugin().flush_backend() transaction.get().commit() self.bookset.close() # or, should I be only doing # self.bookset.close_primary_connection() instead..? main_quit() def startup_event_handler(self, *args): """An event handler programmed to run as soon as gtk's main loop takes over After disabling itself from running again, this calls the __start_callback attribute established in __init__ and if that function returns true calls after_background_load for further processing. If it fails, application_shutdown() is called Part of the motivation here is to defer stuff we didn't want to do in __init__ such as calling gtk.main() """ # this should only be programmed to run once assert( hasattr(self, 'startup_event_handler') ) self.mainwindow.disconnect(self.startup_event_handler) # remove the attribute startup_event_handler to intentionally # cause the event handler to fail delattr(self, 'startup_event_handler') assert(not self.gui_built) # the motivation for making a call back to the high level shell # code is that we're able to get the gui thread rolling # and have any dialogs that need to be launched by the shell # startup code be called at this point, parented to the main # window we've managed to build up and get ready in __init__ if self.__startup_callback(self.__config_path, self.__config, self.set_config_path_and_config, self.set_bookset, self.mainwindow ): self.after_background_load() assert(self.gui_built) else: self.application_shutdown() def build_gui(self): """Called by __init__ to take care of the gtk work or putting the gui together. This consists of loading mainwindow from the glade file, setting the bokeep logo, and getting the cell renderers and empty models set up for the book and transaction type combo boxes. Ends with a a call to set_sensitivities_and_status to nicely grey things out until we have something for the shell to actually display. This is meant to be done without self.book or self.guistate being available yet. It shouldn't be called from anywhere other than __init__, and only once. """ glade_file = get_main_window_glade_file() load_glade_file_get_widgets_and_connect_signals( glade_file, "mainwindow", self, self ) self.mainwindow.set_icon_from_file(get_bo_keep_logo()) self.books_combobox_model = ListStore(str, str, object) self.books_combobox.set_model(self.books_combobox_model) cell = CellRendererText() self.books_combobox.pack_start(cell, True) self.books_combobox.add_attribute(cell, 'text', 0) self.trans_type_model = ListStore(str, int, object) self.set_sensitivities_and_status() def after_background_load(self): """Called to instruct the shell to load from persistent storage the last visited book and transaction, sets up the list of books, determins the current transaction (if any), sets up the transaction type combo for the current bookm and then displays the current transaction. This is only to be called after the right database is loaded from cold, and so far that kind of cold load happens in two places only: * at the end of startup_event_handler, which is run once by gtk's main thread on shell startup * at the end of on_configuration1_activate, where the database connection is entirely closed down and possibly even a tottally new bookset is loaded This procedure sets self.guistate right at the start and may call self.guistate.do_action with BOOK_CHANGE, and RESET as it determines what the current book is. (including possibly None) Then it builds the list of available books in self.books_combobox_model and sets that to whatever the current book is. At that point, the gui can be said to be fully built, as all the elements that won't change even when viewing multiple books are in place, so self.gui_built = True occures The only things left to change are the transaction type combo box which can differ per book, and of course the intereface for whichever transaction is loaded. As a last step, the work described above is done by refresh_trans_types_and_set_sensitivities_and_status (Be sure to read its docstring) """ self.guistate = ( self.bookset.get_dbhandle().get_sub_database_do_cls_init( GUI_STATE_SUB_DB, BoKeepGuiState) ) # we should do some checking that self.guistate is still reasonable # it is possible at this point that the current book or # current transaction could be gone due to someone playing with # the config or database in some way bookname_and_book_list = list(self.bookset.iterbooks()) # if there actually is a book when we think there isn't if self.guistate.get_book() == None and len(bookname_and_book_list) > 0: self.guistate.do_action(BOOK_CHANGE, bookname_and_book_list[0][1] ) # if the current book doesn't actually exist anymore # this includes the case self.guistate.get_book() == None else: for book_name, book in bookname_and_book_list: # there is a matching book, but it is possible that there # is a transaction (when we think none), or the supposedly # current one is gone if book == self.guistate.get_book(): self.guistate.do_action(RESET) break # else clause below is skipped else: # else the current book is no longer in the official list self.guistate.do_action(BOOK_CHANGE, None) # save the (possibly updated) guistate transaction.get().commit() cur_book_index = None for i, (book_name, book) in enumerate(bookname_and_book_list): self.books_combobox_model.append((book_name, book_name, book)) if self.guistate.get_book() != None and \ self.guistate.get_book() == book: cur_book_index = i # this would be the f****d up consequence of a book being deleted # ... until we can deal with it, say it can't happen assert( not( self.guistate.get_book() != None and cur_book_index == None) ) # should do something similar for the current transaction if len(self.books_combobox_model) > 0: if cur_book_index == None: self.books_combobox.set_active(0) self.set_book_from_combo() else: self.books_combobox.set_active(cur_book_index) self.gui_built = True self.refresh_trans_types_and_set_sensitivities_and_status() def closedown_for_config(self): """Called soley by on_configuration1_activate to shut down everything in the gui prior to running the configuration dialog Starts right away by changing self.gui_built = False does self.guistate.do_action(CLOSE) and clears out the books combo list and transaction type combo list """ self.gui_built = False self.guistate.do_action(CLOSE) transaction.get().commit() # redundant self.books_combobox.set_active(COMBO_SELECTION_NONE) self.books_combobox_model.clear() self.set_transcombo_index(COMBO_SELECTION_NONE) # this clear actually triggers the event handler even after we # set to none, I guess losing your model counts as a change! self.programmatic_transcombo_index = True self.trans_type_model.clear() self.programmatic_transcombo_index = False # Functions for window initialisation and use thereafter def set_book_from_combo(self): """Updates self.guistate based on the book selected by the books combobox. Used by after_background_load and on_books_combobox_changed """ self.guistate.do_action( BOOK_CHANGE, self.books_combobox_model[ self.books_combobox.get_active()][2] ) def refresh_trans_types_and_set_sensitivities_and_status(self): """Combines a call to refresh_trans_types and set_sensitivities_and_status. in that order See the respective docstrings used by * after_background_load as a last step after figuring out the current book, constructing the book list, and setting the current book * on_books_combobox_changed as a last step after a new book is selected """ self.refresh_trans_types() self.set_sensitivities_and_status() def refresh_trans_types(self): """Updates the transaction type combobox to reflect the currently selected book and transaction -- then calls self.reset_trans_view() to display the current transaction or hide_transaction() to at least clear off the old one. Does nothing if there isn't a currently selected book Alters the state of self.trans_type_model Calls reset_trans_view if a transaction type is selected. One of the things it does is call hide_transaction() Directly call hide_transaction() if there is no transaction type selected, in either case we want to be sure there isn't an old gui hanging around. """ book = self.guistate.get_book() if book == None: return self.programmatic_transcombo_index = True self.trans_type_model.clear() self.programmatic_transcombo_index = False cur_trans = None if self.guistate.get_transaction_id() != None: cur_trans = book.get_transaction(self.guistate.get_transaction_id()) frontend_plugins = book.get_frontend_plugins() current_trans_type_index = COMBO_SELECTION_NONE for i, (code, trans_cls, module) in \ enumerate(book.get_iter_of_code_class_module_tripplets()): self.trans_type_model.append( ( module.get_transaction_type_pulldown_string_from_code(code), code, module ) ) if cur_trans != None and isinstance(cur_trans, trans_cls): current_trans_type_index = i self.trans_type_combo.set_model(self.trans_type_model) assert( self.trans_type_combo.get_active() == COMBO_SELECTION_NONE ) if current_trans_type_index != COMBO_SELECTION_NONE: self.set_transcombo_index(current_trans_type_index) self.reset_trans_view() else: self.hide_transaction() def set_transcombo_index(self, indx): """Changed the currently selected transaction type combobox to the specified type by index in that combobox The event handler, trans_type_changed is prevented from running """ self.programmatic_transcombo_index = True self.trans_type_combo.set_active(indx) self.programmatic_transcombo_index = False def reset_trans_view(self): """Clears away an old transaction editor (if present) with hide_transaction and starts up a new one for the current transaction It's assumed here that self.guistate does specify a book, transaction, and current transaction type to provide an editor. So don't call this when that isn't true. Called, in this assumed context by, refresh_trans_types, set_trans_type_combo_to_current_and_reset_view, trans_type_changed, on_configure_plugin1_activate """ book = self.guistate.get_book() currindex = self.trans_type_combo.get_active_iter() currcode = self.trans_type_combo.get_model().get_value(currindex,1) currmodule = self.trans_type_combo.get_model().get_value(currindex,2) editor_generator = currmodule.\ get_transaction_edit_interface_hook_from_code(currcode) self.hide_transaction() trans_id = self.guistate.get_transaction_id() self.current_editor = editor_generator( book.get_transaction(trans_id), trans_id, currmodule, self.transaction_viewport, self.guistate.record_trans_dirty_in_backend, book) def hide_transaction(self): """If a transaction is currently displayed (self.current_editor), detach it. """ if self.current_editor != None: self.current_editor.detach() def set_sensitivities_and_status(self): """Update the enabled/disabled attributes of the GUI and update the status. Specifically, this uses checks for action permissability from self.guistate to set the sensitivity of the toolbar buttons, the books and transaction type comboxes, and the plugin menu. These checks are done conditional on self.gui_built being set otherwise they're always set to disabled. After those we call set_backend_error_indicator and self.set_transid_label, see thier docstrings """ for (sensitive_widget, action_code) in \ ( (self.back_button, BACKWARD), (self.forward_button, FORWARD), (self.new_button, NEW), (self.delete_button, DELETE), (self.trans_type_combo, TYPE_CHANGE), (self.books_combobox, BOOK_CHANGE), (self.plugin1, DELETE), (self.plugin1_menu, DELETE), ): if self.gui_built: sensitive_widget.set_sensitive( self.guistate.action_allowed(action_code) ) else: sensitive_widget.set_sensitive(False) self.set_backend_error_indicator() self.set_transid_label() def set_transid_label(self): """Update the field indicating the current transaction index out of the total number of transactions. Only does this if self.gui_built is set ans there's a book selected, otherwise just sets the label blank Only called from set_transid_label """ if self.gui_built and not(self.guistate.get_book() == None): last_trans_id = self.guistate.get_book().get_latest_transaction_id() if last_trans_id != None: self.transid_label.set_text( "%s / %s" % (self.guistate.get_transaction_id(), last_trans_id )) return # avoid set_text("") below self.transid_label.set_text("") def set_backend_error_indicator(self): """Updates the label and related widgets for displaying backend plugin errors with info from the active book's backend plugin Doesn't do anything if self.gui_built is not set, also does nothing if there isn't a book and transaction selected. Called by set_sensitivities_and_status, on_backend_flush_request, and on_backend_close_request """ # don't bother if the gui isn't built yet if not self.gui_built: return # set the backend error indicator led book = self.guistate.get_book() trans_id = self.guistate.get_transaction_id() if book != None and trans_id != None: backend = book.get_backend_plugin() if backend.transaction_is_clean(trans_id): self.backend_error_light.hide() self.backend_error_msg_label.hide() self.error_details_button.hide() else: self.backend_error_light.show() self.backend_error_msg_label.set_text( backend.reason_transaction_is_dirty(trans_id) ) self.backend_error_msg_label.show() self.error_details_button.show() def on_error_details_button_clicked(self, *args): """Event handler for the user requesting to see the full backend plugin error display """ md = MessageDialog(parent = self.mainwindow, type = MESSAGE_ERROR, buttons = BUTTONS_OK, message_format = self.backend_error_msg_label.get_text()) md.run() md.destroy() # Functions for use to event handlers, not used during initialization def set_trans_type_combo_to_current_and_reset_view(self): """Call after the current transaction has changed from one to another within the same book to update the transaction type combo box and to update the editor interface. Another key note is that this doesn't change what's listed in the list of transaction types like refresh_trans_types does, just ensures the right one is used and applies the editor interface for it. This is called only by the only places it makes sense right now: new_button_clicked, delete_button_clicked (only if a transaction remains), on_forward_button_clicked, on_back_button_clicked . Notice how all of the above involve situations where prior to them a book was already being viewed and the transaction type list was already in place, and the only thing that happened was a switch to different transaction and a need to just display it. This calls reset_trans_view as a last step to take care of removing the old transaction from the display and displaying the new one """ book = self.guistate.get_book() trans_id = self.guistate.get_transaction_id() assert( trans_id != None ) assert( isinstance(trans_id, int) ) (i, (a, b, c)) = \ book.get_index_and_code_class_module_tripplet_for_transaction( trans_id ) # What the hell, why is i None sometimes, # (trans id not found in any module) -- # this is a bug that has come up #assert( i != None) # should really have kept that assertion in.. but due to bugs I # have cheated.. if i == None: i = COMBO_SELECTION_NONE # if this is actually still happening, perhaps we # want a hide_transaction() here so we at least clear # off the old gui (if still there...) self.set_transcombo_index(i) if i != COMBO_SELECTION_NONE: self.reset_trans_view() # Event handlers def on_books_combobox_changed(self, combobox): """Change the current BoKeep book.""" #don't mess with stuff until we've finished constructing the gui if not self.gui_built: return self.set_book_from_combo() self.refresh_trans_types_and_set_sensitivities_and_status() def new_button_clicked(self, *args): """Create a new BoKeep transaction.""" self.guistate.do_action(NEW) self.set_trans_type_combo_to_current_and_reset_view() self.set_sensitivities_and_status() # to maintain compatibility with listing of event handler in # the glade file. # we don't change the glade file because those are impossible to # maintain and merge in when developing in a branch, so once # this code is merged to default the glade file can be updated # and this comment and backwards compatibility assignment can be # removed on_new_button_clicked = new_button_clicked def delete_button_clicked(self, *args): """Delete a BoKeep transaction.""" if gtk_yes_no_dialog( """Are you sure you want to delete transaction number %s?""" % self.guistate.get_transaction_id() ): self.guistate.do_action(DELETE) book = self.guistate.get_book() if self.guistate.get_transaction_id() == None: self.set_transcombo_index(COMBO_SELECTION_NONE) self.hide_transaction() else: self.set_trans_type_combo_to_current_and_reset_view() self.set_sensitivities_and_status() # see comment on on_new_button_clicked on_delete_button_clicked = delete_button_clicked def trans_type_changed(self, *args): """Event handler for when the transaction type on a new transaction changes """ #only refresh the trans view if it was the user changing the #transaction type if not self.programmatic_transcombo_index: assert( self.gui_built ) # and never during gui building.. # odd, when this was called without the second argument, the # program rightly crashed if a NEW transaction was created, # followed by TYPE_CHANGE here. But, on re-launch, the memory of # the old type transaction seemed to remain in the state # stuff but was no longer available in the book. The # zodb transaction should of prevented this, what gives? self.guistate.do_action(TYPE_CHANGE, self.trans_type_combo.get_active()) self.reset_trans_view() self.set_sensitivities_and_status() def on_forward_button_clicked(self, *args): """Go forward to next transaction.""" self.guistate.do_action(FORWARD) self.set_trans_type_combo_to_current_and_reset_view() self.set_sensitivities_and_status() def on_back_button_clicked(self, *args): """Go back to previous transaction.""" self.guistate.do_action(BACKWARD) self.set_trans_type_combo_to_current_and_reset_view() self.set_sensitivities_and_status() def on_remove(self, window, event): self.application_shutdown() def on_configuration1_activate(self, *args): """Configure BoKeep.""" assert( self.gui_built ) self.closedown_for_config() self.bookset.close() assert( not self.gui_built ) # major flaw right now is that we don't want this to # re-open the same DB at the end, and we need to do something # the return value and seting bookset self.bookset = \ establish_bokeep_db(self.mainwindow, self.__config_path, self.__config, None) # if there's uncommited stuff, we need to ditch it because # the above function killed off and re-opened the db connecion # we had. But, if there aren't any changes anywhere, this shuldn't # be a problem, right? .. but on the other hand a new transaction # probably begins as new one dies transaction.get().abort() if self.bookset == None: sys.exit() self.after_background_load() assert( self.gui_built ) def on_configure_backend1_activate(self, *args): """Configure the backend plugin.""" book = self.guistate.get_book() if book != None: backend = book.get_backend_plugin() backend.close() backend.configure_backend(self.mainwindow) transaction.get().commit() def on_configure_plugin1_activate(self, *args): """Configure the current front end plugin.""" # the gui should never allow this event handler to be triggered # if there is no transaction and thus no associated plugin # to configure assert( self.guistate.get_book() != None ) assert( self.guistate.get_transaction_id() != None ) currindex = self.trans_type_combo.get_active_iter() if currindex == COMBO_SELECTION_NONE: return currmodule = self.trans_type_combo.get_model().get_value( currindex,2) currmodule.run_configuration_interface( self.mainwindow, self.guistate.get_book().get_backend_plugin( ).backend_account_dialog, self.guistate.get_book()) self.reset_trans_view() transaction.get().commit() def on_about_activate(self, *args): """Displays the Help > About dialog.""" bo_keep_logo_path = get_bo_keep_logo() ab = AboutDialog() ab.set_transient_for(self.mainwindow) ab.set_modal(True) ab.set_name("Bo-Keep") ab.set_version("1.2.1") ab.set_copyright("ParIT Worker Co-operative, Ltd. 2006-2012") ab.set_comments( """Bo-Keep helps you keep your books so you don't get lost. Developed with grant funding from: - Assiniboine Credit Union <http://assiniboine.mb.ca> - Legal Aid Manitoba <http://www.legalaid.mb.ca> """) ab.set_license( """Bo-Keep is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """) ab.set_authors(("Mark Jenkins <*****@*****.**>", "Jamie Campbell <*****@*****.**>", "Samuel Pauls <*****@*****.**>", "Andrew Orr <*****@*****.**>", "Sara Arenson <*****@*****.**>",)) ab.set_artists(("David Henry <*****@*****.**>",)) ab.set_program_name("Bo-Keep") ab.set_logo( pixbuf_new_from_file_at_size( bo_keep_logo_path, 300, 266) ) ab.run() ab.destroy() def on_backend_flush_request(self, *args): """Saves the BoKeep transactions.""" if self.guistate.get_book() == None: return self.flush_book_backend(self.guistate.get_book()) self.set_backend_error_indicator() def on_backend_close_request(self, *args): """Releases the backend that's used to save BoKeep transactions.""" book = self.guistate.get_book() if book == None: return self.flush_book_backend(book) self.close_book_backend(book) self.set_backend_error_indicator()
class PresenceServiceWatcher(VBox): def __init__(self, bus, unique_name, log): VBox.__init__(self) self.bus = bus self.unique_name = unique_name self.proxy = bus.get_object(unique_name, PS_PATH) self.iface = dbus.Interface(self.proxy, PS_IFACE) self.log = log self.activities = None self.iface.connect_to_signal('ActivityAppeared', self._on_activity_appeared) self.iface.connect_to_signal('ActivityDisappeared', self._on_activity_disappeared) self.iface.GetActivities(reply_handler=self._on_get_activities_success, error_handler=self._on_get_activities_failure) self.buddies = None self.iface.connect_to_signal('BuddyAppeared', self._on_buddy_appeared) self.iface.connect_to_signal('BuddyDisappeared', self._on_buddy_disappeared) self.iface.GetBuddies(reply_handler=self._on_get_buddies_success, error_handler=self._on_get_buddies_failure) # keep this in sync with the ACT_COL_ constants self.activities_list_store = ListStore( str, # object path int, # weight (bold if new) bool, # strikethrough (dead) str, # ID str, # color str, # type str, # name str, # conn str, # channels str, # buddies ) self.pack_start(Label('Activities:'), False, False) self.activities_list = TreeView(self.activities_list_store) c = self.activities_list.insert_column_with_attributes( 0, 'Object path', CellRendererText(), text=ACT_COL_PATH, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_PATH) c = self.activities_list.insert_column_with_attributes( 1, 'ID', CellRendererText(), text=ACT_COL_ID, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_ID) c = self.activities_list.insert_column_with_attributes( 2, 'Color', CellRendererText(), text=ACT_COL_COLOR, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_COLOR) c = self.activities_list.insert_column_with_attributes( 3, 'Type', CellRendererText(), text=ACT_COL_TYPE, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_TYPE) c = self.activities_list.insert_column_with_attributes( 4, 'Name', CellRendererText(), text=ACT_COL_NAME, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_NAME) c = self.activities_list.insert_column_with_attributes( 5, 'Connection', CellRendererText(), text=ACT_COL_CONN, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(ACT_COL_CONN) c = self.activities_list.insert_column_with_attributes( 6, 'Channels', CellRendererText(), text=ACT_COL_CHANNELS, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) c = self.activities_list.insert_column_with_attributes( 7, 'Buddies', CellRendererText(), text=ACT_COL_BUDDIES, weight=ACT_COL_WEIGHT, strikethrough=ACT_COL_STRIKE) c.set_resizable(True) scroller = ScrolledWindow() scroller.add(self.activities_list) self.pack_start(scroller) # keep this in sync with the BUDDY_COL_ constants self.buddies_list_store = ListStore(str, int, bool, str, bool, str, str, str, str, str, str) self.pack_start(Label('Buddies:'), False, False) self.buddies_list = TreeView(self.buddies_list_store) c = self.buddies_list.insert_column_with_attributes( 0, 'Object path', CellRendererText(), text=BUDDY_COL_PATH, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_PATH) c = self.buddies_list.insert_column_with_attributes( 1, 'Pubkey', CellRendererText(), text=BUDDY_COL_KEY_ID, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_KEY_ID) c = self.buddies_list.insert_column_with_attributes( 2, 'Nick', CellRendererText(), text=BUDDY_COL_NICK, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_NICK) c = self.buddies_list.insert_column_with_attributes( 3, 'Owner', CellRendererToggle(), active=BUDDY_COL_OWNER) c = self.buddies_list.insert_column_with_attributes( 4, 'Color', CellRendererText(), text=BUDDY_COL_COLOR, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_OWNER) c = self.buddies_list.insert_column_with_attributes( 5, 'IPv4', CellRendererText(), text=BUDDY_COL_IP4, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_IP4) c = self.buddies_list.insert_column_with_attributes( 6, 'CurAct', CellRendererText(), text=BUDDY_COL_CUR_ACT, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_CUR_ACT) c = self.buddies_list.insert_column_with_attributes( 7, 'Activities', CellRendererText(), text=BUDDY_COL_ACTIVITIES, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_ACTIVITIES) c = self.buddies_list.insert_column_with_attributes( 8, 'Handles', CellRendererText(), text=BUDDY_COL_HANDLES, weight=BUDDY_COL_WEIGHT, strikethrough=BUDDY_COL_STRIKE) c.set_resizable(True) c.set_sort_column_id(BUDDY_COL_HANDLES) scroller = ScrolledWindow() scroller.add(self.buddies_list) self.pack_start(scroller) self.iface.connect_to_signal('ActivityInvitation', self._on_activity_invitation) self.iface.connect_to_signal('PrivateInvitation', self._on_private_invitation) def _on_get_activities_success(self, paths): self.log('INFO: PS GetActivities() returned %r', paths) self.activities = {} for path in paths: self.activities[path] = ActivityWatcher(self, path) def _on_get_activities_failure(self, e): self.log('ERROR: PS GetActivities() failed with %s', e) def add_activity(self, act): path = act.object_path if path.startswith('/org/laptop/Sugar/Presence/Activities/'): path = '.../' + path[38:] return self.activities_list_store.append( (path, 700, False, act.id, act.color, act.type, act.name, act.conn, '?', '?')) def remove_activity(self, act): self.activities.pop(act.object_path, None) self.activities_list_store.remove(act.iter) def _on_activity_appeared(self, path): if self.activities is None: return self.log('INFO: PS emitted ActivityAppeared("%s")', path) self.activities[path] = ActivityWatcher(self, path) def _on_activity_disappeared(self, path): if self.activities is None: return self.log('INFO: PS emitted ActivityDisappeared("%s")', path) act = self.activities.get(path) if act is None: self.log( 'WARNING: Trying to remove activity "%s" which is ' 'already absent', path) else: # we don't remove the activity straight away, just cross it out act.disappear() def _on_activity_invitation(self, path): self.log('INFO: PS emitted ActivityInvitation("%s")', path) def _on_private_invitation(self, bus_name, conn, channel): self.log('INFO: PS emitted PrivateInvitation("%s", "%s", "%s")', bus_name, conn, channel) def _on_get_buddies_success(self, paths): self.log('INFO: PS GetBuddies() returned %r', paths) self.buddies = {} for path in paths: self.buddies[path] = BuddyWatcher(self, path) def _on_get_buddies_failure(self, e): self.log('ERROR: PS GetBuddies() failed with %s', e) def add_buddy(self, b): path = b.object_path if path.startswith('/org/laptop/Sugar/Presence/Buddies/'): path = '.../' + path[35:] return self.buddies_list_store.append( (path, 700, False, b.nick, b.owner, b.color, b.ipv4, b.cur_act, b.keyid, '?', '?')) def remove_buddy(self, b): self.buddies.pop(b.object_path, None) self.buddies_list_store.remove(b.iter) def _on_buddy_appeared(self, path): if self.buddies is None: return self.log('INFO: PS emitted BuddyAppeared("%s")', path) self.buddies[path] = BuddyWatcher(self, path) def _on_buddy_disappeared(self, path): if self.buddies is None: return self.log('INFO: PS emitted BuddyDisappeared("%s")', path) b = self.buddies.get(path) if b is None: self.log( 'ERROR: Trying to remove buddy "%s" which is already ' 'absent', path) else: # we don't remove the activity straight away, just cross it out b.disappear()
def update_data_flows(model, data_flow_dict, tree_dict_combos): """ Updates data flow dictionary and combo dictionary of the widget according handed model. :param model: model for which the data_flow_dict and tree_dict_combos should be updated :param data_flow_dict: dictionary that holds all internal and external data-flows and those respective row labels :param tree_dict_combos: dictionary that holds all internal and external data-flow-adaptation-combos :return: """ data_flow_dict['internal'] = {} data_flow_dict['external'] = {} tree_dict_combos['internal'] = {} tree_dict_combos['external'] = {} # free input ports and scopes are real to_keys and real states [free_to_port_internal, from_ports_internal] = find_free_keys(model) [free_to_port_external, from_ports_external] = find_free_keys(model.parent) def take_from_dict(from_dict, key): if key in from_dict: return from_dict[key] else: logger.warning("Key '%s' is not in %s" % (key, from_dict)) pass # from_state, to_key, to_state, to_key, external if isinstance(model, ContainerStateModel): for data_flow in model.state.data_flows.values(): # model.data_flow_list_store: # TREE STORE LABEL # check if from Self_state if data_flow.from_state == model.state.state_id: from_state = model.state from_state_label = 'self.' + model.state.name + '.' + data_flow.from_state else: if take_from_dict(model.state.states, data_flow.from_state): from_state = take_from_dict(model.state.states, data_flow.from_state) from_state_label = from_state.name + '.' + data_flow.from_state else: # print data_flow.from_state, data_flow.from_key, data_flow.to_state, data_flow.to_key logger.warning("DO break in ctrl/data_flow.py -1") break # check if to Self_state if data_flow.to_state == model.state.state_id: to_state = model.state to_state_label = 'self.' + model.state.name + '.' + data_flow.to_state else: if take_from_dict(model.state.states, data_flow.to_state): to_state = take_from_dict(model.state.states, data_flow.to_state) to_state_label = to_state.name + '.' + data_flow.to_state else: # print data_flow.from_state, data_flow.from_key, data_flow.to_state, data_flow.to_key logger.warning("DO break in ctrl/data_flow.py 0") break from_key_port = from_state.get_data_port_by_id(data_flow.from_key) from_key_label = '' if from_key_port is not None: from_key_label = PORT_TYPE_TAG.get(type(from_key_port), 'None') + '.' + \ from_key_port.data_type.__name__ + '.' + \ from_key_port.name to_key_port = to_state.get_data_port_by_id(data_flow.to_key) # to_key_label = '' if to_key_port is not None: to_key_label = PORT_TYPE_TAG.get(type(to_key_port), 'None') + '.' + \ (to_key_port.data_type.__name__ or 'None') + '.' + \ to_key_port.name data_flow_dict['internal'][data_flow.data_flow_id] = {'from_state': from_state_label, 'from_key': from_key_label, 'to_state': to_state_label, 'to_key': to_key_label} # ALL INTERNAL COMBOS from_states_store = ListStore(str) to_states_store = ListStore(str) if isinstance(model, ContainerStateModel): if model.state.state_id in free_to_port_internal or model.state.state_id == data_flow.to_state: to_states_store.append(['self.' + model.state.name + '.' + model.state.state_id]) if model.state.state_id in from_ports_internal or model.state.state_id == data_flow.from_state: from_states_store.append(['self.' + model.state.name + '.' + model.state.state_id]) for state_model in model.states.itervalues(): if state_model.state.state_id in free_to_port_internal or \ state_model.state.state_id == data_flow.to_state: to_states_store.append([state_model.state.name + '.' + state_model.state.state_id]) if state_model.state.state_id in from_ports_internal or \ state_model.state.state_id == data_flow.from_state: from_states_store.append([state_model.state.name + '.' + state_model.state.state_id]) from_keys_store = ListStore(str) if model.state.state_id == data_flow.from_state: # print "input_ports", model.state.input_data_ports # print type(model) if isinstance(model, ContainerStateModel): # print "scoped_variables", model.state.scoped_variables combined_ports = {} combined_ports.update(model.state.scoped_variables) combined_ports.update(model.state.input_data_ports) get_key_combos(combined_ports, from_keys_store, data_flow.from_key) else: get_key_combos(model.state.input_data_ports, from_keys_store, data_flow.from_key) else: # print "output_ports", model.states[data_flow.from_state].state.output_data_ports get_key_combos(model.state.states[data_flow.from_state].output_data_ports, from_keys_store, data_flow.from_key) to_keys_store = ListStore(str) if model.state.state_id == data_flow.to_state: # print "output_ports", model.state.output_data_ports # print type(model) if isinstance(model, ContainerStateModel): # print "scoped_variables", model.state.scoped_variables combined_ports = {} combined_ports.update(model.state.scoped_variables) combined_ports.update(model.state.output_data_ports) get_key_combos(combined_ports, to_keys_store, data_flow.to_key) else: get_key_combos(model.state.output_data_ports, to_keys_store, data_flow.to_key) else: # print "input_ports", model.states[data_flow.to_state].state.input_data_ports get_key_combos(model.state.states[data_flow.to_state].input_data_ports , to_keys_store, data_flow.to_key) tree_dict_combos['internal'][data_flow.data_flow_id] = {'from_state': from_states_store, 'from_key': from_keys_store, 'to_state': to_states_store, 'to_key': to_keys_store} # print "internal", data_flow_dict['internal'][data_flow.data_flow_id] if not model.state.is_root_state: for data_flow in model.parent.state.data_flows.values(): # model.parent.data_flow_list_store: # TREE STORE LABEL # check if from Self_state if model.state.state_id == data_flow.from_state: from_state = model.state from_state_label = 'self.' + model.state.name + '.' + data_flow.from_state else: if model.parent.state.state_id == data_flow.from_state: from_state = model.parent.state from_state_label = 'parent.' + model.parent.state.name + '.' + data_flow.from_state else: if take_from_dict(model.parent.state.states, data_flow.from_state): from_state = take_from_dict(model.parent.state.states, data_flow.from_state) from_state_label = from_state.name + '.' + data_flow.from_state else: # print "#", data_flow.from_state, data_flow.from_key, data_flow.to_state, data_flow.to_key logger.warning("DO break in ctrl/data_flow.py 1") break # check if to Self_state if model.state.state_id == data_flow.to_state: to_state = model.state to_state_label = 'self.' + model.state.name + '.' + data_flow.to_state else: if model.parent.state.state_id == data_flow.to_state: to_state = model.parent.state to_state_label = 'parent.' + model.parent.state.name + '.' + data_flow.to_state else: if take_from_dict(model.parent.state.states, data_flow.to_state): to_state = take_from_dict(model.parent.state.states, data_flow.to_state) to_state_label = to_state.name + '.' + data_flow.to_state else: # print "##", data_flow.from_state, data_flow.from_key, data_flow.to_state, data_flow.to_key logger.warning("DO break in ctrl/data_flow.py 2") break if model.state.state_id in [data_flow.from_state, data_flow.to_state]: from_key_port = from_state.get_data_port_by_id(data_flow.from_key) if from_key_port is None: continue from_key_label = PORT_TYPE_TAG.get(type(from_key_port), 'None') + '.' + \ from_key_port.data_type.__name__ + '.' + \ from_key_port.name to_key_port = to_state.get_data_port_by_id(data_flow.to_key) if to_key_port is None: continue to_key_label = PORT_TYPE_TAG.get(type(to_key_port), 'None') + '.' + \ to_key_port.data_type.__name__ + '.' + \ to_key_port.name data_flow_dict['external'][data_flow.data_flow_id] = {'from_state': from_state_label, 'from_key': from_key_label, 'to_state': to_state_label, 'to_key': to_key_label} # ALL EXTERNAL COMBOS if model.state.state_id in [data_flow.from_state, data_flow.to_state]: # only self-state from_states_store = ListStore(str) for state_id in from_ports_external.keys(): if model.parent.state.state_id == state_id: state_model = model.parent else: state_model = model.parent.states[state_id] if state_model.state.state_id == model.state.state_id: from_states_store.append(['self.' + state_model.state.name + '.' + state_model.state.state_id]) else: from_states_store.append([state_model.state.name + '.' + state_model.state.state_id]) # from_states_store.append(['self.' + model.state.name + '.' + model.state.state_id]) # only outports of self from_keys_store = ListStore(str) if model.parent.state.state_id == data_flow.from_state: # print "output_ports", model.parent.states[data_flow.from_state].state.output_data_ports combined_ports = {} combined_ports.update(model.parent.state.input_data_ports) combined_ports.update(model.parent.state.scoped_variables) get_key_combos(combined_ports, from_keys_store, data_flow.to_key) elif data_flow.from_state in [state_m.state.state_id for state_m in model.parent.states.values()]: get_key_combos(model.parent.state.states[data_flow.from_state].output_data_ports, from_keys_store, data_flow.to_key) else: logger.error( "---------------- FAILURE %s ------------- external from_state PARENT or STATES" % model.state.state_id) # all states and parent-state to_states_store = ListStore(str) for state_id in free_to_port_external.keys(): if model.parent.state.state_id == state_id: state_model = model.parent else: state_model = model.parent.states[state_id] if state_model.state.state_id == model.state.state_id: to_states_store.append(['self.' + state_model.state.name + '.' + state_model.state.state_id]) else: to_states_store.append([state_model.state.name + '.' + state_model.state.state_id]) # all keys of actual to-state to_keys_store = ListStore(str) if get_state_model(model.parent, data_flow.to_state): to_state_model = get_state_model(model.parent, data_flow.to_state) from_state_model = get_state_model(model.parent, data_flow.to_state) act_from_key_port = from_state_model.state.get_data_port_by_id(data_flow.from_key) act_to_key_port = to_state_model.state.get_data_port_by_id(data_flow.to_key) # first actual port to_keys_store.append([PORT_TYPE_TAG.get(type(act_to_key_port), 'None') + '.#' + str(act_to_key_port.data_port_id) + '.' + act_to_key_port.data_type.__name__ + '.' + act_to_key_port.name]) # second all other possible ones if to_state_model.state is model.state.parent: possible_port_ids = to_state_model.state.output_data_ports.keys() + to_state_model.state.scoped_variables.keys() else: possible_port_ids = to_state_model.state.input_data_ports.keys() for port_id in possible_port_ids: port = to_state_model.state.get_data_port_by_id(port_id) # to_state = get_state_model(model.parent, data_flow.to_state).state if not (PORT_TYPE_TAG.get(type(act_from_key_port), 'None') == 'SV' and port.data_port_id == data_flow.from_key)\ and port is not act_to_key_port: to_keys_store.append([PORT_TYPE_TAG.get(type(port), 'None') + '.#' + str(port.data_port_id) + '.' + port.data_type.__name__ + '.' + port.name]) tree_dict_combos['external'][data_flow.data_flow_id] = {'from_state': from_states_store, 'from_key': from_keys_store, 'to_state': to_states_store, 'to_key': to_keys_store} # print "external", data_flow_dict['external'][data_flow.data_flow_id] # print "ALL SCANNED: ", data_flow_dict['internal'].keys(), data_flow_dict['external'].keys(), \ # tree_dict_combos['internal'].keys(), tree_dict_combos['external'].keys() return free_to_port_internal, free_to_port_external, from_ports_internal, from_ports_external
class trustor_entry(object): def __init__(self, trust_trans, trans_id, trust_module, gui_parent, editable, change_register_function): self.change_register_function = change_register_function self.gui_built = False self.trust_trans = trust_trans self.trans_id = trans_id self.trust_module = trust_module self.trustors = self.trust_module.get_trustors() self.editable = editable self.trans_trustor = self.trust_trans.get_trustor() import trustor_entry as plugin_mod filename = get_file_in_same_dir_as_module(plugin_mod, 'data/trustor_entry.glade') load_glade_file_get_widgets_and_connect_signals( glade_file = filename, root_widget = 'window1', widget_holder = self, signal_recipient = self) self.extended_init() if not gui_parent == None: self.table.reparent(gui_parent) buff = self.description_textview.get_buffer() buff.connect("changed", self.description_changed, None) self.window1.hide() self.gui_built = True self.update_trans() # save new transaction immediately after creation def description_changed(self, textbuffer, args): if self.gui_built: self.update_trans() def detach(self): self.table.reparent(self.window1) def extended_init(self): self.trustor_list = ListStore( str ) self.trustor_combo.set_model(self.trustor_list) index = 0 use_index = -1 for trustor_name in self.trustors: self.trustor_list.append([trustor_name]) if not(self.trans_trustor == None) and self.trans_trustor.name == trustor_name: use_index = index index += 1 if use_index > -1: self.trustor_combo.set_active(use_index) self.amount_entry.set_text(str(self.trust_trans.get_displayable_amount())) self.description_textview.get_buffer().set_text(str(self.trust_trans.get_memo())) else: self.trustor_combo.set_active(0) trans_date = self.trust_trans.trans_date self.entry_date_label.set_text( "%s-%s-%s" % (trans_date.year, trans_date.month, trans_date.day) ) if not self.editable or self.trust_trans.get_trustor() == None : self.amount_entry.set_sensitive(False) def construct_filename(self, filename): import trustor_entry as trust_module return join( dirname( abspath( trust_module.__file__ ) ), filename) def update_trans(self): entered_amount = self.amount_entry.get_text() try: self.trust_trans.transfer_amount = Decimal(entered_amount) except InvalidOperation: # In case the user has entered something like "" or ".". self.trust_trans.transfer_amount = Decimal('0') textbuff = self.description_textview.get_buffer() entered_description = textbuff.get_text(textbuff.get_start_iter(), textbuff.get_end_iter()) self.trust_trans.memo = entered_description self.change_register_function() trustor = self.trust_module.get_trustor(self.trustor_combo.get_active_text()) if not(trustor == None): self.trust_module.associate_transaction_with_trustor(self.trans_id, self.trust_trans, trustor.name) def on_window_destroy(self, *args): if self.editable: self.update_trans() main_quit() def on_trustor_combo_changed(self, *args): if self.gui_built: self.update_trans() def on_amount_entry_changed(self, *args): if self.gui_built: self.update_trans()
def create_editable_type_defined_listview_and_model( field_list, new_row_func, parralell_list, change_register, readonly=False, insert_pre_hook=null_function, insert_post_hook=null_function, change_pre_hook=null_function, change_post_hook=null_function, del_pre_hook=null_function, del_post_hook=null_function): vbox = VBox() tv = TreeView() model = ListStore( * chain((display_fieldtype_transform(fieldtype) for fieldname, fieldtype in field_list), (store_fieldtype_transform(fieldtype) for fieldname, fieldtype in field_list) ) # chain ) # ListStore # it is important to do this fill of the liststore # with the existing items first prior to adding event handlers # (row-changed, row-inserted, row-deleted) that # look for changes and keep the two lists in sync for list_row in parralell_list: model.append( tuple(transform_list_row_into_twice_repeated_row_for_model( list_row, field_list) ) ) # append if not readonly: model.connect("row-changed", row_changed_handler, parralell_list, change_register, field_list, change_pre_hook, change_post_hook, ) model.connect("row-inserted", row_inserted_handler, parralell_list, change_register, insert_pre_hook, insert_post_hook ) model.connect("row-deleted", row_deleted_handler, parralell_list, change_register, del_pre_hook, del_post_hook) for i, (fieldname, fieldtype) in enumerate(field_list): def setup_edited_handler_for_renderer_to_original_model(cell_renderer): cell_renderer.connect( 'edited', cell_edited_update_original_modelhandler, model, i, field_list[i][FIELD_TYPE]) return cell_renderer if fieldtype == date: cell_renderer = \ setup_edited_handler_for_renderer_to_original_model( CellRendererDate() ) elif type(fieldtype) == tuple: cell_renderer = CellRendererCombo() cell_renderer.set_property("has-entry", fieldtype[COMBO_TYPE_HAS_ENTRY_FIELD]) combo_liststore = ListStore( str, store_fieldtype_transform(fieldtype) ) for combo_string, combo_value in \ combobox_list_strings_and_values_iteration(fieldtype): combo_liststore.append( (combo_string, combo_value) ) cell_renderer.set_property("model", combo_liststore) cell_renderer.set_property("text-column", 0) if fieldtype[COMBO_TYPE_HAS_ENTRY_FIELD]: setup_edited_handler_for_renderer_to_original_model( cell_renderer) else: lookup_dict = dict( combobox_list_strings_and_values_iteration(fieldtype) ) cell_renderer.connect( 'edited', combo_cell_edited_update_original_modelhandler, model, i, lookup_dict) elif type(fieldtype) == dict and fieldtype['type'] == file: cell_renderer = CellRendererFile( fieldtype['file_type'] if 'file_type' in fieldtype else FILE_CHOOSER_ACTION_OPEN ) setup_edited_handler_for_renderer_to_original_model(cell_renderer) else: cell_renderer = \ setup_edited_handler_for_renderer_to_original_model( CellRendererText() ) if not readonly: cell_renderer.set_property("editable", True) cell_renderer.set_property("editable-set", True) tvc = TreeViewColumn(fieldname, cell_renderer, text=i) tv.append_column(tvc) vbox.pack_start(tv) tv.set_model(model) hbox = HBox() buttons = [ pack_in_stock_but_and_ret(start_stock_button(code), hbox) for code in (STOCK_ADD, STOCK_DELETE) ] if readonly: for button in buttons: button.set_property("sensitive", False) else: buttons[0].connect( "clicked", editable_listview_add_button_clicked_handler, model, new_row_func, field_list ) buttons[1].connect( "clicked", editable_listview_del_button_clicked_handler, tv ) vbox.pack_start(hbox, expand=False) return model, tv, vbox
class PeersTab(Tab): def __init__(self): super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label') self.peer_menu = self.main_builder.get_object('menu_peer_tab') component.get('MainWindow').connect_signals(self) self.listview = self.main_builder.get_object('peers_listview') self.listview.props.has_tooltip = True self.listview.connect('button-press-event', self._on_button_press_event) self.listview.connect('query-tooltip', self._on_query_tooltip) # flag, ip, client, downspd, upspd, country code, int_ip, seed/peer icon, progress self.liststore = ListStore(Pixbuf, str, str, int, int, str, float, Pixbuf, float) self.cached_flag_pixbufs = {} self.seed_pixbuf = icon_seeding self.peer_pixbuf = icon_downloading # key is ip address, item is row iter self.peers = {} # Country column column = TreeViewColumn() render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 0) column.set_sort_column_id(5) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(20) column.set_reorderable(True) self.listview.append_column(column) # Address column column = TreeViewColumn(_('Address')) render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 7) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 1) column.set_sort_column_id(6) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Client column column = TreeViewColumn(_('Client')) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 2) column.set_sort_column_id(2) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Progress column column = TreeViewColumn(_('Progress')) render = CellRendererProgress() column.pack_start(render, True) column.set_cell_data_func(render, cell_data_peer_progress, 8) column.set_sort_column_id(8) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Down Speed column column = TreeViewColumn(_('Down Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_down, 3) column.set_sort_column_id(3) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) column.set_reorderable(True) self.listview.append_column(column) # Up Speed column column = TreeViewColumn(_('Up Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_up, 4) column.set_sort_column_id(4) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) # Bugfix: Last column needs max_width set to stop scrollbar appearing column.set_max_width(150) column.set_reorderable(True) self.listview.append_column(column) self.listview.set_model(self.liststore) self.load_state() self.torrent_id = None def save_state(self): # Get the current sort order of the view column_id, sort_order = self.liststore.get_sort_column_id() # Setup state dict state = { 'columns': {}, 'sort_id': column_id, 'sort_order': int(sort_order) if sort_order else None } for index, column in enumerate(self.listview.get_columns()): state['columns'][column.get_title()] = { 'position': index, 'width': column.get_width() } save_pickled_state_file('peers_tab.state', state) def load_state(self): state = load_pickled_state_file('peers_tab.state') if state is None: return if len(state['columns']) != len(self.listview.get_columns()): log.warning('peers_tab.state is not compatible! rejecting..') return if state['sort_id'] and state['sort_order'] is not None: self.liststore.set_sort_column_id(state['sort_id'], state['sort_order']) for (index, column) in enumerate(self.listview.get_columns()): cname = column.get_title() if cname in state['columns']: cstate = state['columns'][cname] column.set_sizing(TREE_VIEW_COLUMN_FIXED) column.set_fixed_width( cstate['width'] if cstate['width'] > 0 else 10) if state['sort_id'] == index and state[ 'sort_order'] is not None: column.set_sort_indicator(True) column.set_sort_order(state['sort_order']) if cstate['position'] != index: # Column is in wrong position if cstate['position'] == 0: self.listview.move_column_after(column, None) elif self.listview.get_columns()[cstate['position'] - 1].get_title() != cname: self.listview.move_column_after( column, self.listview.get_columns()[cstate['position'] - 1]) def update(self): # Get the first selected torrent torrent_id = component.get('TorrentView').get_selected_torrents() # Only use the first torrent in the list or return if None selected if len(torrent_id) != 0: torrent_id = torrent_id[0] else: # No torrent is selected in the torrentview self.liststore.clear() return if torrent_id != self.torrent_id: # We only want to do this if the torrent_id has changed self.liststore.clear() self.peers = {} self.torrent_id = torrent_id component.get('SessionProxy').get_torrent_status( torrent_id, ['peers']).addCallback(self._on_get_torrent_status) def get_flag_pixbuf(self, country): if not country.strip(): return None if country not in self.cached_flag_pixbufs: # We haven't created a pixbuf for this country yet try: self.cached_flag_pixbufs[country] = pixbuf_new_from_file( deluge.common.resource_filename( 'deluge', os.path.join('ui', 'data', 'pixmaps', 'flags', country.lower() + '.png'))) except Exception as ex: log.debug('Unable to load flag: %s', ex) return None return self.cached_flag_pixbufs[country] def _on_get_torrent_status(self, status): new_ips = set() for peer in status['peers']: new_ips.add(peer['ip']) if peer['ip'] in self.peers: # We already have this peer in our list, so lets just update it row = self.peers[peer['ip']] if not self.liststore.iter_is_valid(row): # This iter is invalid, delete it and continue to next iteration del self.peers[peer['ip']] continue values = self.liststore.get(row, 3, 4, 5, 7, 8) if peer['down_speed'] != values[0]: self.liststore.set_value(row, 3, peer['down_speed']) if peer['up_speed'] != values[1]: self.liststore.set_value(row, 4, peer['up_speed']) if peer['country'] != values[2]: self.liststore.set_value(row, 5, peer['country']) self.liststore.set_value( row, 0, self.get_flag_pixbuf(peer['country'])) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf if icon != values[3]: self.liststore.set_value(row, 7, icon) if peer['progress'] != values[4]: self.liststore.set_value(row, 8, peer['progress']) else: # Peer is not in list so we need to add it # Create an int IP address for sorting purposes if peer['ip'].count(':') == 1: # This is an IPv4 address ip_int = sum([ int(byte) << shift for byte, shift in zip( peer['ip'].split(':')[0].split('.'), (24, 16, 8, 0)) ]) peer_ip = peer['ip'] else: # This is an IPv6 address import socket import binascii # Split out the :port ip = ':'.join(peer['ip'].split(':')[:-1]) ip_int = int( binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16) peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1]) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf row = self.liststore.append([ self.get_flag_pixbuf(peer['country']), peer_ip, peer['client'], peer['down_speed'], peer['up_speed'], peer['country'], float(ip_int), icon, peer['progress'] ]) self.peers[peer['ip']] = row # Now we need to remove any ips that were not in status["peers"] list for ip in set(self.peers).difference(new_ips): self.liststore.remove(self.peers[ip]) del self.peers[ip] def clear(self): self.liststore.clear() def _on_button_press_event(self, widget, event): """This is a callback for showing the right-click context menu.""" log.debug('on_button_press_event') # We only care about right-clicks if self.torrent_id and event.button == 3: self.peer_menu.popup(None, None, None, event.button, event.time) return True def _on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip): if not widget.get_tooltip_context(x, y, keyboard_tip): return False else: model, path, _iter = widget.get_tooltip_context(x, y, keyboard_tip) country_code = model.get(_iter, 5)[0] if country_code != ' ' and country_code in COUNTRIES: tooltip.set_text(COUNTRIES[country_code]) # widget here is self.listview widget.set_tooltip_cell(tooltip, path, widget.get_column(0), None) return True else: return False def on_menuitem_add_peer_activate(self, menuitem): """This is a callback for manually adding a peer""" log.debug('on_menuitem_add_peer') builder = Builder() builder.add_from_file( deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'connect_peer_dialog.ui'))) peer_dialog = builder.get_object('connect_peer_dialog') txt_ip = builder.get_object('txt_ip') response = peer_dialog.run() if response: value = txt_ip.get_text() if value and ':' in value: if ']' in value: # ipv6 ip = value.split(']')[0][1:] port = value.split(']')[1][1:] else: # ipv4 ip = value.split(':')[0] port = value.split(':')[1] if deluge.common.is_ip(ip): log.debug('adding peer %s to %s', value, self.torrent_id) client.core.connect_peer(self.torrent_id, ip, port) peer_dialog.destroy() return True
class trustor_management(object): def __init__(self, trust_module, parent_window, backend_account_fetch): self.backend_account_fetch = backend_account_fetch self.trust_module = trust_module self.trustors = self.trust_module.get_trustors() self.current_name = None self.init() if parent_window != None: self.top_window.set_transient_for(parent_window) self.top_window.set_modal(True) self.extended_init() if hasattr(self.trust_module, 'trust_liability_account_str'): self.widgets['trust_liability_account_label'].set_text( self.trust_module.trust_liability_account_str) if hasattr(self.trust_module, 'cash_account_str'): self.widgets['cash_account_label'].set_text( self.trust_module.cash_account_str ) self.widgets['currency_text_entry'].set_text( self.trust_module.get_currency() ) self.widgets['TrustManagement'].run() self.widgets['TrustManagement'].destroy() def construct_filename(self, filename): import trustor_management as trust_module return join( dirname( abspath( trust_module.__file__ ) ), filename) def reset_view(self): if self.trustor_view.get_column(0) == None: #no columns to remove return while self.trustor_view.remove_column(self.trustor_view.get_column(0)) > 0: pass def refresh_trustor_list(self): self.reset_view() self.trustor_list = ListStore( str, str ) self.trustor_view.set_model(self.trustor_list) for i, title in enumerate(('Trustor', 'Balance')): self.trustor_view.append_column( TreeViewColumn(title,CellRendererText(), text=i ) ) for trustor in self.trustors: self.trustor_list.append([trustor, str(self.trustors[trustor].get_balance())]) def extended_init(self): self.trustor_view = self.widgets['trustor_view'] self.refresh_trustor_list() def init(self): filename = 'data/trustor_management.glade' top_window = 'TrustManagement' do_OldGladeWindowStyleConnect( self, self.construct_filename(filename), top_window) def on_add_button_clicked(self, *args): # Ask the user for a new trustor's name. md = MessageDialog(parent = self.top_window, type = MESSAGE_QUESTION, buttons = BUTTONS_OK_CANCEL, message_format = "What's the new trustor's name?") vbox = md.get_child() name_entry = Entry() vbox.pack_end(name_entry) vbox.show_all() r = md.run() new_trustor_name = name_entry.get_text() md.destroy() # destroys embedded widgets too # Save the new trustor. if r == RESPONSE_OK and new_trustor_name != '': self.current_name = new_trustor_name self.trust_module.add_trustor_by_name(new_trustor_name) transaction.get().commit() self.refresh_trustor_list() def on_remove_button_clicked(self, *args): trustor = self.trust_module.get_trustor(self.current_name) if len(trustor.transactions) > 0: cantDeleteDia = MessageDialog( flags = DIALOG_MODAL, message_format = 'Cannot delete, trustor has associated transacactions.', buttons = BUTTONS_OK) cantDeleteDia.run() cantDeleteDia.hide() else: self.trust_module.drop_trustor_by_name(self.current_name) transaction.get().commit() # Update the view. self.widgets['remove_button'].set_sensitive(False) self.widgets['details_button'].set_sensitive(False) self.refresh_trustor_list() def on_details_button_clicked(self, *args): if self.current_name != None: trustor = self.trust_module.get_trustor(self.current_name) trans = trustor_transactions(self.trust_module, trustor, self) def set_trustor(self, trustor_selected): trustor = self.trust_module.get_trustor(trustor_selected) self.current_name = trustor.name def on_trustor_view_cursor_changed(self, *args): sel = self.trustor_view.get_selection() sel_iter = sel.get_selected()[1] if sel_iter != None: # If the user clicked a row as opposed to whitespace... sel_row = self.trustor_list[sel_iter] trustor_selected = sel_row[0] self.set_trustor(trustor_selected) # Update the view. self.widgets['remove_button'].set_sensitive(True) self.widgets['details_button'].set_sensitive(True) def generate_balance_report(self, filename): report_file = open(filename, 'w') now = datetime.today() nowstring = now.strftime("%B %d, %Y, %H:%M") report_file.write("Trustor balance report as at " + nowstring + "\n\n") for trustor in self.trustors: report_file.write(trustor + ' ' + str(self.trustors[trustor].get_balance()) + '\n') report_file.close() def on_report_button_clicked(self, *args): fcd = FileChooserDialog("Choose report file and location", None, FILE_CHOOSER_ACTION_SAVE, (STOCK_CANCEL, RESPONSE_CANCEL, STOCK_SAVE, RESPONSE_OK)) fcd.set_modal(True) result = fcd.run() file_path = fcd.get_filename() fcd.destroy() if result == RESPONSE_OK and file_path != None: self.generate_balance_report(file_path) def handle_account_fetch(self, label, setter): account_spec, account_str = self.backend_account_fetch( self.top_window) if account_spec != None: setter(account_spec, account_str) label.set_text(account_str) def on_select_trust_liability_clicked(self, *args): self.handle_account_fetch( self.widgets['trust_liability_account_label'], self.trust_module.set_trust_liability_account ) def on_select_cash_account_clicked(self, *args): self.handle_account_fetch( self.widgets['cash_account_label'], self.trust_module.set_cash_account ) def currency_text_entry_changed(self, *args): self.trust_module.currency = \ self.widgets['currency_text_entry'].get_text() transaction.get().commit()
class QueuedTorrents(component.Component): def __init__(self): component.Component.__init__(self, 'QueuedTorrents', depend=['StatusBar', 'AddTorrentDialog']) self.queue = [] self.status_item = None self.config = ConfigManager('gtkui.conf') self.builder = Builder() self.builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'queuedtorrents.ui'))) self.builder.get_object('chk_autoadd').set_active(self.config['autoadd_queued']) self.dialog = self.builder.get_object('queued_torrents_dialog') self.dialog.set_icon(get_logo(32)) self.builder.connect_signals(self) self.treeview = self.builder.get_object('treeview') self.treeview.append_column(TreeViewColumn(_('Torrent'), CellRendererText(), text=0)) self.liststore = ListStore(str, str) self.treeview.set_model(self.liststore) self.treeview.set_tooltip_column(1) def run(self): self.dialog.set_transient_for(component.get('MainWindow').window) self.dialog.show() def start(self): if len(self.queue) == 0: return # Make sure status bar info is showing self.update_status_bar() # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(True) if self.config['autoadd_queued'] or self.config['standalone']: self.on_button_add_clicked(None) else: self.run() def stop(self): # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(False) self.update_status_bar() def add_to_queue(self, torrents): """Adds the list of torrents to the queue""" # Add to the queue while removing duplicates self.queue = list(set(self.queue + torrents)) # Update the liststore self.liststore.clear() for torrent in self.queue: if deluge.common.is_magnet(torrent): magnet = deluge.common.get_magnet_info(torrent) self.liststore.append([magnet['name'], torrent]) else: self.liststore.append([os.path.split(torrent)[1], torrent]) # Update the status bar self.update_status_bar() def update_status_bar(self): """Attempts to update status bar""" # If there are no queued torrents.. remove statusbar widgets and return if len(self.queue) == 0: if self.status_item is not None: component.get('StatusBar').remove_item(self.status_item) self.status_item = None return False try: component.get('StatusBar') except Exception: # The statusbar hasn't been loaded yet, so we'll add a timer to # update it later. timeout_add(100, self.update_status_bar) return False # Set the label text for statusbar if len(self.queue) > 1: label = str(len(self.queue)) + _(' Torrents Queued') else: label = str(len(self.queue)) + _(' Torrent Queued') # Add the statusbar items if needed, or just modify the label if they # have already been added. if self.status_item is None: self.status_item = component.get('StatusBar').add_item( stock=STOCK_SORT_DESCENDING, text=label, callback=self.on_statusbar_click) else: self.status_item.set_text(label) # We return False so the timer stops return False def on_statusbar_click(self, widget, event): log.debug('on_statusbar_click') self.run() def on_button_remove_clicked(self, widget): selected = self.treeview.get_selection().get_selected()[1] if selected is not None: path = self.liststore.get_value(selected, 1) self.liststore.remove(selected) self.queue.remove(path) self.update_status_bar() def on_button_clear_clicked(self, widget): self.liststore.clear() del self.queue[:] self.update_status_bar() def on_button_close_clicked(self, widget): self.dialog.hide() def on_button_add_clicked(self, widget): # Add all the torrents in the liststore def add_torrent(model, path, _iter, data): torrent_path = model.get_value(_iter, 1).decode('utf-8') process_args([torrent_path]) self.liststore.foreach(add_torrent, None) del self.queue[:] self.dialog.hide() self.update_status_bar() def on_chk_autoadd_toggled(self, widget): self.config['autoadd_queued'] = widget.get_active()
class Requirement(Expander): def __init__(self, objectives, new): Expander.__init__(self) self.connect("enter-notify-event", self.onEnterNotifyEvent) self.connect("leave-notify-event", self.onLeaveNotifyEvent) vBox = VBox() self.add(vBox) # Data model self.model = ListStore(str, float) # Title bar hBox = HBox() self.set_property("label-widget", hBox) self.title = Label() hBox.pack_start(self.title) # Alternatives treeView = TreeView(self.model) # treeView.set_headers_visible(False) vBox.pack_start(treeView) listStore_objectives = ListStore(str) for name in objectives: listStore_objectives.append((name, )) def combo_changed(_, path, text, model): model[path][0] = text cellRenderer = CellRendererCombo() cellRenderer.connect("edited", combo_changed, self.model) cellRenderer.set_property("text-column", 0) cellRenderer.set_property("editable", True) cellRenderer.set_property("has-entry", True) cellRenderer.set_property("model", listStore_objectives) treeViewColumn = TreeViewColumn("Alternative", cellRenderer, text=0) # treeViewColumn = TreeViewColumn(None,cellRenderer,text=0) treeView.append_column(treeViewColumn) def spin_changed(_, path, value, model): model[path][1] = float(value.replace(",", ".")) cellRenderer = CellRendererSpin() cellRenderer.connect("edited", spin_changed, self.model) cellRenderer.set_property("adjustment", Adjustment(1, 0, 100, 1, 10, 0)) cellRenderer.set_property("editable", True) cellRenderer.set_property("digits", 2) treeViewColumn = TreeViewColumn(None, cellRenderer, text=1) treeView.append_column(treeViewColumn) # Add/remove alternative button box # hButtonBox = HButtonBox() # vBox.pack_start(hButtonBox, False) # Add alternative button = Button("gtk-add") button.connect("clicked", self.on_btnAdd_Alternative_clicked) button.set_use_stock(True) # hButtonBox.pack_start(button) vBox.pack_start(button, False) # # Remove alternative # button = Button("gtk-remove") # button.connect("clicked",self.on_btnDel_Alternative_clicked) # button.set_use_stock(True) # hButtonBox.pack_start(button) # Expand the requirement and add an alternative if it's new if new: self.set_expanded(True) self.model.append((None, 1.0)) # Show requirement self.show_all() # Delete requirement button (default is hidden) self.imgRemove = Image() self.imgRemove.connect("button-press-event", self.onDelRequirement) self.imgRemove.set_from_stock("gtk-cancel", ICON_SIZE_MENU) hBox.pack_start(self.imgRemove) def Add(self, alternative): self.model.append(alternative) names = [] iter = self.model.get_iter_first() while iter: alt = self.model.get_value(iter, 0) if alt: names.append(alt) iter = self.model.iter_next(iter) self.title.set_text(','.join(names)) def GetData(self): result = OrderedDict() iter = self.model.get_iter_first() while iter: alt = self.model.get_value(iter, 0) if alt: result[alt] = self.model.get_value(iter, 1) iter = self.model.iter_next(iter) return result def onDelRequirement(self, widget): self.get_parent().remove(self) def onEnterNotifyEvent(self, widget, event): self.imgRemove.show() def onLeaveNotifyEvent(self, widget, event): self.imgRemove.hide() def __btnDel_Alternative_Sensitivity(self): pass # self.__btnDel.set_sensitive(len(self.__model.)) def on_btnAdd_Alternative_clicked(self, widget): self.model.append((None, 1.0)) self.__btnDel_Alternative_Sensitivity() def on_btnDel_Alternative_clicked(self, widget): self.__btnDel_Alternative_Sensitivity()
class trustor_transactions(object): def __init__(self, trust_module, trustor, trust_manager): self.trust_module = trust_module self.trustor = trustor self.trust_manager = trust_manager self.init() self.extended_init() parent_window = trust_manager.top_window self.top_window.set_transient_for(parent_window) self.top_window.set_modal(True) def construct_filename(self, filename): import trustor_management as trust_module return join( dirname( abspath( trust_module.__file__ ) ), filename) def get_tran_type(self, transaction): if isinstance(transaction, TrustMoneyInTransaction): return 'In' elif isinstance(transaction, TrustMoneyOutTransaction): return 'Out' else: return 'unknown' def extended_init(self): self.widgets['name_entry'].set_text(self.trustor.name) self.widgets['name_entry'].connect('changed', self.on_name_entry_changed) self.widgets['dyn_balance'].set_text(str(self.trustor.get_balance())) self.transactions_view = self.widgets['transactions_view'] self.transactions_list = ListStore( str, str, str ) self.transactions_view.set_model(self.transactions_list) for i, title in enumerate(('Date', 'Type', 'Balance')): self.transactions_view.append_column( TreeViewColumn(title,CellRendererText(), text=i ) ) for transaction in self.trustor.transactions: self.transactions_list.append([transaction.trans_date.strftime("%B %d, %Y, %H:%M"), self.get_tran_type(transaction), str(transaction.get_transfer_amount())]) def init(self): filename = 'data/trustor_transactions.glade' top_window = 'window1' do_OldGladeWindowStyleConnect( self, self.construct_filename(filename), top_window) def generate_transaction_report(self, filename): report_file = open(filename, 'w') now = datetime.today() nowstring = now.strftime("%B %d, %Y, %H:%M") report_file.write("Trustor transaction report for " + self.trustor.name + " as at " + nowstring + "\n\n") for transaction in self.trustor.transactions: report_file.write(transaction.trans_date.strftime("%B %d, %Y, %H:%M") + ' ' + self.get_tran_type(transaction) + ' ' + str(transaction.get_transfer_amount()) + '\n') report_file.write('\ncurrent balance: ' + str(self.trustor.get_balance()) + '\n') report_file.close() def on_name_entry_changed(self, *args): current_name = self.trustor.name new_name = self.widgets['name_entry'].get_text() save_button = self.widgets['save_button'] if current_name != new_name and new_name != '': save_button.set_sensitive(True) else: save_button.set_sensitive(False) def on_report_button_clicked(self, *args): fcd = gtk.FileChooserDialog( "Choose report file and location", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK) ) fcd.set_modal(True) result = fcd.run() file_path = fcd.get_filename() fcd.destroy() if result == gtk.RESPONSE_OK and file_path != None: self.generate_transaction_report(file_path) def on_save_button_clicked(self, *args): #we're updating the name of someone who already exists current_name = self.trustor.name new_name = self.widgets['name_entry'].get_text() self.trust_module.rename_trustor(current_name, new_name) # refresh view self.trust_manager.refresh_trustor_list() self.widgets['save_button'].set_sensitive(False)
class PeersTab(Tab): def __init__(self): super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label') self.peer_menu = self.main_builder.get_object('menu_peer_tab') component.get('MainWindow').connect_signals(self) self.listview = self.main_builder.get_object('peers_listview') self.listview.props.has_tooltip = True self.listview.connect('button-press-event', self._on_button_press_event) self.listview.connect('query-tooltip', self._on_query_tooltip) # flag, ip, client, downspd, upspd, country code, int_ip, seed/peer icon, progress self.liststore = ListStore(Pixbuf, str, str, int, int, str, float, Pixbuf, float) self.cached_flag_pixbufs = {} self.seed_pixbuf = icon_seeding self.peer_pixbuf = icon_downloading # key is ip address, item is row iter self.peers = {} # Country column column = TreeViewColumn() render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 0) column.set_sort_column_id(5) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(20) column.set_reorderable(True) self.listview.append_column(column) # Address column column = TreeViewColumn(_('Address')) render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 7) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 1) column.set_sort_column_id(6) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Client column column = TreeViewColumn(_('Client')) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 2) column.set_sort_column_id(2) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Progress column column = TreeViewColumn(_('Progress')) render = CellRendererProgress() column.pack_start(render, True) column.set_cell_data_func(render, cell_data_peer_progress, 8) column.set_sort_column_id(8) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Down Speed column column = TreeViewColumn(_('Down Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_down, 3) column.set_sort_column_id(3) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) column.set_reorderable(True) self.listview.append_column(column) # Up Speed column column = TreeViewColumn(_('Up Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_up, 4) column.set_sort_column_id(4) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) # Bugfix: Last column needs max_width set to stop scrollbar appearing column.set_max_width(150) column.set_reorderable(True) self.listview.append_column(column) self.listview.set_model(self.liststore) self.load_state() self.torrent_id = None def save_state(self): # Get the current sort order of the view column_id, sort_order = self.liststore.get_sort_column_id() # Setup state dict state = { 'columns': {}, 'sort_id': column_id, 'sort_order': int(sort_order) if sort_order else None } for index, column in enumerate(self.listview.get_columns()): state['columns'][column.get_title()] = { 'position': index, 'width': column.get_width() } save_pickled_state_file('peers_tab.state', state) def load_state(self): state = load_pickled_state_file('peers_tab.state') if state is None: return if len(state['columns']) != len(self.listview.get_columns()): log.warning('peers_tab.state is not compatible! rejecting..') return if state['sort_id'] and state['sort_order'] is not None: self.liststore.set_sort_column_id(state['sort_id'], state['sort_order']) for (index, column) in enumerate(self.listview.get_columns()): cname = column.get_title() if cname in state['columns']: cstate = state['columns'][cname] column.set_sizing(TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(cstate['width'] if cstate['width'] > 0 else 10) if state['sort_id'] == index and state['sort_order'] is not None: column.set_sort_indicator(True) column.set_sort_order(state['sort_order']) if cstate['position'] != index: # Column is in wrong position if cstate['position'] == 0: self.listview.move_column_after(column, None) elif self.listview.get_columns()[cstate['position'] - 1].get_title() != cname: self.listview.move_column_after(column, self.listview.get_columns()[cstate['position'] - 1]) def update(self): # Get the first selected torrent torrent_id = component.get('TorrentView').get_selected_torrents() # Only use the first torrent in the list or return if None selected if len(torrent_id) != 0: torrent_id = torrent_id[0] else: # No torrent is selected in the torrentview self.liststore.clear() return if torrent_id != self.torrent_id: # We only want to do this if the torrent_id has changed self.liststore.clear() self.peers = {} self.torrent_id = torrent_id component.get('SessionProxy').get_torrent_status(torrent_id, ['peers']).addCallback(self._on_get_torrent_status) def get_flag_pixbuf(self, country): if not country.strip(): return None if country not in self.cached_flag_pixbufs: # We haven't created a pixbuf for this country yet try: self.cached_flag_pixbufs[country] = pixbuf_new_from_file( deluge.common.resource_filename( 'deluge', os.path.join('ui', 'data', 'pixmaps', 'flags', country.lower() + '.png'))) except Exception as ex: log.debug('Unable to load flag: %s', ex) return None return self.cached_flag_pixbufs[country] def _on_get_torrent_status(self, status): new_ips = set() for peer in status['peers']: new_ips.add(peer['ip']) if peer['ip'] in self.peers: # We already have this peer in our list, so lets just update it row = self.peers[peer['ip']] if not self.liststore.iter_is_valid(row): # This iter is invalid, delete it and continue to next iteration del self.peers[peer['ip']] continue values = self.liststore.get(row, 3, 4, 5, 7, 8) if peer['down_speed'] != values[0]: self.liststore.set_value(row, 3, peer['down_speed']) if peer['up_speed'] != values[1]: self.liststore.set_value(row, 4, peer['up_speed']) if peer['country'] != values[2]: self.liststore.set_value(row, 5, peer['country']) self.liststore.set_value(row, 0, self.get_flag_pixbuf(peer['country'])) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf if icon != values[3]: self.liststore.set_value(row, 7, icon) if peer['progress'] != values[4]: self.liststore.set_value(row, 8, peer['progress']) else: # Peer is not in list so we need to add it # Create an int IP address for sorting purposes if peer['ip'].count(':') == 1: # This is an IPv4 address ip_int = sum([int(byte) << shift for byte, shift in zip(peer['ip'].split(':')[0].split('.'), (24, 16, 8, 0))]) peer_ip = peer['ip'] else: # This is an IPv6 address import socket import binascii # Split out the :port ip = ':'.join(peer['ip'].split(':')[:-1]) ip_int = int(binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16) peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1]) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf row = self.liststore.append([ self.get_flag_pixbuf(peer['country']), peer_ip, peer['client'], peer['down_speed'], peer['up_speed'], peer['country'], float(ip_int), icon, peer['progress']]) self.peers[peer['ip']] = row # Now we need to remove any ips that were not in status["peers"] list for ip in set(self.peers).difference(new_ips): self.liststore.remove(self.peers[ip]) del self.peers[ip] def clear(self): self.liststore.clear() def _on_button_press_event(self, widget, event): """This is a callback for showing the right-click context menu.""" log.debug('on_button_press_event') # We only care about right-clicks if self.torrent_id and event.button == 3: self.peer_menu.popup(None, None, None, event.button, event.time) return True def _on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip): if not widget.get_tooltip_context(x, y, keyboard_tip): return False else: model, path, _iter = widget.get_tooltip_context(x, y, keyboard_tip) country_code = model.get(_iter, 5)[0] if country_code != ' ' and country_code in COUNTRIES: tooltip.set_text(COUNTRIES[country_code]) # widget here is self.listview widget.set_tooltip_cell(tooltip, path, widget.get_column(0), None) return True else: return False def on_menuitem_add_peer_activate(self, menuitem): """This is a callback for manually adding a peer""" log.debug('on_menuitem_add_peer') builder = Builder() builder.add_from_file(deluge.common.resource_filename( 'deluge.ui.gtkui', os.path.join('glade', 'connect_peer_dialog.ui') )) peer_dialog = builder.get_object('connect_peer_dialog') txt_ip = builder.get_object('txt_ip') response = peer_dialog.run() if response: value = txt_ip.get_text() if value and ':' in value: if ']' in value: # ipv6 ip = value.split(']')[0][1:] port = value.split(']')[1][1:] else: # ipv4 ip = value.split(':')[0] port = value.split(':')[1] if deluge.common.is_ip(ip): log.debug('adding peer %s to %s', value, self.torrent_id) client.core.connect_peer(self.torrent_id, ip, port) peer_dialog.destroy() return True
def __init__(self, objectives, new): Expander.__init__(self) self.connect("enter-notify-event", self.onEnterNotifyEvent) self.connect("leave-notify-event", self.onLeaveNotifyEvent) vBox = VBox() self.add(vBox) # Data model self.model = ListStore(str, float) # Title bar hBox = HBox() self.set_property("label-widget", hBox) self.title = Label() hBox.pack_start(self.title) # Alternatives treeView = TreeView(self.model) # treeView.set_headers_visible(False) vBox.pack_start(treeView) listStore_objectives = ListStore(str) for name in objectives: listStore_objectives.append((name, )) def combo_changed(_, path, text, model): model[path][0] = text cellRenderer = CellRendererCombo() cellRenderer.connect("edited", combo_changed, self.model) cellRenderer.set_property("text-column", 0) cellRenderer.set_property("editable", True) cellRenderer.set_property("has-entry", True) cellRenderer.set_property("model", listStore_objectives) treeViewColumn = TreeViewColumn("Alternative", cellRenderer, text=0) # treeViewColumn = TreeViewColumn(None,cellRenderer,text=0) treeView.append_column(treeViewColumn) def spin_changed(_, path, value, model): model[path][1] = float(value.replace(",", ".")) cellRenderer = CellRendererSpin() cellRenderer.connect("edited", spin_changed, self.model) cellRenderer.set_property("adjustment", Adjustment(1, 0, 100, 1, 10, 0)) cellRenderer.set_property("editable", True) cellRenderer.set_property("digits", 2) treeViewColumn = TreeViewColumn(None, cellRenderer, text=1) treeView.append_column(treeViewColumn) # Add/remove alternative button box # hButtonBox = HButtonBox() # vBox.pack_start(hButtonBox, False) # Add alternative button = Button("gtk-add") button.connect("clicked", self.on_btnAdd_Alternative_clicked) button.set_use_stock(True) # hButtonBox.pack_start(button) vBox.pack_start(button, False) # # Remove alternative # button = Button("gtk-remove") # button.connect("clicked",self.on_btnDel_Alternative_clicked) # button.set_use_stock(True) # hButtonBox.pack_start(button) # Expand the requeriment and add an alternative if it's new if new: self.set_expanded(True) self.model.append((None, 1.0)) # Show requeriment self.show_all() # Delete requeriment button (default is hidden) self.imgRemove = Image() self.imgRemove.connect("button-press-event", self.onDelRequeriment) self.imgRemove.set_from_stock("gtk-cancel", ICON_SIZE_MENU) hBox.pack_start(self.imgRemove)
class InputWidget(Component): """Input mode widget for Rainbow theme.""" def __init__(self, component_manager): Component.__init__(self, component_manager) self.w_tree = self.main_widget().w_tree self.get_widget = get_widget = self.w_tree.get_widget self.conf = self.config() self.connections = [] self.connect_signals([\ ("input_mode_toolbar_button_back_w", "clicked", \ self.input_to_main_menu_cb), ("front_to_back_mode_selector_w", "released", \ self.change_card_type_cb), ("both_way_mode_selector_w", "released", self.change_card_type_cb), ("three_side_mode_selector_w", "released", \ self.change_card_type_cb), ("cloze_mode_selector_w", "released", self.change_card_type_cb), ("picture_content_button", "clicked", self.add_picture_cb), ("image_selection_dialog_button_select", "clicked", \ self.select_item_cb), ("image_selection_dialog_button_close", "clicked", self.close_media_selection_dialog_cb), ("input_mode_prev_category_w", "clicked", self.change_category_cb), ("input_mode_next_category_w", "clicked", self.change_category_cb), ("input_mode_add_new_category_w", "clicked", \ self.create_new_category_cb), ("sound_content_button", "clicked", self.add_sound_cb), ("category_name_container", "clicked", \ self.show_add_category_block_cb), ("input_mode_close_add_category_block_w", "clicked", self.hide_add_category_block_cb), ("input_mode_snd_button", "released", \ self.preview_sound_in_input_cb)]) self.fact = None self.sounddir = None self.imagedir = None self.card_type = None self.categories_list = [] self.added_new_cards = False #liststore = [text, type, filename, dirname, pixbuf] self.liststore = ListStore(str, str, str, str, gtk.gdk.Pixbuf) iconview_widget = get_widget("iconview_widget") iconview_widget.set_model(self.liststore) iconview_widget.set_pixbuf_column(4) iconview_widget.set_text_column(0) get_widget = self.get_widget # Widgets as attributes self.areas = {# Text areas "cloze": get_widget("cloze_text_w"), "answer": get_widget("answer_text_w"), "foreign": get_widget("foreign_text_w"), "question": get_widget("question_text_w"), "translation": get_widget("translation_text_w"), "pronunciation": get_widget("pronun_text_w") } # Mandatory color setup fot GtkTextView for area in self.areas.values(): area.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#FFFFFF")) area.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000")) # Change default font font = pango.FontDescription("Nokia Sans %s" % \ (self.conf['font_size'] - FONT_DISTINCTION)) for area in self.areas.values(): area.modify_font(font) self.widgets = {# Other widgets "CurrentCategory": get_widget("category_name_w"), "SoundButton": get_widget("sound_content_button"), "PictureButton": get_widget("picture_content_button"), "SoundIndicator": get_widget("input_mode_snd_button"), "CardTypeSwithcer": get_widget("card_type_switcher_w"), "MediaDialog": get_widget("media_selection_dialog"), "SoundContainer": get_widget("input_mode_snd_container"), "QuestionContainer": get_widget("input_mode_question_container"), "NewCategory": get_widget("input_mode_new_category_entry"), "ChangeCategoryBlock": get_widget(\ "input_mode_change_category_block"), "AddCategoryBlock": get_widget("input_mode_add_category_block") } # Mandatory color setup fot GtkEntry self.widgets["NewCategory"].modify_base(gtk.STATE_NORMAL, \ gtk.gdk.color_parse("#FFFFFF")) self.widgets["NewCategory"].modify_text(gtk.STATE_NORMAL, \ gtk.gdk.color_parse("#000000")) # card_id: {"page": page_id, "selector": selector_widget, # "widgets": [(field_name:text_area_widget)...]} self.selectors = { FrontToBack.id: { "page": 0, "selector": get_widget("front_to_back_mode_selector_w"), "widgets": [('q', self.areas["question"]), ('a', self.areas["answer"])] }, BothWays.id: { "page": 0, "selector": get_widget("both_way_mode_selector_w"), "widgets": [('q', self.areas["question"]), ('a', self.areas["answer"])] }, ThreeSided.id: { "page": 1, "selector": get_widget("three_side_mode_selector_w"), "widgets": [('f', self.areas["foreign"]), ('t', self.areas["translation"]), ('p', self.areas["pronunciation"])] }, Cloze.id: { "page": 2, "selector": get_widget("cloze_mode_selector_w"), "widgets": [('text', self.areas["cloze"])] } } # add card_type to selectors subdict for card_type in self.card_types(): self.selectors[card_type.id]["card_type"] = card_type # create {selector_widget:card_type.id} dict self.widget_card_id = dict((self.selectors[id]["selector"], id) \ for id in self.selectors.keys()) self.set_card_type(get_widget("front_to_back_mode_selector_w")) self.compose_widgets() # Turn off hildon autocapitalization try: for widget in self.areas.values(): widget.set_property("hildon-input-mode", 'full') # stock gtk doesn't have hildon properties except (TypeError, AttributeError): pass # so, skip silently def connect_signals(self, control): """Connect signals to widgets and save connection info.""" for wname, signal, callback in control: widget = self.get_widget(wname) cid = widget.connect(signal, callback) self.connections.append((widget, cid)) def disconnect_signals(self): """Disconnect previously connected signals.""" for widget, cid in self.connections: widget.disconnect(cid) self.connections = [] def show_snd_container(self): """Show or hide snd container. """ start, end = self.areas["question"].get_buffer().get_bounds() text = self.areas["question"].get_buffer().get_text(start, end) if "sound src=" in text: self.widgets["QuestionContainer"].hide() self.widgets["SoundContainer"].show() else: self.widgets["QuestionContainer"].show() self.widgets["SoundContainer"].hide() def compose_widgets (self): """Switch to neccessary input page. It depends on card_type.""" self.widgets["CardTypeSwithcer"].set_current_page( \ self.selectors[self.card_type.id]["page"]) self.selectors[self.card_type.id]["selector"].set_active(True) state = self.card_type.id in (FrontToBack.id) self.widgets["PictureButton"].set_sensitive(state) self.widgets["SoundButton"].set_sensitive(state) def set_card_type(self, widget): """Set current card type.""" card_type_id = self.widget_card_id[widget] self.card_type = self.selectors[card_type_id]["card_type"] def update_categories(self): """Update categories list content.""" if not self.categories_list: categories = dict([(i, name) for (i, name) in \ enumerate(self.database().tag_names())]) if categories.values(): for category in sorted(categories.values()): self.categories_list.append(category) self.widgets["CurrentCategory"].set_text( \ sorted(categories.values())[0]) else: self.categories_list.append("default category") self.widgets["CurrentCategory"].set_text("default category") def check_complete_input(self): """Check for non empty fields.""" pattern_list = ["Type %s here..." % item for item in ["ANSWER", \ "QUESTION", "FOREIGN", "PRONUNCIATION", "TRANSLATION", "TEXT"]] pattern_list.append("") for selector in self.selectors[self.card_type.id]["widgets"]: buf = selector[1].get_buffer() start, end = buf.get_bounds() if buf.get_text(start, end) in pattern_list: return False return True def change_category_cb(self, widget): """Change current category.""" if widget.name == "prev_category_w": direction = -1 direction = 1 category_index = self.categories_list.index( \ self.widgets["CurrentCategory"].get_text()) try: new_category = self.categories_list[category_index + direction] except IndexError: if direction: new_category = self.categories_list[0] else: new_category = self.categories_list[len(self.categories_list)-1] self.widgets["CurrentCategory"].set_text(new_category) def create_new_category_cb(self, widget): """Create new category.""" new_category = self.widgets["NewCategory"].get_text() if new_category: self.categories_list.append(new_category) self.widgets["NewCategory"].set_text("") self.widgets["CurrentCategory"].set_text(new_category) self.hide_add_category_block_cb(None) def add_picture_cb(self, widget): """Show image selection dialog.""" self.main_widget().soundplayer.stop() self.liststore.clear() self.imagedir = self.conf['imagedir'] if not os.path.exists(self.imagedir): self.imagedir = "./images" # on Desktop if not os.path.exists(self.imagedir): self.main_widget().information_box(\ _("'Images' directory does not exist!")) return if os.listdir(self.imagedir): self.widgets["MediaDialog"].show() for fname in os.listdir(self.imagedir): if os.path.isfile(os.path.join(self.imagedir, fname)): pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(\ os.path.join(self.imagedir, fname), 100, 100) self.liststore.append(["", "img", fname, \ self.imagedir, pixbuf]) else: self.main_widget().information_box(\ _("There are no files in 'Images' directory!")) def select_item_cb(self, widget): """ Set html-text with media path and type when user select media filefrom media selection dialog. """ self.widgets["MediaDialog"].hide() item_index = self.w_tree.get_widget("iconview_widget"). \ get_selected_items()[0] item_type = self.liststore.get_value( \ self.liststore.get_iter(item_index), 1) item_fname = self.liststore.get_value( \ self.liststore.get_iter(item_index), 2) item_dirname = self.liststore.get_value( \ self.liststore.get_iter(item_index), 3) question_text = """<%s src="%s">""" % \ (item_type, os.path.abspath(os.path.join(item_dirname, item_fname))) self.areas["question"].get_buffer().set_text(question_text) self.show_snd_container() def add_sound_cb(self, widget): """Show sound selection dialog.""" self.main_widget().soundplayer.stop() self.liststore.clear() self.sounddir = self.conf['sounddir'] if not os.path.exists(self.sounddir): self.sounddir = "./sounds" # on Desktop if not os.path.exists(self.sounddir): self.main_widget().information_box(\ _("'Sounds' directory does not exist!")) return if os.listdir(self.sounddir): self.widgets["MediaDialog"].show() for fname in os.listdir(self.sounddir): if os.path.isfile(os.path.join(self.sounddir, fname)): sound_logo_file = os.path.join( \ self.conf["theme_path"], "soundlogo.png") pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(\ sound_logo_file, 100, 100) self.liststore.append([fname, "sound", fname, \ self.sounddir, pixbuf]) else: self.main_widget().information_box(\ _("There are no files in 'Sounds' directory!")) def get_widgets_data(self, check_for_required=True): """ Get data from widgets. """ fact = {} for fact_key, widget in self.selectors[self.card_type.id]["widgets"]: start, end = widget.get_buffer().get_bounds() fact[fact_key] = widget.get_buffer().get_text(start, end) if check_for_required: for required in self.card_type.required_fields: if not fact[required]: raise ValueError return fact def set_widgets_data(self, fact): """Set widgets data from fact.""" for fact_key, widget in self.selectors[self.card_type.id]["widgets"]: widget.get_buffer().set_text(fact[fact_key]) def clear_widgets(self): """Clear data in widgets.""" for caption in self.areas.keys(): self.areas[caption].get_buffer().set_text( \ "Type %s here..." % caption.upper()) def change_card_type_cb(self, widget): """Change cardtype when user choose it from cardtype column.""" self.main_widget().soundplayer.stop() self.clear_widgets() self.show_snd_container() self.set_card_type(widget) self.compose_widgets() def preview_sound_in_input_cb(self, widget): """Preview sound in input mode.""" if widget.get_active(): start, end = self.areas["question"].get_buffer().get_bounds() text = self.areas["question"].get_buffer().get_text(start, end) self.main_widget().soundplayer.play(text, self) else: self.main_widget().soundplayer.stop() def update_indicator(self): """Set non active state for widget.""" self.widgets["SoundIndicator"].set_active(False) def close_media_selection_dialog_cb(self, widget): """Close image selection dialog.""" self.widgets["MediaDialog"].hide() def show_add_category_block_cb(self, widget): """Show add category block.""" self.widgets["ChangeCategoryBlock"].hide() self.widgets["AddCategoryBlock"].show() self.widgets["NewCategory"].grab_focus() def hide_add_category_block_cb(self, widget): """Hide add category block.""" self.widgets["ChangeCategoryBlock"].show() self.widgets["AddCategoryBlock"].hide() def input_to_main_menu_cb(self, widget): """Return to main menu.""" #if self.added_new_cards: #self.review_controller().reset() #self.added_new_cards = False self.disconnect_signals() self.main_widget().soundplayer.stop() self.main_widget().menu_()