1 | #
|
---|
2 | # torrentdetails.py
|
---|
3 | #
|
---|
4 | # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
|
---|
5 | #
|
---|
6 | # Deluge is free software.
|
---|
7 | #
|
---|
8 | # You may redistribute it and/or modify it under the terms of the
|
---|
9 | # GNU General Public License, as published by the Free Software
|
---|
10 | # Foundation; either version 3 of the License, or (at your option)
|
---|
11 | # any later version.
|
---|
12 | #
|
---|
13 | # deluge is distributed in the hope that it will be useful,
|
---|
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
16 | # See the GNU General Public License for more details.
|
---|
17 | #
|
---|
18 | # You should have received a copy of the GNU General Public License
|
---|
19 | # along with deluge. If not, write to:
|
---|
20 | # The Free Software Foundation, Inc.,
|
---|
21 | # 51 Franklin Street, Fifth Floor
|
---|
22 | # Boston, MA 02110-1301, USA.
|
---|
23 | #
|
---|
24 |
|
---|
25 |
|
---|
26 | """The torrent details component shows info about the selected torrent."""
|
---|
27 |
|
---|
28 | import gtk, gtk.glade
|
---|
29 | import os
|
---|
30 | import os.path
|
---|
31 | import cPickle
|
---|
32 |
|
---|
33 | import deluge.component as component
|
---|
34 | from deluge.ui.client import client
|
---|
35 | from deluge.configmanager import ConfigManager
|
---|
36 | import deluge.configmanager
|
---|
37 |
|
---|
38 | from deluge.log import LOG as log
|
---|
39 |
|
---|
40 | class Tab:
|
---|
41 | def __init__(self):
|
---|
42 | self.is_visible = True
|
---|
43 | self.position = -1
|
---|
44 | self.weight = -1
|
---|
45 |
|
---|
46 | def get_name(self):
|
---|
47 | return self._name
|
---|
48 |
|
---|
49 | def get_child_widget(self):
|
---|
50 | parent = self._child_widget.get_parent()
|
---|
51 | if parent is not None:
|
---|
52 | parent.remove(self._child_widget)
|
---|
53 |
|
---|
54 | return self._child_widget
|
---|
55 |
|
---|
56 | def get_tab_label(self):
|
---|
57 | parent = self._tab_label.get_parent()
|
---|
58 | log.debug("parent: %s", parent)
|
---|
59 | if parent is not None:
|
---|
60 | parent.remove(self._tab_label)
|
---|
61 |
|
---|
62 | return self._tab_label
|
---|
63 |
|
---|
64 | class TorrentDetails(component.Component):
|
---|
65 | def __init__(self):
|
---|
66 | component.Component.__init__(self, "TorrentDetails", interval=2)
|
---|
67 | self.window = component.get("MainWindow")
|
---|
68 | glade = self.window.main_glade
|
---|
69 |
|
---|
70 | self.notebook = glade.get_widget("torrent_info")
|
---|
71 |
|
---|
72 | # This is the menu item we'll attach the tabs checklist menu to
|
---|
73 | self.menu_tabs = glade.get_widget("menu_tabs")
|
---|
74 |
|
---|
75 | self.notebook.connect("switch-page", self._on_switch_page)
|
---|
76 |
|
---|
77 | # Tabs holds references to the Tab objects by their name
|
---|
78 | self.tabs = {}
|
---|
79 |
|
---|
80 | # Add the default tabs
|
---|
81 | from status_tab import StatusTab
|
---|
82 | from details_tab import DetailsTab
|
---|
83 | from files_tab import FilesTab
|
---|
84 | from peers_tab import PeersTab
|
---|
85 | from options_tab import OptionsTab
|
---|
86 |
|
---|
87 | default_tabs = {
|
---|
88 | "Status": StatusTab,
|
---|
89 | "Details": DetailsTab,
|
---|
90 | "Files": FilesTab,
|
---|
91 | "Peers": PeersTab,
|
---|
92 | "Options": OptionsTab
|
---|
93 | }
|
---|
94 |
|
---|
95 | # tab_name, visible
|
---|
96 | default_order = [
|
---|
97 | ("Status", True),
|
---|
98 | ("Details", True),
|
---|
99 | ("Files", True),
|
---|
100 | ("Peers", True),
|
---|
101 | ("Options", True)
|
---|
102 | ]
|
---|
103 |
|
---|
104 | # Get the state from saved file
|
---|
105 | state = self.load_state()
|
---|
106 |
|
---|
107 | if state:
|
---|
108 | for item in state:
|
---|
109 | if not isinstance(item, tuple):
|
---|
110 | log.debug("Old tabs.state, using default..")
|
---|
111 | state = None
|
---|
112 | break
|
---|
113 |
|
---|
114 | # The state is a list of tab_names in the order they should appear
|
---|
115 | if state == None:
|
---|
116 | # Set the default order
|
---|
117 | state = default_order
|
---|
118 |
|
---|
119 | # Add the tabs in the order from the state
|
---|
120 | for tab_name, visible in state:
|
---|
121 | # We need to rename the tab in the state for backwards compat
|
---|
122 | tab_name = tab_name.replace("Statistics", "Status")
|
---|
123 | self.add_tab(default_tabs[tab_name]())
|
---|
124 |
|
---|
125 | # Hide any of the non-visible ones
|
---|
126 | for tab_name, visible in state:
|
---|
127 | if not visible:
|
---|
128 | self.hide_tab(tab_name)
|
---|
129 |
|
---|
130 | # Generate the checklist menu
|
---|
131 | self.generate_menu()
|
---|
132 |
|
---|
133 | def add_tab(self, tab_object, position=-1, generate_menu=True):
|
---|
134 | """Adds a tab object to the notebook."""
|
---|
135 | self.tabs[tab_object.get_name()] = tab_object
|
---|
136 | pos = self.notebook.insert_page(
|
---|
137 | tab_object.get_child_widget(),
|
---|
138 | tab_object.get_tab_label(),
|
---|
139 | position)
|
---|
140 |
|
---|
141 | tab_object.position = pos
|
---|
142 | tab_object.weight = pos
|
---|
143 |
|
---|
144 | # Regenerate positions if an insert occured
|
---|
145 | if position > -1:
|
---|
146 | self.regenerate_positions()
|
---|
147 |
|
---|
148 | if generate_menu:
|
---|
149 | self.generate_menu()
|
---|
150 |
|
---|
151 | if not self.notebook.get_property("visible"):
|
---|
152 | # If the notebook isn't visible, show it
|
---|
153 | self.visible(True)
|
---|
154 |
|
---|
155 | def regenerate_positions(self):
|
---|
156 | """This will sync up the positions in the tab, with the position stored
|
---|
157 | in the tab object"""
|
---|
158 | for tab in self.tabs:
|
---|
159 | page_num = self.notebook.page_num(self.tabs[tab]._child_widget)
|
---|
160 | if page_num > -1:
|
---|
161 | self.tabs[tab].position = page_num
|
---|
162 |
|
---|
163 | def remove_tab(self, tab_name):
|
---|
164 | """Removes a tab by name."""
|
---|
165 | self.notebook.remove_page(self.tabs[tab_name].position)
|
---|
166 | del self.tabs[tab_name]
|
---|
167 | self.regenerate_positions()
|
---|
168 | self.generate_menu()
|
---|
169 |
|
---|
170 | # If there are no tabs visible, then do not show the notebook
|
---|
171 | if len(self.tabs) == 0:
|
---|
172 | self.visible(False)
|
---|
173 |
|
---|
174 | def hide_all_tabs(self):
|
---|
175 | """Hides all tabs"""
|
---|
176 | log.debug("n_pages: %s", self.notebook.get_n_pages())
|
---|
177 | for n in xrange(self.notebook.get_n_pages() - 1, -1, -1):
|
---|
178 | self.notebook.remove_page(n)
|
---|
179 |
|
---|
180 | for tab in self.tabs:
|
---|
181 | self.tabs[tab].is_visible = False
|
---|
182 | log.debug("n_pages: %s", self.notebook.get_n_pages())
|
---|
183 | self.generate_menu()
|
---|
184 | self.visible(False)
|
---|
185 |
|
---|
186 | def show_all_tabs(self):
|
---|
187 | """Shows all tabs"""
|
---|
188 | for tab in self.tabs:
|
---|
189 | if not self.tabs[tab].is_visible:
|
---|
190 | self.notebook.insert_page(
|
---|
191 | self.tabs[tab].get_child_widget(),
|
---|
192 | self.tabs[tab].get_tab_label(),
|
---|
193 | self.tabs[tab].position)
|
---|
194 | self.tabs[tab].is_visible = True
|
---|
195 | self.generate_menu()
|
---|
196 | if not self.notebook.get_property("visible"):
|
---|
197 | # If the notebook isn't visible, show it
|
---|
198 | self.visible(True)
|
---|
199 |
|
---|
200 | def hide_tab(self, tab_name):
|
---|
201 | """Hides tab by name"""
|
---|
202 | self.notebook.remove_page(self.tabs[tab_name].position)
|
---|
203 | self.tabs[tab_name].is_visible = False
|
---|
204 | self.regenerate_positions()
|
---|
205 | self.generate_menu()
|
---|
206 |
|
---|
207 | def show_tab(self, tab_name):
|
---|
208 | log.debug("%s\n%s\n%s", self.tabs[tab_name].get_child_widget(),
|
---|
209 | self.tabs[tab_name].get_tab_label(),
|
---|
210 | self.tabs[tab_name].position)
|
---|
211 |
|
---|
212 | # Determine insert position based on weight
|
---|
213 | # weights is a list of visible tab names in weight order
|
---|
214 | weights = []
|
---|
215 |
|
---|
216 | for tab in self.tabs:
|
---|
217 | if self.tabs[tab].is_visible:
|
---|
218 | weights.append((self.tabs[tab].weight, self.tabs[tab].get_name()))
|
---|
219 |
|
---|
220 | weights.sort()
|
---|
221 | log.debug("weights: %s", weights)
|
---|
222 | position = self.tabs[tab_name].position
|
---|
223 | log.debug("weight of tab: %s", self.tabs[tab_name].weight)
|
---|
224 | for i, w in enumerate(weights):
|
---|
225 | if w[0] >= self.tabs[tab_name].weight:
|
---|
226 | position = self.tabs[w[1]].position
|
---|
227 | break
|
---|
228 |
|
---|
229 | log.debug("position: %s", position)
|
---|
230 | self.notebook.insert_page(
|
---|
231 | self.tabs[tab_name].get_child_widget(),
|
---|
232 | self.tabs[tab_name].get_tab_label(),
|
---|
233 | position)
|
---|
234 | self.tabs[tab_name].is_visible = True
|
---|
235 | self.regenerate_positions()
|
---|
236 | self.generate_menu()
|
---|
237 |
|
---|
238 | def generate_menu(self):
|
---|
239 | """Generates the checklist menu for all the tabs and attaches it"""
|
---|
240 | menu = gtk.Menu()
|
---|
241 | # Create 'All' menuitem and a separator
|
---|
242 | menuitem = gtk.CheckMenuItem("All")
|
---|
243 |
|
---|
244 | all_tabs = True
|
---|
245 | for key in self.tabs:
|
---|
246 | if not self.tabs[key].is_visible:
|
---|
247 | all_tabs = False
|
---|
248 | break
|
---|
249 | menuitem.set_active(all_tabs)
|
---|
250 | menuitem.connect("toggled", self._on_menuitem_toggled)
|
---|
251 |
|
---|
252 | menu.append(menuitem)
|
---|
253 |
|
---|
254 | menuitem = gtk.SeparatorMenuItem()
|
---|
255 | menu.append(menuitem)
|
---|
256 |
|
---|
257 | # Create a list in order of tabs to create menu
|
---|
258 | menuitem_list = []
|
---|
259 | for tab_name in self.tabs:
|
---|
260 | menuitem_list.append((self.tabs[tab_name].weight, tab_name))
|
---|
261 | menuitem_list.sort()
|
---|
262 |
|
---|
263 | for pos, name in menuitem_list:
|
---|
264 | menuitem = gtk.CheckMenuItem(name)
|
---|
265 | menuitem.set_active(self.tabs[name].is_visible)
|
---|
266 | menuitem.connect("toggled", self._on_menuitem_toggled)
|
---|
267 | menu.append(menuitem)
|
---|
268 |
|
---|
269 | self.menu_tabs.set_submenu(menu)
|
---|
270 | self.menu_tabs.show_all()
|
---|
271 |
|
---|
272 | def visible(self, visible):
|
---|
273 | if visible:
|
---|
274 | self.notebook.show()
|
---|
275 | else:
|
---|
276 | self.notebook.hide()
|
---|
277 | self.window.vpaned.set_position(-1)
|
---|
278 |
|
---|
279 | def set_tab_visible(self, tab_name, visible):
|
---|
280 | """Sets the tab to visible"""
|
---|
281 | log.debug("set_tab_visible name: %s visible: %s", tab_name, visible)
|
---|
282 | if visible and not self.tabs[tab_name].is_visible:
|
---|
283 | self.show_tab(tab_name)
|
---|
284 | elif not visible and self.tabs[tab_name].is_visible:
|
---|
285 | self.hide_tab(tab_name)
|
---|
286 |
|
---|
287 | def start(self):
|
---|
288 | for tab in self.tabs.values():
|
---|
289 | try:
|
---|
290 | tab.start()
|
---|
291 | except AttributeError:
|
---|
292 | pass
|
---|
293 |
|
---|
294 | def stop(self):
|
---|
295 | self.clear()
|
---|
296 | for tab in self.tabs.values():
|
---|
297 | try:
|
---|
298 | tab.stop()
|
---|
299 | except AttributeError:
|
---|
300 | pass
|
---|
301 |
|
---|
302 |
|
---|
303 | def shutdown(self):
|
---|
304 | # Save the state of the tabs
|
---|
305 | for tab in self.tabs:
|
---|
306 | try:
|
---|
307 | self.tabs[tab].save_state()
|
---|
308 | except AttributeError:
|
---|
309 | pass
|
---|
310 |
|
---|
311 | # Save tabs state
|
---|
312 | self.save_state()
|
---|
313 |
|
---|
314 | def update(self, page_num=None):
|
---|
315 | if len(component.get("TorrentView").get_selected_torrents()) == 0:
|
---|
316 | # No torrents selected, so just clear
|
---|
317 | self.clear()
|
---|
318 |
|
---|
319 | if self.notebook.get_property("visible"):
|
---|
320 | if page_num == None:
|
---|
321 | page_num = self.notebook.get_current_page()
|
---|
322 | try:
|
---|
323 | # Get the tab name
|
---|
324 | name = None
|
---|
325 | for tab in self.tabs:
|
---|
326 | if self.tabs[tab].position == page_num and self.tabs[tab].is_visible:
|
---|
327 | name = tab
|
---|
328 | except IndexError:
|
---|
329 | return
|
---|
330 | # Update the tab that is in view
|
---|
331 | if name:
|
---|
332 | self.tabs[name].update()
|
---|
333 |
|
---|
334 | def clear(self):
|
---|
335 | # Get the tab name
|
---|
336 | try:
|
---|
337 | page_num = self.notebook.get_current_page()
|
---|
338 | name = None
|
---|
339 | for tab in self.tabs:
|
---|
340 | if self.tabs[tab].position == page_num and self.tabs[tab].is_visible:
|
---|
341 | name = tab
|
---|
342 | if name:
|
---|
343 | self.tabs[name].clear()
|
---|
344 | except Exception, e:
|
---|
345 | log.debug("Unable to clear torrentdetails: %s", e)
|
---|
346 |
|
---|
347 | def _on_switch_page(self, notebook, page, page_num):
|
---|
348 | self.update(page_num)
|
---|
349 | client.force_call(False)
|
---|
350 |
|
---|
351 | def _on_menuitem_toggled(self, widget):
|
---|
352 | # Get the tab name
|
---|
353 | name = widget.get_child().get_text()
|
---|
354 | if name == "All":
|
---|
355 | if widget.get_active():
|
---|
356 | self.show_all_tabs()
|
---|
357 | else:
|
---|
358 | self.hide_all_tabs()
|
---|
359 | return
|
---|
360 |
|
---|
361 | self.set_tab_visible(name, widget.get_active())
|
---|
362 |
|
---|
363 | def save_state(self):
|
---|
364 | """We save the state, which is basically the tab_index list"""
|
---|
365 | filename = "tabs.state"
|
---|
366 | state = []
|
---|
367 | for tab in self.tabs:
|
---|
368 | state.append((self.tabs[tab].weight, self.tabs[tab].get_name(),
|
---|
369 | self.tabs[tab].is_visible))
|
---|
370 | # Sort by weight
|
---|
371 | state.sort()
|
---|
372 | state = [(n, v) for w, n, v in state]
|
---|
373 |
|
---|
374 | # Get the config location for saving the state file
|
---|
375 | config_location = deluge.configmanager.get_config_dir()
|
---|
376 |
|
---|
377 | try:
|
---|
378 | log.debug("Saving TorrentDetails state file: %s", filename)
|
---|
379 | state_file = open(os.path.join(config_location, filename), "wb")
|
---|
380 | cPickle.dump(state, state_file)
|
---|
381 | state_file.close()
|
---|
382 | except IOError, e:
|
---|
383 | log.warning("Unable to save state file: %s", e)
|
---|
384 |
|
---|
385 | def load_state(self):
|
---|
386 | filename = "tabs.state"
|
---|
387 | # Get the config location for loading the state file
|
---|
388 | config_location = deluge.configmanager.get_config_dir()
|
---|
389 | state = None
|
---|
390 |
|
---|
391 | try:
|
---|
392 | log.debug("Loading TorrentDetails state file: %s", filename)
|
---|
393 | state_file = open(os.path.join(config_location, filename), "rb")
|
---|
394 | state = cPickle.load(state_file)
|
---|
395 | state_file.close()
|
---|
396 | except (EOFError, IOError), e:
|
---|
397 | log.warning("Unable to load state file: %s", e)
|
---|
398 |
|
---|
399 | return state
|
---|