diff --git a/TODO b/TODO index 3e0041e72..d75e19416 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* Add state saving to listview.. this includes saving column size and position. * Queue plugin 'apply_queue' stuff.. Just finishing the queue plugin and it's intended functionality. * Figure out easy way for user-made plugins to add i18n support. diff --git a/deluge/core/core.py b/deluge/core/core.py index be3b3f000..0fa3591f6 100644 --- a/deluge/core/core.py +++ b/deluge/core/core.py @@ -55,6 +55,7 @@ from deluge.core.signalmanager import SignalManager from deluge.log import LOG as log DEFAULT_PREFS = { + "config_location": deluge.common.get_config_dir(), "daemon_port": 58846, "allow_remote": False, "compact_allocation": True, diff --git a/deluge/pluginmanagerbase.py b/deluge/pluginmanagerbase.py index 8746203da..a66d9543b 100644 --- a/deluge/pluginmanagerbase.py +++ b/deluge/pluginmanagerbase.py @@ -63,7 +63,6 @@ class PluginManagerBase: self.enable_plugin(name) def shutdown(self): - log.debug("PluginManager shutting down..") for plugin in self.plugins.values(): plugin.disable() del self.plugins diff --git a/deluge/ui/component.py b/deluge/ui/component.py index e5055c827..d824ec244 100644 --- a/deluge/ui/component.py +++ b/deluge/ui/component.py @@ -60,6 +60,9 @@ class Component: def _stop(self): self._state = COMPONENT_STATE.index("Stopped") + def shutdown(self): + pass + def update(self): pass @@ -100,6 +103,7 @@ class ComponentRegistry: # Only start if the component is stopped. if self.components[name].get_state() == \ COMPONENT_STATE.index("Stopped"): + log.debug("Starting component %s..", name) self.components[name].start() self.components[name]._start() @@ -107,6 +111,7 @@ class ComponentRegistry: def stop(self): """Stops all components""" for component in self.components.keys(): + log.debug("Stopping component %s..", component) self.components[component].stop() self.components[component]._stop() # Stop the update timer @@ -122,6 +127,17 @@ class ComponentRegistry: return True + def shutdown(self): + """Shuts down all components. This should be called when the program + exits so that components can do any necessary clean-up.""" + for component in self.components.keys(): + log.debug("Shutting down component %s..", component) + try: + self.components[component].shutdown() + except Exception, e: + log.debug("Unable to call shutdown(): %s", e) + + _ComponentRegistry = ComponentRegistry() def register(name, obj, depend=None): @@ -140,6 +156,10 @@ def update(): """Updates all components""" _ComponentRegistry.update() +def shutdown(): + """Shutdowns all components""" + _ComponentRegistry.shutdown() + def get(component): """Return a reference to the component""" return _ComponentRegistry.get(component) diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 63226f6ba..6b1fe4d1b 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -55,10 +55,12 @@ from pluginmanager import PluginManager from dbusinterface import DbusInterface from queuedtorrents import QueuedTorrents from deluge.configmanager import ConfigManager +import deluge.common from deluge.log import LOG as log import deluge.configmanager DEFAULT_PREFS = { + "config_location": deluge.common.get_config_dir(), "interactive_add": False, "enable_files_dialog": False, "enable_system_tray": True, @@ -151,6 +153,8 @@ class GtkUI: del config # Clean-up + # Shutdown all components + component.shutdown() del self.mainwindow del self.systemtray del self.menubar diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 54ccb5c31..bb6fbcf90 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -31,11 +31,15 @@ # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. +import cPickle +import os.path + import pygtk pygtk.require('2.0') import gtk import gettext +from deluge.configmanager import ConfigManager import deluge.common from deluge.log import LOG as log @@ -84,6 +88,16 @@ def cell_data_ratio(column, cell, model, row, data): ratio_str = "%.3f" % ratio cell.set_property('text', ratio_str) +class ListViewColumnState: + """Used for saving/loading column state""" + def __init__(self, name, position, width, visible, sort, sort_order): + self.name = name + self.position = position + self.width = width + self.visible = visible + self.sort = sort + self.sort_order = sort_order + class ListView: """ListView is used to make custom GtkTreeViews. It supports the adding and removing of columns, creating a menu for a column toggle list and @@ -106,8 +120,7 @@ class ListView: # If column is 'hidden' then it will not be visible and will not # show up in any menu listing; it cannot be shown ever. self.hidden = False - - + def __init__(self, treeview_widget=None): log.debug("ListView initialized..") @@ -138,6 +151,50 @@ class ListView: # created. self.checklist_menus = [] + def save_state(self, filename): + """Saves the listview state (column positions and visibility) to + filename.""" + # A list of ListViewColumnStates + state = [] + + # Get the list of TreeViewColumns from the TreeView + treeview_columns = self.treeview.get_columns() + counter = 0 + for column in treeview_columns: + # Append a new column state to the state list + state.append(ListViewColumnState(column.get_title(), counter, + column.get_width(), column.get_visible(), + column.get_sort_indicator(), int(column.get_sort_order()))) + # Increase the counter because this is how we determine position + counter += 1 + + # Get the config location for saving the state file + config_location = ConfigManager("gtkui.conf")["config_location"] + + try: + log.debug("Saving ListView state file: %s", filename) + state_file = open(os.path.join(config_location, filename), "wb") + cPickle.dump(state, state_file) + state_file.close() + except IOError, e: + log.warning("Unable to save state file: %s", e) + + def load_state(self, filename): + """Load the listview state from filename.""" + # Get the config location for loading the state file + config_location = ConfigManager("gtkui.conf")["config_location"] + + try: + log.debug("Loading ListView state file: %s", filename) + state_file = open(os.path.join(config_location, filename), "rb") + state = cPickle.load(state_file) + state_file.close() + except IOError: + log.warning("Unable to load state file: %s", e) + + # Keep the state in self.state so we can access it as we add new columns + self.state = state + def set_treeview(self, treeview_widget): """Set the treeview widget that this listview uses.""" self.treeview = treeview_widget @@ -317,6 +374,7 @@ class ListView: elif column_type == None: return + column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_sort_column_id(self.columns[header].column_indices[sortid]) column.set_clickable(True) column.set_resizable(True) @@ -324,10 +382,23 @@ class ListView: column.set_min_width(10) column.set_reorderable(True) column.set_visible(not hidden) + + # Check for loaded state and apply + for column_state in self.state: + if header == column_state.name: + # We found a loaded state + if column_state.width > 0: + column.set_fixed_width(column_state.width) + column.set_sort_indicator(column_state.sort) + column.set_sort_order(column_state.sort_order) + column.set_visible(column_state.visible) + position = column_state.position + if position is not None: self.treeview.insert_column(column, position) else: self.treeview.append_column(column) + # Set hidden in the column self.columns[header].hidden = hidden self.columns[header].column = column diff --git a/deluge/ui/gtkui/statusbar.py b/deluge/ui/gtkui/statusbar.py index b0ede5e55..30378b0d4 100644 --- a/deluge/ui/gtkui/statusbar.py +++ b/deluge/ui/gtkui/statusbar.py @@ -107,7 +107,6 @@ class StatusBar(component.Component): self.show_not_connected() def start(self): - log.debug("StatusBar start..") # Add in images and labels self.remove_item(self.not_connected_item) self.connections_item = StatusBarItem( diff --git a/deluge/ui/gtkui/systemtray.py b/deluge/ui/gtkui/systemtray.py index c0be1eef7..326fd149f 100644 --- a/deluge/ui/gtkui/systemtray.py +++ b/deluge/ui/gtkui/systemtray.py @@ -99,7 +99,6 @@ class SystemTray(component.Component): self.tray_glade.get_widget(widget).hide() def start(self): - log.debug("SystemTray start..") if self.config["enable_system_tray"]: # Show widgets in the hide list because we've connected to a host for widget in self.hide_widget_list: @@ -109,7 +108,6 @@ class SystemTray(component.Component): self.build_tray_bwsetsubmenu() def stop(self): - log.debug("SystemTray stop..") try: # Hide widgets in hide list because we're not connected to a host for widget in self.hide_widget_list: diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 5a5766632..170c5c2b7 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -116,7 +116,9 @@ class TorrentView(listview.ListView, component.Component): listview.ListView.__init__(self, self.window.main_glade.get_widget("torrent_view")) log.debug("TorrentView Init..") - + # Try to load the state file if available + self.load_state("torrentview.state") + # Register the columns menu with the listview so it gets updated # accordingly. self.register_checklist_menu( @@ -185,7 +187,7 @@ class TorrentView(listview.ListView, component.Component): # changes. self.treeview.get_selection().connect("changed", self.on_selection_changed) - + def start(self): """Start the torrentview""" # We need to get the core session state to know which torrents are in @@ -198,7 +200,11 @@ class TorrentView(listview.ListView, component.Component): """Stops the torrentview""" # We need to clear the liststore self.liststore.clear() - + + def shutdown(self): + """Called when GtkUi is exiting""" + self.save_state("torrentview.state") + def set_filter(self, field, condition): """Sets filters for the torrentview..""" self.filter = (field, condition)