diff --git a/deluge/common.py b/deluge/common.py index 8c655ec07..3d75f9da1 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -258,6 +258,17 @@ def open_url_in_browser(url): ## Formatting text functions +byte_txt = "Bytes" +kib_txt = "KiB" +mib_txt = "MiB" +gib_txt = "GiB" + +def translate_strings(): + byte_txt = _("Bytes") + kib_txt = _("KiB") + mib_txt = _("MiB") + gib_txt = _("GiB") + def fsize(fsize_b): """ Formats the bytes value into a string with KiB, MiB or GiB units @@ -273,14 +284,17 @@ def fsize(fsize_b): '109.6 KiB' """ - fsize_kb = fsize_b / 1024.0 - if fsize_kb < 1024: - return "%.1f %s" % (fsize_kb, _("KiB")) - fsize_mb = fsize_kb / 1024.0 - if fsize_mb < 1024: - return "%.1f %s" % (fsize_mb, _("MiB")) - fsize_gb = fsize_mb / 1024.0 - return "%.1f %s" % (fsize_gb, _("GiB")) + # Bigger than 1 GiB + if (fsize_b >= 1073741824): + return "%.1f %s" % (fsize_b / 1073741824.0, gib_txt) + # Bigger than 1 MiB + elif (fsize_b >= 1048576): + return "%.1f %s" % (fsize_b / 1048576.0, mib_txt) + # Bigger than 1 KiB + elif (fsize_b >= 1024): + return "%.1f %s" % (fsize_b / 1024.0, kib_txt) + else: + return "%d %s" % (fsize_b, byte_txt) def fsize_short(fsize_b): """ @@ -797,6 +811,7 @@ def setup_translations(setup_pygtk=False): import gtk.glade gtk.glade.bindtextdomain("deluge", translations_path) gtk.glade.textdomain("deluge") + translate_strings() except Exception, e: log.error("Unable to initialize gettext/locale!") log.exception(e) diff --git a/deluge/tests/test_common.py b/deluge/tests/test_common.py index 81c633258..c693c4b98 100644 --- a/deluge/tests/test_common.py +++ b/deluge/tests/test_common.py @@ -12,7 +12,15 @@ class CommonTestCase(unittest.TestCase): pass def test_fsize(self): - self.failUnless(fsize(112245) == "109.6 KiB") + self.assertEquals(fsize(100), "100 Bytes") + self.assertEquals(fsize(1023), "1023 Bytes") + self.assertEquals(fsize(1024), "1.0 KiB") + self.assertEquals(fsize(1048575), "1024.0 KiB") + self.assertEquals(fsize(1048576), "1.0 MiB") + self.assertEquals(fsize(1073741823), "1024.0 MiB") + self.assertEquals(fsize(1073741824), "1.0 GiB") + self.assertEquals(fsize(112245), "109.6 KiB") + self.assertEquals(fsize(110723441824), "103.1 GiB") def test_fpcnt(self): self.failUnless(fpcnt(0.9311) == "93.11%") diff --git a/deluge/tests/test_tracker_icons.py b/deluge/tests/test_tracker_icons.py index 4bac31714..cc9492fcd 100644 --- a/deluge/tests/test_tracker_icons.py +++ b/deluge/tests/test_tracker_icons.py @@ -17,7 +17,7 @@ class TrackerIconsTestCase(unittest.TestCase): def test_get_deluge_png(self): # Deluge has a png favicon link icon = TrackerIcon(os.path.join(dirname, "deluge.png")) - d = icons.get("deluge-torrent.org") + d = icons.fetch("deluge-torrent.org") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d @@ -26,7 +26,7 @@ class TrackerIconsTestCase(unittest.TestCase): # Google doesn't have any icon links # So instead we'll grab its favicon.ico icon = TrackerIcon(os.path.join(dirname, "google.ico")) - d = icons.get("www.google.com") + d = icons.fetch("www.google.com") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d @@ -34,7 +34,7 @@ class TrackerIconsTestCase(unittest.TestCase): def test_get_google_ico_with_redirect(self): # google.com redirects to www.google.com icon = TrackerIcon(os.path.join(dirname, "google.ico")) - d = icons.get("google.com") + d = icons.fetch("google.com") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d @@ -42,7 +42,7 @@ class TrackerIconsTestCase(unittest.TestCase): def test_get_ubuntu_ico(self): # ubuntu.com has inline css which causes HTMLParser issues icon = TrackerIcon(os.path.join(dirname, "ubuntu.ico")) - d = icons.get("www.ubuntu.com") + d = icons.fetch("www.ubuntu.com") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d @@ -50,19 +50,19 @@ class TrackerIconsTestCase(unittest.TestCase): def test_get_openbt_png(self): # openbittorrent.com has an incorrect type (image/gif) icon = TrackerIcon(os.path.join(dirname, "openbt.png")) - d = icons.get("openbittorrent.com") + d = icons.fetch("openbittorrent.com") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d def test_get_publicbt_ico(self): icon = TrackerIcon(os.path.join(dirname, "publicbt.ico")) - d = icons.get("publicbt.org") + d = icons.fetch("publicbt.org") d.addCallback(self.assertNotIdentical, None) d.addCallback(self.assertEquals, icon) return d def test_get_empty_string_tracker(self): - d = icons.get("") + d = icons.fetch("") d.addCallback(self.assertIdentical, None) return d diff --git a/deluge/ui/gtkui/addtorrentdialog.py b/deluge/ui/gtkui/addtorrentdialog.py index dd8663233..38068fe0d 100644 --- a/deluge/ui/gtkui/addtorrentdialog.py +++ b/deluge/ui/gtkui/addtorrentdialog.py @@ -48,7 +48,7 @@ import twisted.web.error from deluge.ui.client import client from deluge.httpdownloader import download_file import deluge.component as component -import listview +from torrentview_data_funcs import cell_data_size from deluge.configmanager import ConfigManager import deluge.common import deluge.ui.common @@ -136,7 +136,7 @@ class AddTorrentDialog(component.Component): render = gtk.CellRendererText() column = gtk.TreeViewColumn(_("Size")) column.pack_start(render) - column.set_cell_data_func(render, listview.cell_data_size, 2) + column.set_cell_data_func(render, cell_data_size, 2) self.listview_files.append_column(column) self.listview_torrents.set_model(self.torrent_liststore) diff --git a/deluge/ui/gtkui/files_tab.py b/deluge/ui/gtkui/files_tab.py index b248122f3..10dbd4ad7 100644 --- a/deluge/ui/gtkui/files_tab.py +++ b/deluge/ui/gtkui/files_tab.py @@ -142,7 +142,7 @@ class FilesTab(Tab): column = gtk.TreeViewColumn(_("Size")) render = gtk.CellRendererText() column.pack_start(render, False) - column.set_cell_data_func(render, deluge.ui.gtkui.listview.cell_data_size, 1) + column.set_cell_data_func(render, deluge.ui.gtkui.torrentview_data_funcs.cell_data_size, 1) column.set_sort_column_id(1) column.set_clickable(True) column.set_resizable(True) diff --git a/deluge/ui/gtkui/filtertreeview.py b/deluge/ui/gtkui/filtertreeview.py index 239e16961..caa5be403 100644 --- a/deluge/ui/gtkui/filtertreeview.py +++ b/deluge/ui/gtkui/filtertreeview.py @@ -228,7 +228,7 @@ class FilterTreeView(component.Component): self.filters[(cat, value)] = row if cat == "tracker_host" and value not in ("All", "Error") and value: - d = self.tracker_icons.get(value) + d = self.tracker_icons.fetch(value) d.addCallback(on_get_icon) self.treestore.set_value(row, FILTER_COLUMN, True) diff --git a/deluge/ui/gtkui/listview.py b/deluge/ui/gtkui/listview.py index 07f45d1a6..74fbd2789 100644 --- a/deluge/ui/gtkui/listview.py +++ b/deluge/ui/gtkui/listview.py @@ -49,71 +49,6 @@ signal_new('button-press-event', gtk.TreeViewColumn, log = logging.getLogger(__name__) -# Cell data functions to pass to add_func_column() -def cell_data_speed(column, cell, model, row, data): - """Display value as a speed, eg. 2 KiB/s""" - speed = model.get_value(row, data) - speed_str = "" - if speed > 0: - speed_str = deluge.common.fspeed(speed) - - cell.set_property('text', speed_str) - -def cell_data_size(column, cell, model, row, data): - """Display value in terms of size, eg. 2 MB""" - size = model.get_value(row, data) - size_str = deluge.common.fsize(size) - cell.set_property('text', size_str) - -def cell_data_peer(column, cell, model, row, data): - """Display values as 'value1 (value2)'""" - (first, second) = model.get(row, *data) - # Only display a (total) if second is greater than -1 - if second > -1: - cell.set_property('text', '%d (%d)' % (first, second)) - else: - cell.set_property('text', '%d' % first) - -def cell_data_time(column, cell, model, row, data): - """Display value as time, eg 1m10s""" - time = model.get_value(row, data) - if time <= 0: - time_str = "" - else: - time_str = deluge.common.ftime(time) - cell.set_property('text', time_str) - -def cell_data_ratio(column, cell, model, row, data): - """Display value as a ratio with a precision of 3.""" - ratio = model.get_value(row, data) - if ratio < 0: - ratio_str = "∞" - else: - ratio_str = "%.3f" % ratio - - cell.set_property('text', ratio_str) - -def cell_data_date(column, cell, model, row, data): - """Display value as date, eg 05/05/08""" - cell.set_property('text', deluge.common.fdate(model.get_value(row, data))) - -def cell_data_date_or_never(column, cell, model, row, data): - """Display value as date, eg 05/05/08 or Never""" - value = model.get_value(row, data) - if value > 0.0: - cell.set_property('text', deluge.common.fdate(value)) - else: - cell.set_property('text', _("Never")) - -def cell_data_speed_limit(column, cell, model, row, data): - """Display value as a speed, eg. 2 KiB/s""" - speed = model.get_value(row, data) - speed_str = "" - if speed > 0: - speed_str = deluge.common.fspeed(speed * 1024) - - cell.set_property('text', speed_str) - class ListViewColumnState: """Used for saving/loading column state""" def __init__(self, name, position, width, visible, sort, sort_order): @@ -150,7 +85,7 @@ class ListView: self.sort_func = None self.sort_id = None - class TreeviewColumn(gtk.TreeViewColumn): + class TreeviewColumn(gtk.TreeViewColumn, object): """ TreeViewColumn does not signal right-click events, and we need them This subclass is equivalent to TreeViewColumn, but it signals these events @@ -165,6 +100,10 @@ class ListView: self.set_widget(label) label.show() label.__realize = label.connect('realize', self.onRealize) + self.title = title + self.data_func = None + self.data_func_data = None + self.cell_renderer = None def onRealize(self, widget): widget.disconnect(widget.__realize) @@ -176,6 +115,21 @@ class ListView: def onButtonPressed(self, widget, event): self.emit('button-press-event', event) + def set_cell_data_func_attributes(self, cell_renderer, func, func_data=None): + """Store the values to be set by set_cell_data_func""" + self.data_func = func + self.data_func_data = func_data + self.cell_renderer = cell_renderer + + def set_visible(self, visible): + gtk.TreeViewColumn.set_visible(self, visible) + if self.data_func: + if not visible: + # Set data function to None to prevent unecessary calls when column is hidden + self.set_cell_data_func(self.cell_renderer, None, func_data=None) + else: + self.set_cell_data_func(self.cell_renderer, self.data_func, self.data_func_data) + def __init__(self, treeview_widget=None, state_file=None): log.debug("ListView initialized..") @@ -521,10 +475,10 @@ class ListView: elif column_type == "func": column.pack_start(render, True) if len(self.columns[header].column_indices) > 1: - column.set_cell_data_func(render, function, + column.set_cell_data_func_attributes(render, function, tuple(self.columns[header].column_indices)) else: - column.set_cell_data_func(render, function, + column.set_cell_data_func_attributes(render, function, self.columns[header].column_indices[0]) elif column_type == "progress": column.pack_start(render) @@ -534,12 +488,12 @@ class ListView: column.add_attribute(render, "value", self.columns[header].column_indices[value]) else: - column.set_cell_data_func(render, function, + column.set_cell_data_func_attributes(render, function, tuple(self.columns[header].column_indices)) elif column_type == "texticon": column.pack_start(render[pixbuf], False) if function is not None: - column.set_cell_data_func(render[pixbuf], function, + column.set_cell_data_func_attributes(render[pixbuf], function, self.columns[header].column_indices[pixbuf]) column.pack_start(render[text], True) column.add_attribute(render[text], "text", diff --git a/deluge/ui/gtkui/peers_tab.py b/deluge/ui/gtkui/peers_tab.py index 5efbaa385..e4d6a20a2 100644 --- a/deluge/ui/gtkui/peers_tab.py +++ b/deluge/ui/gtkui/peers_tab.py @@ -41,7 +41,7 @@ from itertools import izip from deluge.ui.client import client import deluge.component as component import deluge.common -from deluge.ui.gtkui.listview import cell_data_speed as cell_data_speed +from deluge.ui.gtkui.torrentview_data_funcs import cell_data_speed_down, cell_data_speed_up from deluge.ui.gtkui.torrentdetails import Tab from deluge.ui.countries import COUNTRIES from deluge.ui.gtkui.common import save_pickled_state_file, load_pickled_state_file @@ -139,7 +139,7 @@ class PeersTab(Tab): column = gtk.TreeViewColumn(_("Down Speed")) render = gtk.CellRendererText() column.pack_start(render, False) - column.set_cell_data_func(render, cell_data_speed, 3) + 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) @@ -152,7 +152,7 @@ class PeersTab(Tab): column = gtk.TreeViewColumn(_("Up Speed")) render = gtk.CellRendererText() column.pack_start(render, False) - column.set_cell_data_func(render, cell_data_speed, 4) + 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) diff --git a/deluge/ui/gtkui/torrentview.py b/deluge/ui/gtkui/torrentview.py index 6cfc80a59..eb8decabd 100644 --- a/deluge/ui/gtkui/torrentview.py +++ b/deluge/ui/gtkui/torrentview.py @@ -50,101 +50,10 @@ import deluge.common import deluge.component as component from deluge.ui.client import client from removetorrentdialog import RemoveTorrentDialog +import torrentview_data_funcs as funcs log = logging.getLogger(__name__) -# Status icons.. Create them from file only once to avoid constantly -# re-creating them. -icon_downloading = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("downloading16.png")) -icon_seeding = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("seeding16.png")) -icon_inactive = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("inactive16.png")) -icon_alert = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("alert16.png")) -icon_queued = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("queued16.png")) -icon_checking = gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("checking16.png")) - -# Holds the info for which status icon to display based on state -ICON_STATE = { - "Allocating": icon_checking, - "Checking": icon_checking, - "Downloading": icon_downloading, - "Seeding": icon_seeding, - "Paused": icon_inactive, - "Error": icon_alert, - "Queued": icon_queued, - "Checking Resume Data": icon_checking -} - - -def cell_data_statusicon(column, cell, model, row, data): - """Display text with an icon""" - try: - icon = ICON_STATE[model.get_value(row, data)] - - #Supress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed - original_filters = warnings.filters[:] - warnings.simplefilter("ignore") - try: - if cell.get_property("pixbuf") != icon: - cell.set_property("pixbuf", icon) - finally: - warnings.filters = original_filters - - except KeyError: - pass - -def cell_data_trackericon(column, cell, model, row, data): - def on_get_icon(icon): - def create_blank_pixbuf(): - i = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 16, 16) - i.fill(0x00000000) - return i - - if icon: - pixbuf = icon.get_cached_icon() - if not pixbuf: - try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon.get_filename(), 16, 16) - except gobject.GError, e: - # Failed to load the pixbuf (Bad image file), so set a blank pixbuf - pixbuf = create_blank_pixbuf() - finally: - icon.set_cached_icon(pixbuf) - else: - pixbuf = create_blank_pixbuf() - - #Suppress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - if cell.get_property("pixbuf") != pixbuf: - cell.set_property("pixbuf", pixbuf) - - host = model[row][data] - if host: - d = component.get("TrackerIcons").get(host) - d.addCallback(on_get_icon) - else: - on_get_icon(None) - -def cell_data_progress(column, cell, model, row, data): - """Display progress bar with text""" - (value, state_str) = model.get(row, *data) - if cell.get_property("value") != value: - cell.set_property("value", value) - - # Marked for translate states text are in filtertreeview - textstr = _(state_str) - if state_str != "Seeding" and value < 100: - textstr = "%s %.2f%%" % (textstr, value) - if cell.get_property("text") != textstr: - cell.set_property("text", textstr) - -def cell_data_queue(column, cell, model, row, data): - value = model.get_value(row, data) - if value < 0: - cell.set_property("text", "") - else: - cell.set_property("text", str(value + 1)) - def queue_peer_seed_sort_function(v1, v2): if v1 == v2: return 0 @@ -339,58 +248,58 @@ class TorrentView(listview.ListView, component.Component): # Add the columns to the listview self.add_text_column("torrent_id", hidden=True, unique=True) self.add_bool_column("dirty", hidden=True) - self.add_func_column("#", cell_data_queue, [int], + self.add_func_column("#", funcs.cell_data_queue, [int], status_field=["queue"], sort_func=queue_column_sort) self.add_texticon_column(_("Name"), status_field=["state", "name"], - function=cell_data_statusicon, + function=funcs.cell_data_statusicon, default_sort=True) - self.add_func_column(_("Size"), listview.cell_data_size, + self.add_func_column(_("Size"), funcs.cell_data_size, [gobject.TYPE_UINT64], status_field=["total_wanted"]) - self.add_func_column(_("Downloaded"), listview.cell_data_size, + self.add_func_column(_("Downloaded"), funcs.cell_data_size, [gobject.TYPE_UINT64], status_field=["all_time_download"], default=False) - self.add_func_column(_("Uploaded"), listview.cell_data_size, + self.add_func_column(_("Uploaded"), funcs.cell_data_size, [gobject.TYPE_UINT64], status_field=["total_uploaded"], default=False) - self.add_func_column(_("Remaining"), listview.cell_data_size, [gobject.TYPE_UINT64], + self.add_func_column(_("Remaining"), funcs.cell_data_size, [gobject.TYPE_UINT64], status_field=["total_remaining"], default=False) self.add_progress_column(_("Progress"), - status_field=["progress", "state"], - col_types=[float, str], - function=cell_data_progress) - self.add_func_column(_("Seeders"), listview.cell_data_peer, [int, int], + status_field=["progress", "state"], + col_types=[float, str], + function=funcs.cell_data_progress) + self.add_func_column(_("Seeders"), funcs.cell_data_peer, [int, int], status_field=["num_seeds", "total_seeds"], sort_func=seed_peer_column_sort, default=False) - self.add_func_column(_("Peers"), listview.cell_data_peer, [int, int], + self.add_func_column(_("Peers"), funcs.cell_data_peer, [int, int], status_field=["num_peers", "total_peers"], sort_func=seed_peer_column_sort, default=False) - self.add_func_column(_("Seeders") + "/" + _("Peers"), listview.cell_data_ratio, [float], + self.add_func_column(_("Seeders") + "/" + _("Peers"), funcs.cell_data_ratio_seeders, [float], status_field=["seeds_peers_ratio"], default=False) - self.add_func_column(_("Down Speed"), listview.cell_data_speed, [float], + self.add_func_column(_("Down Speed"), funcs.cell_data_speed_down, [float], status_field=["download_payload_rate"]) - self.add_func_column(_("Up Speed"), listview.cell_data_speed, [float], + self.add_func_column(_("Up Speed"), funcs.cell_data_speed_up, [float], status_field=["upload_payload_rate"]) - self.add_func_column(_("Down Limit"), listview.cell_data_speed_limit, [float], + self.add_func_column(_("Down Limit"), funcs.cell_data_speed_limit_down, [float], status_field=["max_download_speed"], default=False) - self.add_func_column(_("Up Limit"), listview.cell_data_speed_limit, [float], + self.add_func_column(_("Up Limit"), funcs.cell_data_speed_limit_up, [float], status_field=["max_upload_speed"], default=False) - self.add_func_column(_("ETA"), listview.cell_data_time, [int], + self.add_func_column(_("ETA"), funcs.cell_data_time, [int], status_field=["eta"], sort_func=eta_column_sort) - self.add_func_column(_("Ratio"), listview.cell_data_ratio, [float], + self.add_func_column(_("Ratio"), funcs.cell_data_ratio_ratio, [float], status_field=["ratio"], default=False) - self.add_func_column(_("Avail"), listview.cell_data_ratio, [float], + self.add_func_column(_("Avail"), funcs.cell_data_ratio_avail, [float], status_field=["distributed_copies"], default=False) - self.add_func_column(_("Added"), listview.cell_data_date, [float], + self.add_func_column(_("Added"), funcs.cell_data_date, [float], status_field=["time_added"], default=False) self.add_func_column(_("Last Seen Complete"), - listview.cell_data_date_or_never, [float], + funcs.cell_data_date_or_never, [float], status_field=["last_seen_complete"], default=False) self.add_texticon_column(_("Tracker"), status_field=["tracker_host", "tracker_host"], - function=cell_data_trackericon, default=False) + function=funcs.cell_data_trackericon, default=False) self.add_text_column(_("Save Path"), status_field=["save_path"], default=False) self.add_text_column(_("Owner"), status_field=["owner"], default=False) self.add_bool_column(_("Public"), status_field=["public"], default=False) diff --git a/deluge/ui/gtkui/torrentview_data_funcs.py b/deluge/ui/gtkui/torrentview_data_funcs.py new file mode 100644 index 000000000..edba5b6ae --- /dev/null +++ b/deluge/ui/gtkui/torrentview_data_funcs.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +# torrentview_data_funcs.py +# +# Copyright (C) 2007, 2008 Andrew Resch +# +# 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 3 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 deluge.common as common +import gtk +import warnings +import gobject +import deluge.component as component + +# Status icons.. Create them from file only once to avoid constantly +# re-creating them. +icon_downloading = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("downloading16.png")) +icon_seeding = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("seeding16.png")) +icon_inactive = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("inactive16.png")) +icon_alert = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("alert16.png")) +icon_queued = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("queued16.png")) +icon_checking = gtk.gdk.pixbuf_new_from_file(common.get_pixmap("checking16.png")) + +# Holds the info for which status icon to display based on state +ICON_STATE = { + "Allocating": icon_checking, + "Checking": icon_checking, + "Downloading": icon_downloading, + "Seeding": icon_seeding, + "Paused": icon_inactive, + "Error": icon_alert, + "Queued": icon_queued, + "Checking Resume Data": icon_checking +} + +def _(message): return message + +TRANSLATE = { + "Downloading": _("Downloading"), + "Seeding": _("Seeding"), + "Paused": _("Paused"), + "Checking": _("Checking"), + "Queued": _("Queued"), + "Error": _("Error"), +} + +del _ + +def _t(text): + if text in TRANSLATE: + text = TRANSLATE[text] + return _(text) + +# Cache the key used to calculate the current value set for the specific cell +# renderer. This is much cheaper than fetch the current value and test if +# it's equal. +func_last_value = {"cell_data_speed_down": None, + "cell_data_speed_up": None, + "cell_data_time": None, + "cell_data_ratio_seeders": None, + "cell_data_ratio_ratio": None, + "cell_data_ratio_avail": None, + "cell_data_date": None, + "cell_data_date_or_never": None, + "cell_data_speed_limit_down": None, + "cell_data_speed_limit_up": None, + "cell_data_trackericon": None, + "cell_data_statusicon": None, + "cell_data_queue": None, + "cell_data_progress": [None, None], + } + +def cell_data_statusicon(column, cell, model, row, data): + """Display text with an icon""" + try: + state = model.get_value(row, data) + + if func_last_value["cell_data_statusicon"] == state: + return + func_last_value["cell_data_statusicon"] = state + + icon = ICON_STATE[state] + + #Supress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed + original_filters = warnings.filters[:] + warnings.simplefilter("ignore") + try: + cell.set_property("pixbuf", icon) + finally: + warnings.filters = original_filters + + except KeyError: + pass + +def create_blank_pixbuf(): + i = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 16, 16) + i.fill(0x00000000) + return i + +def set_icon(icon, cell): + if icon: + pixbuf = icon.get_cached_icon() + if pixbuf is None: + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon.get_filename(), 16, 16) + except gobject.GError, e: + # Failed to load the pixbuf (Bad image file), so set a blank pixbuf + pixbuf = create_blank_pixbuf() + finally: + icon.set_cached_icon(pixbuf) + else: + pixbuf = create_blank_pixbuf() + + #Suppress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + cell.set_property("pixbuf", pixbuf) + +def cell_data_trackericon(column, cell, model, row, data): + host = model[row][data] + + if func_last_value["cell_data_trackericon"] == host: + return + if host: + if not component.get("TrackerIcons").has(host): + # Set blank icon while waiting for the icon to be loaded + set_icon(None, cell) + component.get("TrackerIcons").fetch(host) + func_last_value["cell_data_trackericon"] = None + else: + set_icon(component.get("TrackerIcons").get(host), cell) + # Only set the last value when we have found the icon + func_last_value["cell_data_trackericon"] = host + else: + set_icon(None, cell) + func_last_value["cell_data_trackericon"] = None + +def cell_data_progress(column, cell, model, row, data): + """Display progress bar with text""" + (value, state_str) = model.get(row, *data) + if func_last_value["cell_data_progress"][0] != value: + func_last_value["cell_data_progress"][0] = value + cell.set_property("value", value) + + textstr = _t(state_str) + if state_str != "Seeding" and value < 100: + textstr = textstr + " %.2f%%" % value + + if func_last_value["cell_data_progress"][1] != textstr: + func_last_value["cell_data_progress"][1] = textstr + cell.set_property("text", textstr) + +def cell_data_queue(column, cell, model, row, data): + value = model.get_value(row, data) + + if func_last_value["cell_data_queue"] == value: + return + func_last_value["cell_data_queue"] = value + + if value < 0: + cell.set_property("text", "") + else: + cell.set_property("text", str(value + 1)) + +def cell_data_speed(cell, model, row, data, cache_key): + """Display value as a speed, eg. 2 KiB/s""" + try: + speed = model.get_value(row, data) + except AttributeError, e: + print "AttributeError" + import traceback + traceback.print_exc() + if func_last_value[cache_key] == speed: + return + func_last_value[cache_key] = speed + + speed_str = "" + if speed > 0: + speed_str = common.fspeed(speed) + cell.set_property('text', speed_str) + +def cell_data_speed_down(column, cell, model, row, data): + """Display value as a speed, eg. 2 KiB/s""" + cell_data_speed(cell, model, row, data, "cell_data_speed_down") + +def cell_data_speed_up(column, cell, model, row, data): + """Display value as a speed, eg. 2 KiB/s""" + cell_data_speed(cell, model, row, data, "cell_data_speed_up") + +def cell_data_speed_limit(cell, model, row, data, cache_key): + """Display value as a speed, eg. 2 KiB/s""" + speed = model.get_value(row, data) + + if func_last_value[cache_key] == speed: + return + func_last_value[cache_key] = speed + + speed_str = "" + if speed > 0: + speed_str = common.fspeed(speed * 1024) + cell.set_property('text', speed_str) + +def cell_data_speed_limit_down(column, cell, model, row, data): + cell_data_speed_limit(cell, model, row, data, "cell_data_speed_limit_down") + +def cell_data_speed_limit_up(column, cell, model, row, data): + cell_data_speed_limit(cell, model, row, data, "cell_data_speed_limit_up") + +def cell_data_size(column, cell, model, row, data): + """Display value in terms of size, eg. 2 MB""" + size = model.get_value(row, data) + cell.set_property('text', common.fsize(size)) + +def cell_data_peer(column, cell, model, row, data): + """Display values as 'value1 (value2)'""" + (first, second) = model.get(row, *data) + # Only display a (total) if second is greater than -1 + if second > -1: + cell.set_property('text', '%d (%d)' % (first, second)) + else: + cell.set_property('text', '%d' % first) + +def cell_data_time(column, cell, model, row, data): + """Display value as time, eg 1m10s""" + time = model.get_value(row, data) + if func_last_value["cell_data_time"] == time: + return + func_last_value["cell_data_time"] = time + + if time <= 0: + time_str = "" + else: + time_str = common.ftime(time) + cell.set_property('text', time_str) + +def cell_data_ratio(cell, model, row, data, cache_key): + """Display value as a ratio with a precision of 3.""" + ratio = model.get_value(row, data) + # Previous value in cell is the same as for this value, so ignore + if func_last_value[cache_key] == ratio: + return + func_last_value[cache_key] = ratio + cell.set_property('text', "∞" if ratio < 0 else "%.3f" % ratio) + +def cell_data_ratio_seeders(column, cell, model, row, data): + cell_data_ratio(cell, model, row, data, "cell_data_ratio_seeders") + +def cell_data_ratio_ratio(column, cell, model, row, data): + cell_data_ratio(cell, model, row, data, "cell_data_ratio_ratio") + +def cell_data_ratio_avail(column, cell, model, row, data): + cell_data_ratio(cell, model, row, data, "cell_data_ratio_avail") + +def cell_data_date(column, cell, model, row, data): + """Display value as date, eg 05/05/08""" + date = model.get_value(row, data) + + if func_last_value["cell_data_date"] == date: + return + func_last_value["cell_data_date"] = date + + date_str = common.fdate(date) + cell.set_property('text', date_str) + +def cell_data_date_or_never(column, cell, model, row, data): + """Display value as date, eg 05/05/08 or Never""" + value = model.get_value(row, data) + + if func_last_value["cell_data_date_or_never"] == value: + return + func_last_value["cell_data_date_or_never"] = value + + date_str = common.fdate(value) if value > 0.0 else _("Never") + cell.set_property('text', date_str) + diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py index f46979d5c..0c0f71dac 100644 --- a/deluge/ui/tracker_icons.py +++ b/deluge/ui/tracker_icons.py @@ -178,9 +178,38 @@ class TrackerIcons(Component): self.pending = {} self.redirects = {} + def has(self, host): + """ + Returns True or False if the tracker icon for the given host exists or not. + + :param host: the host for the TrackerIcon + :type host: string + :returns: True or False + :rtype: bool + """ + return host.lower() in self.icons + def get(self, host): """ Returns a TrackerIcon for the given tracker's host + from the icon cache. + + :param host: the host for the TrackerIcon + :type host: string + :returns: the TrackerIcon for the host + :rtype: TrackerIcon + """ + host = host.lower() + if host in self.icons: + return self.icons[host] + else: + return None + + def fetch(self, host): + """ + Fetches (downloads) the icon for the given host. + When the icon is downloaded a callback is fired + on the the queue of callers to this function. :param host: the host to obtain the TrackerIcon for :type host: string