[GTKUI] Reorganise layout of tab items and add Tracker tab

* Changed layout of Status, Details and Options tabs.
 * Moved the Tracker translations to ui.common.
 * Created a new Trackers tab.
 * Added State to progressbar.
 * Translate State in piecesbar.
This commit is contained in:
Calum Lind 2014-08-22 14:00:40 +01:00
commit 6496383e82
8 changed files with 1462 additions and 1424 deletions

View file

@ -39,10 +39,7 @@ all the interfaces.
""" """
import os import os
import sys
import logging import logging
import urlparse
import locale
from hashlib import sha1 as sha from hashlib import sha1 as sha
from deluge import bencode from deluge import bencode
@ -51,10 +48,12 @@ import deluge.configmanager
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Dummy tranlation dict so Torrent states text is available for Translators # Dummy tranlation dict so Torrent states text is available for Translators
# All entries in deluge.common.TORRENT_STATE should be here. It does not need importing # All entries in deluge.common.TORRENT_STATE should be here. It does not need importing
# as the string matches the translation text so using the _() function is enough. # as the string matches the translation text so using the _() function is enough.
def _(message): return message def _(message):
return message
STATE_TRANSLATION = { STATE_TRANSLATION = {
"All": _("All"), "All": _("All"),
"Active": _("Active"), "Active": _("Active"),
@ -67,8 +66,16 @@ STATE_TRANSLATION = {
"Queued": _("Queued"), "Queued": _("Queued"),
"Error": _("Error"), "Error": _("Error"),
} }
TRACKER_STATUS_TRANSLATION = {
"Error": _("Error"),
"Warning": _("Warning"),
"Announce OK": _("Announce OK"),
"Announce Sent": _("Announce Sent")
}
del _ del _
class TorrentInfo(object): class TorrentInfo(object):
""" """
Collects information about a torrent file. Collects information about a torrent file.

View file

@ -21,11 +21,11 @@ def fpeer_size_second(first, second):
def fdate_blank(value): def fdate_blank(value):
"""Display value as date, eg 05/05/08 or blank""" """Display value as date, eg 05/05/08 or dash"""
if value > 0.0: if value > 0.0:
return fdate(value) return fdate(value)
else: else:
return "" return "-"
def str_yes_no(value): def str_yes_no(value):
@ -59,25 +59,22 @@ class DetailsTab(Tab):
(builder.get_object("summary_pieces"), fpeer_size_second, ("num_pieces", "piece_length")), (builder.get_object("summary_pieces"), fpeer_size_second, ("num_pieces", "piece_length")),
] ]
self.status_keys = [status for widget in self.label_widgets for status in widget[2]]
def update(self): def update(self):
# Get the first selected torrent # Get the first selected torrent
selected = component.get("TorrentView").get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected # Only use the first torrent in the list or return if None selected
if len(selected) != 0: if selected:
selected = selected[0] selected = selected[0]
else: else:
# No torrent is selected in the torrentview # No torrent is selected in the torrentview
self.clear() self.clear()
return return
# Get the torrent status
status_keys = ["name", "total_size", "num_files", "time_added", "completed_time",
"download_location", "hash", "comment", "owner", "num_pieces", "piece_length",
"shared", "private"]
session = component.get("SessionProxy") session = component.get("SessionProxy")
session.get_torrent_status(selected, status_keys).addCallback(self._on_get_torrent_status) session.get_torrent_status(selected, self.status_keys).addCallback(self._on_get_torrent_status)
def _on_get_torrent_status(self, status): def _on_get_torrent_status(self, status):
# Check to see if we got valid data from the core # Check to see if we got valid data from the core
@ -87,14 +84,11 @@ class DetailsTab(Tab):
# Update all the label widgets # Update all the label widgets
for widget in self.label_widgets: for widget in self.label_widgets:
if widget[1] is not None: if widget[1] is not None:
args = []
try: try:
for key in widget[2]: args = [status[key] for key in widget[2]]
args.append(status[key]) except KeyError, ex:
except Exception, e: log.debug("Unable to get status value: %s", ex)
log.debug("Unable to get status value: %s", e)
continue continue
txt = widget[1](*args) txt = widget[1](*args)
else: else:
txt = status[widget[2][0]] txt = status[widget[2][0]]

File diff suppressed because it is too large Load diff

View file

