source: deluge/ui/gtkui/filtertreeview.py@ 7b72d7

2.0.x develop extjs4-port
Last change on this file since 7b72d7 was 7b72d7, checked in by Andrew Resch <andrewresch@gmail.com>, 16 years ago

Made TrackerIcons a component to prevent trying to get an icon multiple
times
Fixed showing the wrong tracker icon in the TorrentView when the icon
could not be retrieved from the tracker

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