[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
parent 14776d86f5
commit 6496383e82
8 changed files with 1462 additions and 1424 deletions

View File

@ -39,10 +39,7 @@ all the interfaces.
"""
import os
import sys
import logging
import urlparse
import locale
from hashlib import sha1 as sha
from deluge import bencode
@ -51,10 +48,12 @@ import deluge.configmanager
log = logging.getLogger(__name__)
# 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
# as the string matches the translation text so using the _() function is enough.
def _(message): return message
def _(message):
return message
STATE_TRANSLATION = {
"All": _("All"),
"Active": _("Active"),
@ -67,8 +66,16 @@ STATE_TRANSLATION = {
"Queued": _("Queued"),
"Error": _("Error"),
}
TRACKER_STATUS_TRANSLATION = {
"Error": _("Error"),
"Warning": _("Warning"),
"Announce OK": _("Announce OK"),
"Announce Sent": _("Announce Sent")
}
del _
class TorrentInfo(object):
"""
Collects information about a torrent file.

View File

@ -21,11 +21,11 @@ def fpeer_size_second(first, second):
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:
return fdate(value)
else:
return ""
return "-"
def str_yes_no(value):
@ -59,25 +59,22 @@ class DetailsTab(Tab):
(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):
# 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 len(selected) != 0:
if selected:
selected = selected[0]
else:
# No torrent is selected in the torrentview
self.clear()
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.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):
# Check to see if we got valid data from the core
@ -87,14 +84,11 @@ class DetailsTab(Tab):
# Update all the label widgets
for widget in self.label_widgets:
if widget[1] is not None:
args = []
try:
for key in widget[2]:
args.append(status[key])
except Exception, e:
log.debug("Unable to get status value: %s", e)
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)
else:
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({
"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_stop_at_ratio_toggled": self._on_chk_stop_at_ratio_toggled,
"on_chk_toggled": self._on_chk_toggled,
@ -102,7 +101,7 @@ class OptionsTab(Tab):
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:
if torrent_id:
torrent_id = torrent_id[0]
self._child_widget.set_sensitive(True)
else:
@ -250,17 +249,6 @@ class OptionsTab(Tab):
)
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):
value = self.chk_move_completed.get_active()
self.move_completed_path_chooser.set_sensitive(value)

View File