@ -77,7 +77,6 @@ class OptionsTab(Tab):
component.get("MainWindow").connect_signals({ component.get("MainWindow").connect_signals({
"on_button_apply_clicked": self._on_button_apply_clicked, "on_button_apply_clicked": self._on_button_apply_clicked,
"on_button_edit_trackers_clicked": self._on_button_edit_trackers_clicked,
"on_chk_move_completed_toggled": self._on_chk_move_completed_toggled, "on_chk_move_completed_toggled": self._on_chk_move_completed_toggled,
"on_chk_stop_at_ratio_toggled": self._on_chk_stop_at_ratio_toggled, "on_chk_stop_at_ratio_toggled": self._on_chk_stop_at_ratio_toggled,
"on_chk_toggled": self._on_chk_toggled, "on_chk_toggled": self._on_chk_toggled,
@ -102,7 +101,7 @@ class OptionsTab(Tab):
torrent_id = component.get("TorrentView").get_selected_torrents() torrent_id = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected # Only use the first torrent in the list or return if None selected
if len(torrent_id) != 0: if torrent_id:
torrent_id = torrent_id[0] torrent_id = torrent_id[0]
self._child_widget.set_sensitive(True) self._child_widget.set_sensitive(True)
else: else:
@ -250,17 +249,6 @@ class OptionsTab(Tab):
) )
self.button_apply.set_sensitive(False) self.button_apply.set_sensitive(False)
def _on_button_edit_trackers_clicked(self, button):
from edittrackersdialog import EditTrackersDialog
dialog = EditTrackersDialog(
self.prev_torrent_id,
component.get("MainWindow").window)
def on_response(result):
if result:
self.button_apply.set_sensitive(True)
dialog.run().addCallback(on_response)
def _on_chk_move_completed_toggled(self, widget): def _on_chk_move_completed_toggled(self, widget):
value = self.chk_move_completed.get_active() value = self.chk_move_completed.get_active()
self.move_completed_path_chooser.set_sensitive(value) self.move_completed_path_chooser.set_sensitive(value)

View file

@ -52,6 +52,7 @@ COLOR_STATES = {
3: "completed" 3: "completed"
} }
class PiecesBar(gtk.DrawingArea): class PiecesBar(gtk.DrawingArea):
# Draw in response to an expose-event # Draw in response to an expose-event
__gsignals__ = {"expose-event": "override"} __gsignals__ = {"expose-event": "override"}
@ -141,7 +142,7 @@ class PiecesBar(gtk.DrawingArea):
def __draw_pieces(self): def __draw_pieces(self):
if (self.__resized() or self.__pieces != self.__old_pieces or if (self.__resized() or self.__pieces != self.__old_pieces or
self.__pieces_overlay == None): self.__pieces_overlay is None):
# Need to recreate the cache drawing # Need to recreate the cache drawing
self.__pieces_overlay = cairo.ImageSurface( self.__pieces_overlay = cairo.ImageSurface(
cairo.FORMAT_ARGB32, self.__width, self.__height cairo.FORMAT_ARGB32, self.__width, self.__height
@ -194,14 +195,13 @@ class PiecesBar(gtk.DrawingArea):
if not self.__state: if not self.__state:
# Nothing useful to draw, return now! # Nothing useful to draw, return now!
return return
if (self.__resized() or self.__fraction != self.__old_fraction) or \ if (self.__resized() or self.__fraction != self.__old_fraction) or self.__progress_overlay is None:
self.__progress_overlay is None:
# Need to recreate the cache drawing # Need to recreate the cache drawing
self.__progress_overlay = cairo.ImageSurface( self.__progress_overlay = cairo.ImageSurface(
cairo.FORMAT_ARGB32, self.__width, self.__height cairo.FORMAT_ARGB32, self.__width, self.__height
) )
ctx = cairo.Context(self.__progress_overlay) ctx = cairo.Context(self.__progress_overlay)
ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent
ctx.rectangle(0.0, 0.0, self.__width*self.__fraction, self.__height) ctx.rectangle(0.0, 0.0, self.__width*self.__fraction, self.__height)
ctx.fill() ctx.fill()
self.__cr.set_source_surface(self.__progress_overlay) self.__cr.set_source_surface(self.__progress_overlay)
@ -230,7 +230,7 @@ class PiecesBar(gtk.DrawingArea):
text += self.__text text += self.__text
else: else:
if self.__state: if self.__state:
text += self.__state + " " text += _(self.__state) + " "
if self.__fraction == 1.0: if self.__fraction == 1.0:
format = "%d%%" format = "%d%%"
else: else:

View file

@ -55,8 +55,10 @@ def fratio(value):
return "%.3f" % value return "%.3f" % value
def fpcnt(value): def fpcnt(value, state):
return "%.2f%%" % value if state:
state = _(state) + " "
return "%s%.2f%%" % (state, value)
def fspeed(value, max_value=-1): def fspeed(value, max_value=-1):
@ -102,40 +104,32 @@ class StatusTab(Tab):
(builder.get_object("summary_peers"), deluge.common.fpeer, ("num_peers", "total_peers")), (builder.get_object("summary_peers"), deluge.common.fpeer, ("num_peers", "total_peers")),
(builder.get_object("summary_eta"), deluge.common.ftime, ("eta",)), (builder.get_object("summary_eta"), deluge.common.ftime, ("eta",)),
(builder.get_object("summary_share_ratio"), fratio, ("ratio",)), (builder.get_object("summary_share_ratio"), fratio, ("ratio",)),
(builder.get_object("summary_tracker_status"), None, ("tracker_status",)),
(builder.get_object("summary_next_announce"), deluge.common.ftime, ("next_announce",)),
(builder.get_object("summary_active_time"), deluge.common.ftime, ("active_time",)), (builder.get_object("summary_active_time"), deluge.common.ftime, ("active_time",)),
(builder.get_object("summary_seed_time"), deluge.common.ftime, ("seeding_time",)), (builder.get_object("summary_seed_time"), deluge.common.ftime, ("seeding_time",)),
(builder.get_object("summary_seed_rank"), str, ("seed_rank",)), (builder.get_object("summary_seed_rank"), str, ("seed_rank",)),
(builder.get_object("summary_auto_managed"), str, ("is_auto_managed",)), (builder.get_object("progressbar"), fpcnt, ("progress", "state")),
(builder.get_object("progressbar"), fpcnt, ("progress",)),
(builder.get_object("summary_last_seen_complete"), fdate_or_never, ("last_seen_complete",)), (builder.get_object("summary_last_seen_complete"), fdate_or_never, ("last_seen_complete",)),
(builder.get_object("summary_torrent_status"), str, ("message",)), (builder.get_object("summary_torrent_status"), str, ("message",)),
(builder.get_object("summary_tracker"), None, ("tracker_host",)),
] ]
self.status_keys = [status for widget in self.label_widgets for status in widget[2]]
def update(self): def update(self):
# Get the first selected torrent # Get the first selected torrent
selected = component.get("TorrentView").get_selected_torrents() selected = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected # Only use the first torrent in the list or return if None selected
if len(selected) != 0: if selected:
selected = selected[0] selected = selected[0]
else: else:
# No torrent is selected in the torrentview # No torrent is selected in the torrentview
self.clear()
return return
# Get the torrent status # Get the torrent status
status_keys = [ status_keys = self.status_keys
"distributed_copies", "all_time_download", "total_payload_download",
"total_uploaded", "total_payload_upload", "download_payload_rate", "max_download_speed",
"upload_payload_rate", "max_upload_speed", "num_peers", "num_seeds", "total_peers",
"total_seeds", "eta", "ratio", "tracker_status", "next_announce", "active_time",
"seeding_time", "seed_rank", "is_auto_managed", "progress", "last_seen_complete",
"message", "tracker_host"
]
if self.config['show_piecesbar']: if self.config['show_piecesbar']:
status_keys.extend(["pieces", "state", "num_pieces"]) status_keys = self.status_keys + ["pieces", "num_pieces"]
component.get("SessionProxy").get_torrent_status( component.get("SessionProxy").get_torrent_status(
selected, status_keys).addCallback(self._on_get_torrent_status) selected, status_keys).addCallback(self._on_get_torrent_status)
@ -145,33 +139,14 @@ class StatusTab(Tab):
if status is None: if status is None:
return return
if status["is_auto_managed"]:
status["is_auto_managed"] = _("On")
else:
status["is_auto_managed"] = _("Off")
translate_tracker_status = {
"Error": _("Error"),
"Warning": _("Warning"),
"Announce OK": _("Announce OK"),
"Announce Sent": _("Announce Sent")
}
for key, value in translate_tracker_status.iteritems():
if key in status["tracker_status"]:
status["tracker_status"] = status["tracker_status"].replace(key, value, 1)
break
# Update all the label widgets # Update all the label widgets
for widget in self.label_widgets: for widget in self.label_widgets:
if widget[1] is not None: if widget[1] is not None:
args = []
try: try:
for key in widget[2]: args = [status[key] for key in widget[2]]
args.append(status[key]) except KeyError, ex:
except Exception, e: log.debug("Unable to get status value: %s", ex)
log.debug("Unable to get status value: %s", e)
continue continue
txt = widget[1](*args) txt = widget[1](*args)
else: else:
txt = status[widget[2][0]] txt = status[widget[2][0]]
@ -202,7 +177,7 @@ class StatusTab(Tab):
if show: if show:
self.piecesbar = PiecesBar() self.piecesbar = PiecesBar()
self.builder.get_object("status_progress_vbox").pack_start( self.builder.get_object("status_progress_vbox").pack_start(
self.piecesbar, False, False, 5 self.piecesbar, False, False, 0
) )
self.progressbar.hide() self.progressbar.hide()

View file

@ -1,36 +1,10 @@
# # -*- coding: utf-8 -*-
# torrentdetails.py
# #
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
# #
# Deluge is free software. # This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# # the additional special exception to link portions of this program with the OpenSSL library.
# You may redistribute it and/or modify it under the terms of the # See LICENSE for more details.
# 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.
#
# #
@ -45,6 +19,7 @@ from deluge.ui.gtkui.common import save_pickled_state_file, load_pickled_state_f
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Tab: class Tab:
def __init__(self): def __init__(self):
self.is_visible = True self.is_visible = True
@ -69,6 +44,7 @@ class Tab:
return self._tab_label return self._tab_label
class TorrentDetails(component.Component): class TorrentDetails(component.Component):
def __init__(self): def __init__(self):
component.Component.__init__(self, "TorrentDetails", interval=2) component.Component.__init__(self, "TorrentDetails", interval=2)
@ -91,13 +67,15 @@ class TorrentDetails(component.Component):
from files_tab import FilesTab from files_tab import FilesTab
from peers_tab import PeersTab from peers_tab import PeersTab
from options_tab import OptionsTab from options_tab import OptionsTab
from trackers_tab import TrackersTab
default_tabs = { default_tabs = {
"Status": StatusTab, "Status": StatusTab,
"Details": DetailsTab, "Details": DetailsTab,
"Files": FilesTab, "Files": FilesTab,
"Peers": PeersTab, "Peers": PeersTab,
"Options": OptionsTab "Options": OptionsTab,
"Trackers": TrackersTab
} }
# tab_name, visible # tab_name, visible
@ -106,17 +84,19 @@ class TorrentDetails(component.Component):
("Details", True), ("Details", True),
("Files", True), ("Files", True),
("Peers", True), ("Peers", True),
("Options", True) ("Options", True),
("Trackers", True)
] ]
self.translate_tabs = { self.translate_tabs = {
"All" : _("_All"), "All": _("_All"),
"Status" : _("_Status"), "Status": _("_Status"),
"Details" : _("_Details"), "Details": _("_Details"),
"Files" : _("_Files"), "Files": _("_Files"),
"Peers" : _("_Peers"), "Peers": _("_Peers"),
"Options" : _("_Options") "Options": _("_Options"),
} "Trackers": _("_Trackers")
}
# Get the state from saved file # Get the state from saved file
state = self.load_state() state = self.load_state()
@ -129,13 +109,12 @@ class TorrentDetails(component.Component):
break break
# The state is a list of tab_names in the order they should appear # The state is a list of tab_names in the order they should appear
if state == None: if state is None:
# Set the default order # Set the default order
state = default_order state = default_order
# We need to rename the tab in the state for backwards compat # We need to rename the tab in the state for backwards compat
self.state = [(tab_name.replace("Statistics", "Status"), visible) for self.state = [(tab_name.replace("Statistics", "Status"), visible) for tab_name, visible in state]
tab_name, visible in state]
for tab in default_tabs.itervalues(): for tab in default_tabs.itervalues():
self.add_tab(tab(), generate_menu=False) self.add_tab(tab(), generate_menu=False)
@ -162,7 +141,6 @@ class TorrentDetails(component.Component):
break break
return position return position
def add_tab(self, tab, generate_menu=True, visible=None): def add_tab(self, tab, generate_menu=True, visible=None):
name = tab.get_name() name = tab.get_name()
@ -208,10 +186,8 @@ class TorrentDetails(component.Component):
if generate_menu: if generate_menu:
self.generate_menu() self.generate_menu()
def regenerate_positions(self): def regenerate_positions(self):
"""This will sync up the positions in the tab, with the position stored """Sync the positions in the tab, with the position stored in the tab object"""
in the tab object"""
for tab in self.tabs: for tab in self.tabs:
page_num = self.notebook.page_num(self.tabs[tab]._child_widget) page_num = self.notebook.page_num(self.tabs[tab]._child_widget)
if page_num > -1: if page_num > -1:
@ -262,8 +238,7 @@ class TorrentDetails(component.Component):
def show_tab(self, tab_name, generate_menu=True): def show_tab(self, tab_name, generate_menu=True):
log.debug("%s\n%s\n%s", self.tabs[tab_name].get_child_widget(), log.debug("%s\n%s\n%s", self.tabs[tab_name].get_child_widget(),
self.tabs[tab_name].get_tab_label(), self.tabs[tab_name].get_tab_label(), self.tabs[tab_name].position)
self.tabs[tab_name].position)
position = self.tab_insert_position(self.tabs[tab_name].weight) position = self.tab_insert_position(self.tabs[tab_name].weight)
@ -344,7 +319,6 @@ class TorrentDetails(component.Component):
except AttributeError: except AttributeError:
pass pass
def shutdown(self): def shutdown(self):
# Save the state of the tabs # Save the state of the tabs
for tab in self.tabs: for tab in self.tabs:
@ -362,7 +336,7 @@ class TorrentDetails(component.Component):
self.clear() self.clear()
if self.notebook.get_property("visible"): if self.notebook.get_property("visible"):
if page_num == None: if page_num is None:
page_num = self.notebook.get_current_page() page_num = self.notebook.get_current_page()
try: try:
# Get the tab name # Get the tab name

