This moves the directory structure so that there is no conflict with the old gtk2 UI. Also changes the conf and state files being loaded.
459 lines
15 KiB
Python
459 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2007 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.
|
|
#
|
|
|
|
|
|
"""The torrent details component shows info about the selected torrent."""
|
|
from __future__ import unicode_literals
|
|
|
|
import logging
|
|
from collections import namedtuple
|
|
|
|
from gi.repository.Gtk import CheckMenuItem, Menu, SeparatorMenuItem
|
|
|
|
import deluge.component as component
|
|
from deluge.ui.client import client
|
|
|
|
from .common import load_pickled_state_file, save_pickled_state_file
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
TabWidget = namedtuple('TabWidget', ('obj', 'func', 'status_keys'))
|
|
|
|
|
|
class Tab(object):
|
|
def __init__(self, name=None, child_widget=None, tab_label=None):
|
|
self._name = name
|
|
self.is_visible = True
|
|
self.position = -1
|
|
self.weight = -1
|
|
|
|
self.main_builder = component.get('MainWindow').get_builder()
|
|
self._child_widget = (
|
|
self.main_builder.get_object(child_widget) if child_widget else None
|
|
)
|
|
self._tab_label = self.main_builder.get_object(tab_label) if tab_label else None
|
|
|
|
self.tab_widgets = {}
|
|
self.status_keys = []
|
|
|
|
def get_name(self):
|
|
return self._name
|
|
|
|
def get_child_widget(self):
|
|
parent = self._child_widget.get_parent()
|
|
if parent is not None:
|
|
parent.remove(self._child_widget)
|
|
|
|
return self._child_widget
|
|
|
|
def get_tab_label(self):
|
|
parent = self._tab_label.get_parent()
|
|
log.debug('parent: %s', parent)
|
|
if parent is not None:
|
|
parent.remove(self._tab_label)
|
|
|
|
return self._tab_label
|
|
|
|
def widget_status_as_fstr(self, widget, status):
|
|
"""Use TabWidget status_key and func to format status string.
|
|
|
|
Args:
|
|
widget (TabWidget): A tuple of widget object, func and status_keys.
|
|
status (dict): Torrent status dict.
|
|
|
|
Returns:
|
|
str: The formatted status string.
|
|
"""
|
|
try:
|
|
if widget.func is None:
|
|
txt = status[widget.status_keys[0]]
|
|
else:
|
|
args = [status[key] for key in widget.status_keys]
|
|
txt = widget.func(*args)
|
|
except KeyError as ex:
|
|
log.warning('Unable to get status value: %s', ex)
|
|
txt = ''
|
|
return txt
|
|
|
|
def add_tab_widget(self, widget_id, format_func, status_keys):
|
|
"""Create TabWidget item in tab_widgets dictionary.
|
|
|
|
Args:
|
|
widget_id (str): The widget id used to retrieve widget from mainwindow builder.
|
|
format_func (str): A func name related to widget e.g. string label formatter.
|
|
status_keys (list): List of status keys to lookup for the widget.
|
|
|
|
"""
|
|
widget_obj = self.main_builder.get_object(widget_id)
|
|
self.status_keys.extend(status_keys)
|
|
# Store the widget in a tab_widgets dict with name as key for faster lookup.
|
|
self.tab_widgets[widget_id] = TabWidget(widget_obj, format_func, status_keys)
|
|
|
|
|
|
class TorrentDetails(component.Component):
|
|
def __init__(self):
|
|
component.Component.__init__(self, 'TorrentDetails', interval=2)
|
|
main_builder = component.get('MainWindow').get_builder()
|
|
|
|
self.notebook = main_builder.get_object('torrent_info')
|
|
|
|
# This is the menu item we'll attach the tabs checklist menu to
|
|
self.menu_tabs = main_builder.get_object('menu_tabs')
|
|
|
|
self.notebook.connect('switch-page', self._on_switch_page)
|
|
|
|
# Tabs holds references to the Tab objects by their name
|
|
self.tabs = {}
|
|
|
|
# Add the default tabs
|
|
from .status_tab import StatusTab
|
|
from .details_tab import DetailsTab
|
|
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,
|
|
'Trackers': TrackersTab,
|
|
}
|
|
|
|
# tab_name, visible
|
|
default_order = [
|
|
('Status', True),
|
|
('Details', True),
|
|
('Options', True),
|
|
('Files', True),
|
|
('Peers', True),
|
|
('Trackers', True),
|
|
]
|
|
|
|
self.translate_tabs = {
|
|
'All': _('_All'),
|
|
'Status': _('_Status'),
|
|
'Details': _('_Details'),
|
|
'Files': _('Fi_les'),
|
|
'Peers': _('_Peers'),
|
|
'Options': _('_Options'),
|
|
'Trackers': _('_Trackers'),
|
|
}
|
|
|
|
# Get the state from saved file
|
|
state = self.load_state()
|
|
|
|
if state:
|
|
for item in state:
|
|
if not isinstance(item, tuple):
|
|
log.debug('Old tabs.state, using default..')
|
|
state = None
|
|
break
|
|
|
|
# The state is a list of tab_names in the order they should appear
|
|
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
|
|
]
|
|
|
|
for tab in default_tabs.values():
|
|
self.add_tab(tab(), generate_menu=False)
|
|
|
|
# Generate the checklist menu
|
|
self.generate_menu()
|
|
|
|
def tab_insert_position(self, weight):
|
|
"""Returns the position a tab with a given weight should be inserted in"""
|
|
# Determine insert position based on weight
|
|
# weights is a list of visible tab names in weight order
|
|
|
|
weights = sorted(
|
|
(tab.weight, name) for name, tab in self.tabs.items() if tab.is_visible
|
|
)
|
|
|
|
log.debug('weights: %s', weights)
|
|
log.debug('weight of tab: %s', weight)
|
|
|
|
position = -1
|
|
for w, name in weights:
|
|
if w >= weight:
|
|
position = self.tabs[name].position
|
|
log.debug('Found pos %d', position)
|
|
break
|
|
return position
|
|
|
|
def add_tab(self, tab, generate_menu=True, visible=None):
|
|
name = tab.get_name()
|
|
|
|
# find position of tab in self.state, this is the tab weight
|
|
weight = None
|
|
for w, item in enumerate(self.state):
|
|
if item[0] == name:
|
|
weight = w
|
|
if visible is None:
|
|
visible = item[1]
|
|
break
|
|
|
|
if weight is None:
|
|
if visible is None:
|
|
visible = True
|
|
weight = len(self.state)
|
|
self.state.append((name, visible))
|
|
|
|
tab.weight = weight
|
|
|
|
if visible:
|
|
tab.is_visible = True
|
|
# add the tab at position guided by the weight
|
|
insert_pos = self.tab_insert_position(weight)
|
|
log.debug('Trying to insert tab at %d', insert_pos)
|
|
pos = self.notebook.insert_page(
|
|
tab.get_child_widget(), tab.get_tab_label(), insert_pos
|
|
)
|
|
log.debug('Tab inserted at %d', pos)
|
|
tab.position = pos
|
|
if not self.notebook.get_property('visible'):
|
|
# If the notebook isn't visible, show it
|
|
self.visible(True)
|
|
else:
|
|
tab.is_visible = False
|
|
|
|
self.tabs[name] = tab
|
|
if name not in self.translate_tabs:
|
|
self.translate_tabs[name] = _(name)
|
|
|
|
self.regenerate_positions()
|
|
if generate_menu:
|
|
self.generate_menu()
|
|
|
|
def regenerate_positions(self):
|
|
"""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:
|
|
self.tabs[tab].position = page_num
|
|
|
|
def remove_tab(self, tab_name):
|
|
"""Removes a tab by name."""
|
|
self.notebook.remove_page(self.tabs[tab_name].position)
|
|
del self.tabs[tab_name]
|
|
self.regenerate_positions()
|
|
self.generate_menu()
|
|
|
|
# If there are no tabs visible, then do not show the notebook
|
|
if len(self.tabs) == 0:
|
|
self.visible(False)
|
|
|
|
def hide_all_tabs(self):
|
|
"""Hides all tabs"""
|
|
log.debug('n_pages: %s', self.notebook.get_n_pages())
|
|
for n in range(self.notebook.get_n_pages() - 1, -1, -1):
|
|
self.notebook.remove_page(n)
|
|
|
|
for tab in self.tabs:
|
|
self.tabs[tab].is_visible = False
|
|
log.debug('n_pages: %s', self.notebook.get_n_pages())
|
|
self.generate_menu()
|
|
self.visible(False)
|
|
|
|
def show_all_tabs(self):
|
|
"""Shows all tabs"""
|
|
for tab in self.tabs:
|
|
if not self.tabs[tab].is_visible:
|
|
self.show_tab(tab, generate_menu=False)
|
|
self.generate_menu()
|
|
|
|
def hide_tab(self, tab_name):
|
|
"""Hides tab by name"""
|
|
self.tabs[tab_name].is_visible = False
|
|
self.notebook.remove_page(self.tabs[tab_name].position)
|
|
self.regenerate_positions()
|
|
self.generate_menu()
|
|
|
|
show = False
|
|
for name, tab in self.tabs.items():
|
|
show = show or tab.is_visible
|
|
|
|
self.visible(show)
|
|
|
|
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,
|
|
)
|
|
|
|
position = self.tab_insert_position(self.tabs[tab_name].weight)
|
|
|
|
log.debug('position: %s', position)
|
|
self.notebook.insert_page(
|
|
self.tabs[tab_name].get_child_widget(),
|
|
self.tabs[tab_name].get_tab_label(),
|
|
position,
|
|
)
|
|
self.tabs[tab_name].is_visible = True
|
|
self.regenerate_positions()
|
|
if generate_menu:
|
|
self.generate_menu()
|
|
self.visible(True)
|
|
|
|
def generate_menu(self):
|
|
"""Generates the checklist menu for all the tabs and attaches it"""
|
|
menu = Menu()
|
|
# Create 'All' menuitem and a separator
|
|
menuitem = CheckMenuItem.new_with_mnemonic(self.translate_tabs['All'])
|
|
menuitem.set_name('All')
|
|
|
|
all_tabs = True
|
|
for key in self.tabs:
|
|
if not self.tabs[key].is_visible:
|
|
all_tabs = False
|
|
break
|
|
menuitem.set_active(all_tabs)
|
|
menuitem.connect('toggled', self._on_menuitem_toggled)
|
|
|
|
menu.append(menuitem)
|
|
|
|
menuitem = SeparatorMenuItem()
|
|
menu.append(menuitem)
|
|
|
|
# Create a list in order of tabs to create menu
|
|
menuitem_list = []
|
|
for tab_name in self.tabs:
|
|
menuitem_list.append((self.tabs[tab_name].weight, tab_name))
|
|
menuitem_list.sort()
|
|
|
|
for pos, name in menuitem_list:
|
|
menuitem = CheckMenuItem.new_with_mnemonic(self.translate_tabs[name])
|
|
menuitem.set_name(name)
|
|
menuitem.set_active(self.tabs[name].is_visible)
|
|
menuitem.connect('toggled', self._on_menuitem_toggled)
|
|
menu.append(menuitem)
|
|
|
|
self.menu_tabs.set_submenu(menu)
|
|
self.menu_tabs.show_all()
|
|
|
|
def visible(self, visible):
|
|
if visible:
|
|
self.notebook.show()
|
|
else:
|
|
self.notebook.hide()
|
|
component.get('MainWindow').vpaned.set_position(-1)
|
|
|
|
def set_tab_visible(self, tab_name, visible):
|
|
"""Sets the tab to visible"""
|
|
log.debug('set_tab_visible name: %s visible: %s', tab_name, visible)
|
|
if visible and not self.tabs[tab_name].is_visible:
|
|
self.show_tab(tab_name)
|
|
elif not visible and self.tabs[tab_name].is_visible:
|
|
self.hide_tab(tab_name)
|
|
|
|
def start(self):
|
|
for tab in self.tabs.values():
|
|
try:
|
|
tab.start()
|
|
except AttributeError:
|
|
pass
|
|
|
|
def stop(self):
|
|
self.clear()
|
|
for tab in self.tabs.values():
|
|
try:
|
|
tab.stop()
|
|
except AttributeError:
|
|
pass
|
|
|
|
def shutdown(self):
|
|
# Save the state of the tabs
|
|
for tab in self.tabs:
|
|
try:
|
|
self.tabs[tab].save_state()
|
|
except AttributeError:
|
|
pass
|
|
|
|
# Save tabs state
|
|
self.save_state()
|
|
|
|
def update(self, page_num=None):
|
|
if len(component.get('TorrentView').get_selected_torrents()) == 0:
|
|
# No torrents selected, so just clear
|
|
self.clear()
|
|
|
|
if self.notebook.get_property('visible'):
|
|
if page_num is None:
|
|
page_num = self.notebook.get_current_page()
|
|
try:
|
|
# Get the tab name
|
|
name = None
|
|
for tab in self.tabs:
|
|
if (
|
|
self.tabs[tab].position == page_num
|
|
and self.tabs[tab].is_visible
|
|
):
|
|
name = tab
|
|
except IndexError:
|
|
return
|
|
# Update the tab that is in view
|
|
if name:
|
|
self.tabs[name].update()
|
|
|
|
def clear(self):
|
|
# Get the tab name
|
|
try:
|
|
page_num = self.notebook.get_current_page()
|
|
name = None
|
|
for tab in self.tabs:
|
|
if self.tabs[tab].position == page_num and self.tabs[tab].is_visible:
|
|
name = tab
|
|
if name:
|
|
self.tabs[name].clear()
|
|
except Exception as ex:
|
|
log.debug('Unable to clear torrentdetails: %s', ex)
|
|
|
|
def _on_switch_page(self, notebook, page, page_num):
|
|
self.update(page_num)
|
|
client.force_call(False)
|
|
|
|
def _on_menuitem_toggled(self, widget):
|
|
# Get the tab name
|
|
name = widget.get_name()
|
|
if name == 'All':
|
|
if widget.get_active():
|
|
self.show_all_tabs()
|
|
else:
|
|
self.hide_all_tabs()
|
|
return
|
|
|
|
self.set_tab_visible(name, widget.get_active())
|
|
|
|
def save_state(self):
|
|
"""We save the state, which is basically the tab_index list"""
|
|
# Update the visiblity status of all tabs
|
|
# Leave tabs we dont know anything about it the state as they
|
|
# might come from a plugin
|
|
for i, (name, visible) in enumerate(self.state):
|
|
log.debug('Testing name: %s', name)
|
|
if name in self.tabs:
|
|
self.state[i] = (name, self.tabs[name].is_visible)
|
|
log.debug('Set to %s', self.state[i])
|
|
state = self.state
|
|
|
|
save_pickled_state_file('tabs.state', state)
|
|
|
|
def load_state(self):
|
|
return load_pickled_state_file('tabs.state')
|