@ -52,6 +52,7 @@ COLOR_STATES = {
3: "completed"
}
class PiecesBar(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = {"expose-event": "override"}
@ -141,7 +142,7 @@ class PiecesBar(gtk.DrawingArea):
def __draw_pieces(self):
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
self.__pieces_overlay = cairo.ImageSurface(
cairo.FORMAT_ARGB32, self.__width, self.__height
@ -194,8 +195,7 @@ class PiecesBar(gtk.DrawingArea):
if not self.__state:
# Nothing useful to draw, return now!
return
if (self.__resized() or self.__fraction != self.__old_fraction) or \
self.__progress_overlay is None:
if (self.__resized() or self.__fraction != self.__old_fraction) or self.__progress_overlay is None:
# Need to recreate the cache drawing
self.__progress_overlay = cairo.ImageSurface(
cairo.FORMAT_ARGB32, self.__width, self.__height
@ -230,7 +230,7 @@ class PiecesBar(gtk.DrawingArea):
text += self.__text
else:
if self.__state:
text += self.__state + " "
text += _(self.__state) + " "
if self.__fraction == 1.0:
format = "%d%%"
else:

View File

@ -55,8 +55,10 @@ def fratio(value):
return "%.3f" % value
def fpcnt(value):
return "%.2f%%" % value
def fpcnt(value, state):
if state:
state = _(state) + " "
return "%s%.2f%%" % (state, value)
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_eta"), deluge.common.ftime, ("eta",)),
(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_seed_time"), deluge.common.ftime, ("seeding_time",)),
(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",)),
(builder.get_object("progressbar"), fpcnt, ("progress", "state")),
(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_tracker"), None, ("tracker_host",)),
]
self.status_keys = [status for widget in self.label_widgets for status in widget[2]]
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 len(selected) != 0:
if selected:
selected = selected[0]
else:
# No torrent is selected in the torrentview
self.clear()
return
# Get the torrent status
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"
]
status_keys = self.status_keys
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(
selected, status_keys).addCallback(self._on_get_torrent_status)
@ -145,33 +139,14 @@ class StatusTab(Tab):
if status is None:
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
for widget in self.label_widgets:
if widget[1] is not None:
args = []
try:
for key in widget[2]:
args.append(status[key])
except Exception, e:
log.debug("Unable to get status value: %s", e)
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)
else:
txt = status[widget[2][0]]
@ -202,7 +177,7 @@ class StatusTab(Tab):
if show:
self.piecesbar = PiecesBar()
self.builder.get_object("status_progress_vbox").pack_start(
self.piecesbar, False, False, 5
self.piecesbar, False, False, 0
)
self.progressbar.hide()

View File

@ -1,36 +1,10 @@
#
# torrentdetails.py
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
#
# 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.
#
# 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.
#
@ -45,6 +19,7 @@ from deluge.ui.gtkui.common import save_pickled_state_file, load_pickled_state_f
log = logging.getLogger(__name__)
class Tab:
def __init__(self):
self.is_visible = True
@ -69,6 +44,7 @@ class Tab:
return self._tab_label
class TorrentDetails(component.Component):
def __init__(self):
component.Component.__init__(self, "TorrentDetails", interval=2)
@ -91,13 +67,15 @@ class TorrentDetails(component.Component):
from files_tab import FilesTab
from peers_tab import PeersTab
from options_tab import OptionsTab
from trackers_tab import TrackersTab
default_tabs = {
"Status": StatusTab,
"Details": DetailsTab,
"Files": FilesTab,
"Peers": PeersTab,
"Options": OptionsTab
"Options": OptionsTab,
"Trackers": TrackersTab
}
# tab_name, visible
@ -106,16 +84,18 @@ class TorrentDetails(component.Component):
("Details", True),
("Files", True),
("Peers", True),
("Options", True)
("Options", True),
("Trackers", True)
]
self.translate_tabs = {
"All" : _("_All"),
"Status" : _("_Status"),
"Details" : _("_Details"),
"Files" : _("_Files"),
"Peers" : _("_Peers"),
"Options" : _("_Options")
"All": _("_All"),
"Status": _("_Status"),
"Details": _("_Details"),
"Files": _("_Files"),
"Peers": _("_Peers"),
"Options": _("_Options"),
"Trackers": _("_Trackers")
}
# Get the state from saved file
@ -129,13 +109,12 @@ class TorrentDetails(component.Component):
break
# 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
state = default_order
# We need to rename the tab in the state for backwards compat
self.state = [(tab_name.replace("Statistics", "Status"), visible) for
tab_name, visible in state]
self.state = [(tab_name.replace("Statistics", "Status"), visible) for tab_name, visible in state]
for tab in default_tabs.itervalues():
self.add_tab(tab(), generate_menu=False)
@ -162,7 +141,6 @@ class TorrentDetails(component.Component):
break
return position
def add_tab(self, tab, generate_menu=True, visible=None):
name = tab.get_name()
@ -208,10 +186,8 @@ class TorrentDetails(component.Component):
if generate_menu:
self.generate_menu()
def regenerate_positions(self):
"""This will sync up the positions in the tab, with the position stored
in the tab object"""
"""Sync the positions in the tab, with the position stored in the tab object"""
for tab in self.tabs:
page_num = self.notebook.page_num(self.tabs[tab]._child_widget)
if page_num > -1:
@ -262,8 +238,7 @@ class TorrentDetails(component.Component):
def show_tab(self, tab_name, generate_menu=True):
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].position)
self.tabs[tab_name].get_tab_label(), self.tabs[tab_name].position)
position = self.tab_insert_position(self.tabs[tab_name].weight)
@ -344,7 +319,6 @@ class TorrentDetails(component.Component):
except AttributeError:
pass
def shutdown(self):
# Save the state of the tabs
for tab in self.tabs:
@ -362,7 +336,7 @@ class TorrentDetails(component.Component):
self.clear()
if self.notebook.get_property("visible"):
if page_num == None:
if page_num is None:
page_num = self.notebook.get_current_page()
try:
# 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()