diff --git a/deluge/core/pluginmanager.py b/deluge/core/pluginmanager.py index 21bc5d114..b40bc5522 100644 --- a/deluge/core/pluginmanager.py +++ b/deluge/core/pluginmanager.py @@ -76,7 +76,11 @@ class PluginManager: self.hooks[hook].append(function) except KeyError: log.warning("Plugin attempting to register invalid hook.") + + def run_hook(self, hook, data): + log.debug("Running hook %s", hook) + def run_post_torrent_add(self, torrent_id): log.debug("run_post_torrent_add") try: diff --git a/deluge/plugins/queue/queue/gtkui.py b/deluge/plugins/queue/queue/gtkui.py index f243fbbb5..e9f6839de 100644 --- a/deluge/plugins/queue/queue/gtkui.py +++ b/deluge/plugins/queue/queue/gtkui.py @@ -39,3 +39,7 @@ log = logging.getLogger("deluge") class GtkUI: def __init__(self, plugin_manager): log.debug("Queue GtkUI plugin initalized..") + self.plugin = plugin_manager + # Get the torrentview component from the plugin manager + self.torrentview = self.plugin.get_torrentview() + diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 2a4e83635..3d405706e 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -60,16 +60,16 @@ class GtkUI: "po")) # Initialize the main window - self.main_window = MainWindow() + self.mainwindow = MainWindow() # Start the signal receiver self.signal_receiver = Signals(self) # Initalize the plugins - self.plugins = PluginManager() + self.plugins = PluginManager(self) # Show the main window - self.main_window.show() + self.mainwindow.show() # Start the gtk main loop gtk.main() diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py new file mode 100644 index 000000000..ca03fe61e --- /dev/null +++ b/deluge/ui/gtkui/listview.py @@ -0,0 +1,241 @@ +# +# listview.py +# +# Copyright (C) 2007 Andrew Resch ('andar') +# +# Deluge is free software. +# +# You may redistribute it and/or modify it under the terms of the +# GNU General Public License, as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) +# any later version. +# +# deluge 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 deluge. If not, write to: +# The Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the OpenSSL +# library. +# You must obey the GNU General Public License in all respects for all of +# the code used other than OpenSSL. If you modify file(s) with this +# exception, you may extend this exception to your version of the file(s), +# but you are not obligated to do so. If you do not wish to do so, delete +# 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 logging + +import pygtk +pygtk.require('2.0') +import gtk +import gettext + +# Get the logger +log = logging.getLogger("deluge") + +# Cell data functions to pass to add_func_column() + +def cell_data_speed(column, cell, model, iter, data): + speed = int(model.get_value(iter, data)) + speed_str = deluge.common.fspeed(speed) + cell.set_property('text', speed_str) + +def cell_data_size(column, cell, model, iter, data): + size = long(model.get_value(iter, data)) + size_str = deluge.common.fsize(size) + cell.set_property('text', size_str) + +def cell_data_peer(column, cell, model, iter, data): + c1, c2 = data + a = int(model.get_value(iter, c1)) + b = int(model.get_value(iter, c2)) + cell.set_property('text', '%d (%d)'%(a, b)) + +def cell_data_time(column, cell, model, iter, data): + time = int(model.get_value(iter, data)) + if time < 0 or time == 0: + time_str = _("Infinity") + else: + time_str = deluge.common.ftime(time) + cell.set_property('text', time_str) + +def cell_data_ratio(column, cell, model, iter, data): + ratio = float(model.get_value(iter, data)) + if ratio == -1: + ratio_str = _("Unknown") + else: + ratio_str = "%.3f"%ratio + cell.set_property('text', ratio_str) + +class ListView: + class ListViewColumn: + def __init__(self, name, column_indices): + self.name = name + # self.column_types = column_types + self.column_indices = column_indices + + def __init__(self, treeview_widget=None): + log.debug("ListView initialized..") + + if treeview_widget is not None: + # User supplied a treeview widget + self.treeview = treeview_widget + else: + self.treeview = gtk.TreeView() + + self.liststore = None + + self.treeview.set_model(self.liststore) + self.treeview.set_rules_hint(True) + self.treeview.set_reorderable(True) + self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) + + self.columns = {} + self.liststore_columns = [] + + def create_new_liststore(self): + # Create a new liststore with added column and move the data from the + # old one to the new one. + new_list = gtk.ListStore(*tuple(self.liststore_columns)) + + # This function is used in the liststore.foreach method with user_data + # being the new liststore and the columns list + def copy_row(model, path, row, user_data): + new_list, columns = user_data + # Iterate over the columns except the last one. This one would have + # been just added and no need to copy it from the old list. + for column in range(model.get_n_columns()): + # Get the current value of the column for this row + value = model.get_value(row, column) + # Set the value of this row and column in the new liststore + new_list.set_value(row, column, value) + + # Do the actual row copy + if self.liststore is not None: + self.liststore.foreach(copy_row, (new_list, self.columns)) + + self.liststore = new_list + self.treeview.set_model(self.liststore) + + return + + def add_text_column(self, header, visible=True): + # Create a new column object and add it to the list + self.liststore_columns.append(str) + self.columns[header] = self.ListViewColumn(header, + [len(self.liststore_columns) - 1]) + + # Create a new list with the added column + self.create_new_liststore() + + # Now add the column to the treeview so the user can see it + render = gtk.CellRendererText() + column = gtk.TreeViewColumn(header, render, + text=self.columns[header].column_indices[0]) + column.set_clickable(True) + column.set_sort_column_id(self.columns[header].column_indices[0]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + column.set_visible(visible) + self.treeview.append_column(column) + + return True + + def add_func_column(self, header, function, column_types, sortid=0): + # Add the new column types to the list and keep track of the liststore + # columns that this column object uses. + # Set sortid to the column index relative the to column_types used. + # Default is 0. + + column_indices = [] + for column_type in column_types: + self.liststore_columns.append(column_type) + column_indices.append(len(self.liststore_columns) - 1) + + # Create a new column object and add it to the list + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + column = gtk.TreeViewColumn(header) + render = gtk.CellRendererText() + column.pack_start(render, True) + column.set_cell_data_func(render, function, + tuple(self.columns[header].column_indices)) + column.set_clickable(True) + column.set_sort_column_id(column_indices[sortid]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + self.treeview.append_column(column) + + return True + + def add_progress_column(self, header): + # For the progress value + self.liststore_columns.append(float) + # For the text + self.liststore_columns.append(str) + column_indices = [len(self.liststore_columns) - 2, + len(self.liststore_columns) - 1] + # Create a new column object and add it to the list + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + render = gtk.CellRendererProgress() + column = gtk.TreeViewColumn(header, render, + value=self.columns[header].column_indices[0], + text=self.columns[header].column_indices[1]) + column.set_clickable(True) + column.set_sort_column_id(self.columns[header].column_indices[0]) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + self.treeview.append_column(column) + + return True + + def add_texticon_column(self, header): + # For icon + self.liststore_columns.append(gtk.gdk.Pixbuf) + # For text + self.liststore_columns.append(str) + column_indices = [len(self.liststore_columns) - 2, + len(self.liststore_columns) - 1] + self.columns[header] = self.ListViewColumn(header, column_indices) + + # Create new list with the added columns + self.create_new_liststore() + + column = gtk.TreeViewColumn(header) + column.set_clickable(True) + column.set_resizable(True) + column.set_expand(False) + column.set_min_width(10) + column.set_reorderable(True) + render = gtk.CellRendererPixbuf() + column.pack_start(render, expand=False) + column.add_attribute(render, 'pixbuf', + self.columns[header].column_indices[0]) + render = gtk.CellRendererText() + column.pack_start(render, expand=True) + column.add_attribute(render, 'text', + self.columns[header].column_indices[1]) + self.treeview.append_column(column) + + return True diff --git a/deluge/ui/gtkui/pluginmanager.py b/deluge/ui/gtkui/pluginmanager.py index 6ea4cf9a7..527d0cd12 100644 --- a/deluge/ui/gtkui/pluginmanager.py +++ b/deluge/ui/gtkui/pluginmanager.py @@ -40,10 +40,9 @@ import pkg_resources log = logging.getLogger("deluge") class PluginManager: - def __init__(self): - # Set up the hooks dictionary - self.hooks = { - } + def __init__(self, gtkui): + + self._gtkui = gtkui # This will load any .eggs in the plugins folder inside the main # deluge egg.. Need to scan the local plugin folder too. @@ -67,3 +66,7 @@ class PluginManager: def __getitem__(self, key): return self.plugins[key] + + def get_torrentview(self): + """Returns a reference to the torrentview component""" + return self._gtkui.mainwindow.torrentview diff --git a/deluge/ui/gtkui/signals.py b/deluge/ui/gtkui/signals.py index 6ffd3f671..1d97ac4dd 100644 --- a/deluge/ui/gtkui/signals.py +++ b/deluge/ui/gtkui/signals.py @@ -63,27 +63,20 @@ class Signals: self.core.connect_to_signal("torrent_added", self.torrent_added_signal) self.core.connect_to_signal("torrent_removed", self.torrent_removed_signal) - self.core.connect_to_signal("torrent_queue_changed", - self.torrent_queue_changed_signal) self.core.connect_to_signal("torrent_paused", self.torrent_paused) def torrent_added_signal(self, torrent_id): log.debug("torrent_added signal received..") log.debug("torrent id: %s", torrent_id) # Add the torrent to the treeview - self.ui.main_window.torrentview.add_row(torrent_id) + self.ui.mainwindow.torrentview.add_row(torrent_id) def torrent_removed_signal(self, torrent_id): log.debug("torrent_remove signal received..") log.debug("torrent id: %s", torrent_id) # Remove the torrent from the treeview - self.ui.main_window.torrentview.remove_row(torrent_id) - - def torrent_queue_changed_signal(self): - log.debug("torrent_queue_changed signal received..") - # Force an update of the torrent view - self.ui.main_window.torrentview.update() + self.ui.mainwindow.torrentview.remove_row(torrent_id) def torrent_paused(self, torrent_id): log.debug("torrent_paused signal received..") - self.ui.main_window.torrentview.update() + self.ui.mainwindow.torrentview.update() diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 52beff1d8..a44b6645c 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -39,208 +39,68 @@ import gtk, gtk.glade import gobject import gettext -import columns import deluge.ui.functions as functions +import listview # Get the logger log = logging.getLogger("deluge") -# Initializes the columns for the torrent_view -(TORRENT_VIEW_COL_UID, -#TORRENT_VIEW_COL_QUEUE, -TORRENT_VIEW_COL_STATUSICON, -TORRENT_VIEW_COL_NAME, -TORRENT_VIEW_COL_SIZE, -TORRENT_VIEW_COL_PROGRESS, -TORRENT_VIEW_COL_STATUS, -TORRENT_VIEW_COL_CONNECTED_SEEDS, -TORRENT_VIEW_COL_SEEDS, -TORRENT_VIEW_COL_CONNECTED_PEERS, -TORRENT_VIEW_COL_PEERS, -TORRENT_VIEW_COL_DOWNLOAD, -TORRENT_VIEW_COL_UPLOAD, -TORRENT_VIEW_COL_ETA, -TORRENT_VIEW_COL_RATIO) = range(14) - class TorrentView: def __init__(self, window): log.debug("TorrentView Init..") self.window = window self.core = functions.get_core() - # Get the torrent_view widget - self.torrent_view = self.window.main_glade.get_widget("torrent_view") - - ## TreeModel setup ## - # UID, Status Icon, Name, Size, Progress, Message, Seeders, Peers, - # DL, UL, ETA, Share - self.torrent_model = gtk.ListStore(str, gtk.gdk.Pixbuf, str, - long, float, str, int, int, int, int, int, int, int, float) - - ## TreeView setup ## - self.torrent_view.set_model(self.torrent_model) - self.torrent_view.set_rules_hint(True) - self.torrent_view.set_reorderable(True) - self.torrent_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - # self.queue_column = columns.add_text_column( - # self.torrent_view, "#", - # TORRENT_VIEW_COL_QUEUE) - self.name_column = columns.add_texticon_column( - self.torrent_view, - _("Name"), - TORRENT_VIEW_COL_STATUSICON, - TORRENT_VIEW_COL_NAME) - self.size_column = columns.add_func_column( - self.torrent_view, - _("Size"), - columns.cell_data_size, - TORRENT_VIEW_COL_SIZE) - self.progress_column = columns.add_progress_column( - self.torrent_view, - _("Progress"), - TORRENT_VIEW_COL_PROGRESS, - TORRENT_VIEW_COL_STATUS) - self.seed_column = columns.add_func_column( - self.torrent_view, - _("Seeders"), - columns.cell_data_peer, - (TORRENT_VIEW_COL_CONNECTED_SEEDS, TORRENT_VIEW_COL_SEEDS)) - self.peer_column = columns.add_func_column( - self.torrent_view, - _("Peers"), - columns.cell_data_peer, - (TORRENT_VIEW_COL_CONNECTED_PEERS, TORRENT_VIEW_COL_PEERS)) - self.dl_column = columns.add_func_column( - self.torrent_view, - _("Down Speed"), - columns.cell_data_speed, - TORRENT_VIEW_COL_DOWNLOAD) - self.ul_column = columns.add_func_column( - self.torrent_view, - _("Up Speed"), - columns.cell_data_speed, - TORRENT_VIEW_COL_UPLOAD) - self.eta_column = columns.add_func_column( - self.torrent_view, - _("ETA"), - columns.cell_data_time, - TORRENT_VIEW_COL_ETA) - self.share_column = columns.add_func_column( - self.torrent_view, - _("Ratio"), - columns.cell_data_ratio, - TORRENT_VIEW_COL_RATIO) - - # Set some column settings - self.progress_column.set_expand(True) - self.name_column.set_sort_column_id(TORRENT_VIEW_COL_NAME) - self.seed_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_SEEDS) - self.peer_column.set_sort_column_id(TORRENT_VIEW_COL_CONNECTED_PEERS) - - # Set the default sort column to the queue column - # self.torrent_model.set_sort_column_id(TORRENT_VIEW_COL_QUEUE, - # gtk.SORT_ASCENDING) - + # Create a ListView object using the torrent_view from the mainwindow + self.torrent_view = listview.ListView( + self.window.main_glade.get_widget("torrent_view")) + + self.torrent_view.add_text_column("torrent_id", visible=False) + self.torrent_view.add_texticon_column("Name") + self.torrent_view.add_func_column("Size", + listview.cell_data_size, + [long]) + self.torrent_view.add_progress_column("Progress") + self.torrent_view.add_func_column("Seeders", + listview.cell_data_peer, + [int, int]) + self.torrent_view.add_func_column("Peers", + listview.cell_data_peer, + [int, int]) + self.torrent_view.add_func_column("Down Speed", + listview.cell_data_speed, + [int]) + self.torrent_view.add_func_column("Up Speed", + listview.cell_data_speed, + [int]) + self.torrent_view.add_func_column("ETA", + listview.cell_data_time, + [int]) + self.torrent_view.add_func_column("Ratio", + listview.cell_data_ratio, + [float]) + ### Connect Signals ### # Connect to the 'button-press-event' to know when to bring up the # torrent menu popup. - self.torrent_view.connect("button-press-event", + self.torrent_view.treeview.connect("button-press-event", self.on_button_press_event) # Connect to the 'changed' event of TreeViewSelection to get selection # changes. - self.torrent_view.get_selection().connect("changed", + self.torrent_view.treeview.get_selection().connect("changed", self.on_selection_changed) def update(self): - """Update the view, this is likely called by a timer""" - # This function is used for the foreach method of the treemodel - def update_row(model, path, row, user_data): - torrent_id = self.torrent_model.get_value(row, 0) - status_keys = ["progress", "state", "num_seeds", - "num_peers", "download_payload_rate", "upload_payload_rate", - "eta"] - status = functions.get_torrent_status(self.core, torrent_id, - status_keys) - - # Set values for each column in the row - # self.torrent_model.set_value(row, TORRENT_VIEW_COL_QUEUE, - # status["queue"]+1) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_PROGRESS, - status["progress"]*100) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_STATUS, - status["state"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_SEEDS, - status["num_seeds"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_SEEDS, - status["num_seeds"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_CONNECTED_PEERS, - status["num_peers"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_PEERS, - status["num_peers"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_DOWNLOAD, - status["download_payload_rate"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_UPLOAD, - status["upload_payload_rate"]) - self.torrent_model.set_value(row, TORRENT_VIEW_COL_ETA, - status["eta"]) - - # Iterates through every row and updates them accordingly - self.torrent_model.foreach(update_row, None) + pass def add_row(self, torrent_id): - """Adds a new torrent row to the treeview""" - # Get the status and info dictionaries - status_keys = ["name", "total_size", "progress", "state", - "num_seeds", "num_peers", "download_payload_rate", - "upload_payload_rate", "eta"] - status = functions.get_torrent_status(self.core, torrent_id, - status_keys) - # Insert the row with info provided from core - #self.torrent_model.insert(status["queue"], [ - self.torrent_model.append([ - torrent_id, - # status["queue"]+1, - None, - status["name"], - status["total_size"], - status["progress"]*100, - status["state"], - status["num_seeds"], - status["num_seeds"], - status["num_peers"], - status["num_peers"], - status["download_payload_rate"], - status["upload_payload_rate"], - status["eta"], - 0.0 - ]) + pass def remove_row(self, torrent_id): - """Removes a row with torrent_id""" - row = self.torrent_model.get_iter_first() - while row is not None: - # Check if this row is the row we want to remove - if self.torrent_model.get_value(row, 0) == torrent_id: - self.torrent_model.remove(row) - # Force an update of the torrentview - self.update() - break - row = self.torrent_model.iter_next(row) - + pass + def get_selected_torrents(self): - """Returns a list of selected torrents or None""" - torrent_ids = [] - paths = self.torrent_view.get_selection().get_selected_rows()[1] - - try: - for path in paths: - torrent_ids.append( - self.torrent_model.get_value( - self.torrent_model.get_iter(path), 0)) - return torrent_ids - except ValueError: - return None - + pass + ### Callbacks ### def on_button_press_event(self, widget, event): log.debug("on_button_press_event")