View file

@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import logging
import deluge.component as component
from deluge.common import ftime
from deluge.ui.gtkui.torrentdetails import Tab
log = logging.getLogger(__name__)
def fcount(value):
return "%s" % len(value)
def ftranslate(text):
if text:
text = _(text)
return text
class TrackersTab(Tab):
def __init__(self):
Tab.__init__(self)
# Get the labels we need to update.
# widget name, modifier function, status keys
builder = component.get("MainWindow").get_builder()
self._name = "Trackers"
self._child_widget = builder.get_object("trackers_tab")
self._tab_label = builder.get_object("trackers_tab_label")
self.label_widgets = [
(builder.get_object("summary_next_announce"), ftime, ("next_announce",)),
(builder.get_object("summary_tracker"), None, ("tracker_host",)),
(builder.get_object("summary_tracker_status"), ftranslate, ("tracker_status",)),
(builder.get_object("summary_tracker_total"), fcount, ("trackers",)),
]
self.status_keys = [status for widget in self.label_widgets for status in widget[2]]
component.get("MainWindow").connect_signals({
"on_button_edit_trackers_clicked": self._on_button_edit_trackers_clicked,
})
def update(self):
# Get the first selected torrent
selected = component.get("TorrentView").get_selected_torrents()
# Only use the first torrent in the list or return if None selected
if selected:
selected = selected[0]
else:
self.clear()
return
session = component.get("SessionProxy")
session.get_torrent_status(selected, self.status_keys).addCallback(self._on_get_torrent_status)
def _on_get_torrent_status(self, status):
# Check to see if we got valid data from the core
if not status:
return
# Update all the label widgets
for widget in self.label_widgets:
if widget[1] is None:
txt = status[widget[2][0]]
else:
try:
args = [status[key] for key in widget[2]]
except KeyError, ex:
log.debug("Unable to get status value: %s", ex)
continue
txt = widget[1](*args)
if widget[0].get_text() != txt:
widget[0].set_text(txt)
def clear(self):
for widget in self.label_widgets:
widget[0].set_text("")
def _on_button_edit_trackers_clicked(self, button):
torrent_id = component.get("TorrentView").get_selected_torrent()
if torrent_id:
from edittrackersdialog import EditTrackersDialog
dialog = EditTrackersDialog(torrent_id, component.get("MainWindow").window)
dialog.run()