1 | #
|
---|
2 | # filtertreeview.py
|
---|
3 | #
|
---|
4 | # Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
|
---|
5 | # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
---|
6 | #
|
---|
7 | # Deluge is free software.
|
---|
8 | #
|
---|
9 | # You may redistribute it and/or modify it under the terms of the
|
---|
10 | # GNU General Public License, as published by the Free Software
|
---|
11 | # Foundation; either version 3 of the License, or (at your option)
|
---|
12 | # any later version.
|
---|
13 | #
|
---|
14 | # deluge is distributed in the hope that it will be useful,
|
---|
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
---|
17 | # See the GNU General Public License for more details.
|
---|
18 | #
|
---|
19 | # You should have received a copy of the GNU General Public License
|
---|
20 | # along with deluge. If not, write to:
|
---|
21 | # The Free Software Foundation, Inc.,
|
---|
22 | # 51 Franklin Street, Fifth Floor
|
---|
23 | # Boston, MA 02110-1301, USA.
|
---|
24 | #
|
---|
25 |
|
---|
26 |
|
---|
27 | import gtk
|
---|
28 | import gtk.glade
|
---|
29 | import pkg_resources
|
---|
30 |
|
---|
31 | import deluge.component as component
|
---|
32 | import deluge.common
|
---|
33 | from deluge.ui.tracker_icons import TrackerIcons
|
---|
34 | from deluge.log import LOG as log
|
---|
35 | from deluge.ui.client import client
|
---|
36 | from deluge.configmanager import ConfigManager
|
---|
37 |
|
---|
38 | STATE_PIX = {
|
---|
39 | "All": "all",
|
---|
40 | "Downloading": "downloading",
|
---|
41 | "Seeding": "seeding",
|
---|
42 | "Paused": "inactive",
|
---|
43 | "Checking": "checking",
|
---|
44 | "Queued": "queued",
|
---|
45 | "Error": "alert",
|
---|
46 | "Active": "active"
|
---|
47 | }
|
---|
48 |
|
---|
49 |
|
---|
50 | TRANSLATE = {
|
---|
51 | "state": "States",
|
---|
52 | "tracker_host": "Trackers",
|
---|
53 | "label": "Labels"
|
---|
54 | }
|
---|
55 |
|
---|
56 | FILTER_COLUMN = 5
|
---|
57 |
|
---|
58 | def _t(text):
|
---|
59 | if text in TRANSLATE:
|
---|
60 | text = TRANSLATE[text]
|
---|
61 | return _(text)
|
---|
62 |
|
---|
63 |
|
---|
64 | #sidebar-treeview
|
---|
65 | class FilterTreeView(component.Component):
|
---|
66 | def __init__(self):
|
---|
67 | component.Component.__init__(self, "FilterTreeView", interval=2)
|
---|
68 | self.window = component.get("MainWindow")
|
---|
69 | glade = self.window.main_glade
|
---|
70 | self.hpaned = glade.get_widget("hpaned")
|
---|
71 | self.scrolled = glade.get_widget("scrolledwindow_sidebar")
|
---|
72 | self.sidebar = component.get("SideBar")
|
---|
73 | self.config = ConfigManager("gtkui.conf")
|
---|
74 | self.tracker_icons = TrackerIcons()
|
---|
75 |
|
---|
76 | self.label_view = gtk.TreeView()
|
---|
77 | self.sidebar.add_tab(self.label_view, "filters", _("Filters"))
|
---|
78 |
|
---|
79 | #set filter to all when hidden:
|
---|
80 | self.sidebar.notebook.connect("hide", self._on_hide)
|
---|
81 |
|
---|
82 | #menu
|
---|
83 | glade_menu = gtk.glade.XML(pkg_resources.resource_filename("deluge.ui.gtkui",
|
---|
84 | "glade/filtertree_menu.glade"))
|
---|
85 | self.menu = glade_menu.get_widget("filtertree_menu")
|
---|
86 | glade_menu.signal_autoconnect({
|
---|
87 | "select_all": self.on_select_all,
|
---|
88 | "pause_all": self.on_pause_all,
|
---|
89 | "resume_all": self.on_resume_all
|
---|
90 | })
|
---|
91 |
|
---|
92 | self.default_menu_items = self.menu.get_children()
|
---|
93 |
|
---|
94 | # Create the liststore
|
---|
95 | #cat, value, label, count, pixmap, visible
|
---|
96 | self.treestore = gtk.TreeStore(str, str, str, int, gtk.gdk.Pixbuf, bool)
|
---|
97 |
|
---|
98 | # Create the column
|
---|
99 | column = gtk.TreeViewColumn(_("Filters"))
|
---|
100 | column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
|
---|
101 | render = gtk.CellRendererPixbuf()
|
---|
102 | self.renderpix = render
|
---|
103 | column.pack_start(render, expand=False)
|
---|
104 | column.add_attribute(render, 'pixbuf', 4)
|
---|
105 | render = gtk.CellRendererText()
|
---|
106 | column.pack_start(render, expand=False)
|
---|
107 | column.set_cell_data_func(render, self.render_cell_data,None)
|
---|
108 |
|
---|
109 | self.label_view.append_column(column)
|
---|
110 |
|
---|
111 | #style:
|
---|
112 | self.label_view.set_show_expanders(True)
|
---|
113 | self.label_view.set_headers_visible(False)
|
---|
114 | self.label_view.set_level_indentation(-35)
|
---|
115 |
|
---|
116 | self.label_view.set_model(self.treestore)
|
---|
117 | self.label_view.get_selection().connect("changed", self.on_selection_changed)
|
---|
118 | self.create_model_filter()
|
---|
119 |
|
---|
120 | #init.....
|
---|
121 | self.label_view.connect("button-press-event", self.on_button_press_event)
|
---|
122 |
|
---|
123 | #colors using current theme.
|
---|
124 | style = self.window.window.get_style()
|
---|
125 | self.colour_background = style.bg[gtk.STATE_NORMAL]
|
---|
126 | self.colour_foreground = style.fg[gtk.STATE_NORMAL]
|
---|
127 |
|
---|
128 | def start(self):
|
---|
129 | #add Cat nodes:
|
---|
130 | self.cat_nodes = {}
|
---|
131 | self.filters = {}
|
---|
132 |
|
---|
133 | #initial order of state filter:
|
---|
134 | self.cat_nodes["state"] = self.treestore.append(None, ["cat", "state", _("State"), 0, None, False])
|
---|
135 | self.update_row("state", "All" , 0)
|
---|
136 | self.update_row("state", "Downloading" , 0)
|
---|
137 | self.update_row("state", "Seeding" , 0)
|
---|
138 | self.update_row("state", "Active" , 0)
|
---|
139 | self.update_row("state", "Paused" , 0)
|
---|
140 | self.update_row("state", "Queued" , 0)
|
---|
141 |
|
---|
142 | # We set to this expand the rows on start-up
|
---|
143 | self.expand_rows = True
|
---|
144 |
|
---|
145 | self.selected_path = None
|
---|
146 |
|
---|
147 | def stop(self):
|
---|
148 | self.treestore.clear()
|
---|
149 |
|
---|
150 | def create_model_filter(self):
|
---|
151 | self.model_filter = self.treestore.filter_new()
|
---|
152 | self.model_filter.set_visible_column(FILTER_COLUMN)
|
---|
153 | self.label_view.set_model(self.model_filter)
|
---|
154 |
|
---|
155 | def cb_update_filter_tree(self, filter_items):
|
---|
156 | #create missing cat_nodes
|
---|
157 | for cat in filter_items:
|
---|
158 | if not cat in self.cat_nodes:
|
---|
159 | self.cat_nodes[cat] = self.treestore.append(None, ["cat", cat, _t(cat), 0, None, False])
|
---|
160 |
|
---|
161 | #update rows
|
---|
162 | visible_filters = []
|
---|
163 | for cat,filters in filter_items.iteritems():
|
---|
164 | for value, count in filters:
|
---|
165 | self.update_row(cat, value , count)
|
---|
166 | visible_filters.append((cat, value))
|
---|
167 |
|
---|
168 | # hide root-categories not returned by core-part of the plugin.
|
---|
169 | for cat in self.cat_nodes:
|
---|
170 | if cat in filter_items:
|
---|
171 | self.treestore.set_value(self.cat_nodes[cat], FILTER_COLUMN, True)
|
---|
172 | else:
|
---|
173 | self.treestore.set_value(self.cat_nodes[cat], FILTER_COLUMN, False)
|
---|
174 |
|
---|
175 | # hide items not returned by core-plugin.
|
---|
176 | for f in self.filters:
|
---|
177 | if not f in visible_filters:
|
---|
178 | self.treestore.set_value(self.filters[f], FILTER_COLUMN, False)
|
---|
179 |
|
---|
180 | if self.expand_rows:
|
---|
181 | self.label_view.expand_all()
|
---|
182 | self.expand_rows = False
|
---|
183 |
|
---|
184 | if not self.selected_path:
|
---|
185 | self.select_default_filter()
|
---|
186 |
|
---|
187 | def update_row(self, cat, value , count):
|
---|
188 | if (cat, value) in self.filters:
|
---|
189 | row = self.filters[(cat, value)]
|
---|
190 | self.treestore.set_value(row, 3, count)
|
---|
191 | else:
|
---|
192 | pix = self.get_pixmap(cat, value)
|
---|
193 | label = value
|
---|
194 | if cat == "state":
|
---|
195 | label = _(value)
|
---|
196 | row = self.treestore.append(self.cat_nodes[cat],[cat, value, label, count , pix, True])
|
---|
197 | self.filters[(cat, value)] = row
|
---|
198 |
|
---|
199 | if cat == "tracker_host" or cat == "label":
|
---|
200 | self.tracker_icons.get_async(value, lambda filename: self.set_row_image(cat, value, filename))
|
---|
201 |
|
---|
202 | self.treestore.set_value(row, FILTER_COLUMN, True)
|
---|
203 | return row
|
---|
204 |
|
---|
205 | def render_cell_data(self, column, cell, model, row, data):
|
---|
206 | "cell renderer"
|
---|
207 | cat = model.get_value(row, 0)
|
---|
208 | value = model.get_value(row, 1)
|
---|
209 | label = model.get_value(row, 2)
|
---|
210 | count = model.get_value(row, 3)
|
---|
211 | pix = model.get_value(row, 4)
|
---|
212 |
|
---|
213 | if label == "" and cat == "label":
|
---|
214 | label = _("no label")
|
---|
215 |
|
---|
216 | if pix:
|
---|
217 | self.renderpix.set_property("visible", True)
|
---|
218 | else:
|
---|
219 | self.renderpix.set_property("visible", False)
|
---|
220 |
|
---|
221 | if cat == "cat":
|
---|
222 | txt = label
|
---|
223 | cell.set_property("cell-background-gdk", self.colour_background)
|
---|
224 | cell.set_property("foreground-gdk", self.colour_foreground)
|
---|
225 | else:
|
---|
226 | txt = "%s (%s)" % (label, count)
|
---|
227 | cell.set_property("cell-background", None)
|
---|
228 | cell.set_property("foreground", None)
|
---|
229 |
|
---|
230 | cell.set_property('text', txt)
|
---|
231 |
|
---|
232 | def get_pixmap(self, cat, value):
|
---|
233 | if cat == "state":
|
---|
234 | pix = STATE_PIX.get(value, "dht")
|
---|
235 | return gtk.gdk.pixbuf_new_from_file(deluge.common.get_pixmap("%s16.png" % pix))
|
---|
236 |
|
---|
237 | return None
|
---|
238 |
|
---|
239 | def set_row_image(self, cat, value, filename):
|
---|
240 | pix = None
|
---|
241 | try: #assume we could get trashed images here..
|
---|
242 | pix = gtk.gdk.pixbuf_new_from_file_at_size(filename, 16, 16)
|
---|
243 | except Exception, e:
|
---|
244 | log.debug(e)
|
---|
245 |
|
---|
246 | if not pix:
|
---|
247 | pix = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 16, 16)
|
---|
248 | pix.fill(0x00000000)
|
---|
249 | row = self.filters[(cat, value)]
|
---|
250 | self.treestore.set_value(row, 4, pix)
|
---|
251 | return False
|
---|
252 |
|
---|
253 |
|
---|
254 | def on_selection_changed(self, selection):
|
---|
255 | try:
|
---|
256 | (model, row) = self.label_view.get_selection().get_selected()
|
---|
257 | if not row:
|
---|
258 | log.debug("nothing selected")
|
---|
259 | return
|
---|
260 |
|
---|
261 | cat = model.get_value(row, 0)
|
---|
262 | value = model.get_value(row, 1)
|
---|
263 |
|
---|
264 | filter_dict = {cat: [value]}
|
---|
265 | if value == "All" or cat == "cat":
|
---|
266 | filter_dict = {}
|
---|
267 |
|
---|
268 | component.get("TorrentView").set_filter(filter_dict)
|
---|
269 |
|
---|
270 | self.selected_path = model.get_path(row)
|
---|
271 |
|
---|
272 | except Exception, e:
|
---|
273 | log.debug(e)
|
---|
274 | # paths is likely None .. so lets return None
|
---|
275 | return None
|
---|
276 |
|
---|
277 | def update(self):
|
---|
278 | try:
|
---|
279 | hide_cat = []
|
---|
280 | if not self.config["sidebar_show_trackers"]:
|
---|
281 | hide_cat = ["tracker_host"]
|
---|
282 | client.core.get_filter_tree(self.config["sidebar_show_zero"], hide_cat).addCallback(self.cb_update_filter_tree)
|
---|
283 | except Exception, e:
|
---|
284 | log.debug(e)
|
---|
285 |
|
---|
286 |
|
---|
287 | ### Callbacks ###
|
---|
288 | def on_button_press_event(self, widget, event):
|
---|
289 | """This is a callback for showing the right-click context menu.
|
---|
290 | NOT YET!
|
---|
291 | """
|
---|
292 | x, y = event.get_coords()
|
---|
293 | path = self.label_view.get_path_at_pos(int(x), int(y))
|
---|
294 | if not path:
|
---|
295 | return
|
---|
296 | path = path[0]
|
---|
297 | cat = self.model_filter[path][0]
|
---|
298 |
|
---|
299 | if event.button == 1:
|
---|
300 | # Prevent selecting a category label
|
---|
301 | if cat == "cat":
|
---|
302 | if self.label_view.row_expanded(path):
|
---|
303 | self.label_view.collapse_row(path)
|
---|
304 | else:
|
---|
305 | self.label_view.expand_row(path, False)
|
---|
306 | if not self.selected_path:
|
---|
307 | self.select_default_filter()
|
---|
308 | else:
|
---|
309 | self.label_view.get_selection().select_path(self.selected_path)
|
---|
310 | return True
|
---|
311 |
|
---|
312 | elif event.button == 3:
|
---|
313 | #assign current cat, value to self:
|
---|
314 | x, y = event.get_coords()
|
---|
315 | path = self.label_view.get_path_at_pos(int(x), int(y))
|
---|
316 | if not path:
|
---|
317 | return
|
---|
318 | row = self.model_filter.get_iter(path[0])
|
---|
319 | self.cat = self.model_filter.get_value(row, 0)
|
---|
320 | self.value = self.model_filter.get_value(row, 1)
|
---|
321 | self.count = self.model_filter.get_value(row, 3)
|
---|
322 |
|
---|
323 | #Show the pop-up menu
|
---|
324 | self.set_menu_sensitivity()
|
---|
325 | self.menu.hide()
|
---|
326 | self.menu.popup(None, None, None, event.button, event.time)
|
---|
327 | self.menu.show()
|
---|
328 |
|
---|
329 | if cat == "cat":
|
---|
330 | # Do not select the row
|
---|
331 | return True
|
---|
332 |
|
---|
333 | def set_menu_sensitivity(self):
|
---|
334 | #select-all/pause/resume
|
---|
335 | sensitive = (self.cat != "cat" and self.count <> 0)
|
---|
336 | for item in self.default_menu_items:
|
---|
337 | item.set_sensitive(sensitive)
|
---|
338 |
|
---|
339 | def select_all(self):
|
---|
340 | "for use in popup menu"
|
---|
341 | component.get("TorrentView").treeview.get_selection().select_all()
|
---|
342 |
|
---|
343 | def on_select_all(self, event):
|
---|
344 | self.select_all()
|
---|
345 |
|
---|
346 | def on_pause_all(self, event):
|
---|
347 | self.select_all()
|
---|
348 | func = getattr(component.get("MenuBar"), "on_menuitem_%s_activate" % "pause")
|
---|
349 | func(event)
|
---|
350 |
|
---|
351 | def on_resume_all(self, event):
|
---|
352 | self.select_all()
|
---|
353 | func = getattr(component.get("MenuBar"), "on_menuitem_%s_activate" % "resume")
|
---|
354 | func(event)
|
---|
355 |
|
---|
356 | def _on_hide(self, *args):
|
---|
357 | self.select_default_filter()
|
---|
358 |
|
---|
359 | def select_default_filter(self):
|
---|
360 | row = self.filters[("state", "All")]
|
---|
361 | path = self.treestore.get_path(row)
|
---|
362 | self.label_view.get_selection().select_path(path)
|
---|