1
# -*- coding: utf-8 -*-
2
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
4
# Copyright (c) 2005-2006 The PIDA Project
6
#Permission is hereby granted, free of charge, to any person obtaining a copy
7
#of this software and associated documentation files (the "Software"), to deal
8
#in the Software without restriction, including without limitation the rights
9
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
#copies of the Software, and to permit persons to whom the Software is
11
#furnished to do so, subject to the following conditions:
13
#The above copyright notice and this permission notice shall be included in
14
#all copies or substantial portions of the Software.
16
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
from kiwi.utils import gsignal, gproperty
31
class TreeItem(object):
32
"""An item inside a tree-view."""
33
def __init__(self, key, value):
37
def __get_markup(self):
38
"""Return the markup for the item."""
40
markup = property(__get_markup)
43
"""Return the key for the treeview item."""
45
key = property(__get_key)
47
def __get_value(self):
48
"""Return the value for the tree view item."""
50
value = property(__get_value)
53
class QuestionBox(gtk.HBox):
56
gtk.HBox.__init__(self)
57
self.__entry = gtk.Entry()
58
self.pack_start(self.__entry)
59
self.__toolbar = toolbar.Toolbar()
60
self.pack_start(self.__toolbar)
61
self.__toolbar.add_button('ok', 'apply', 'ok')
62
self.__toolbar.add_button('cancel', 'cancel', 'cancel')
63
self.__toolbar.connect('clicked', self.cb_toolbar_clicked)
64
self.set_sensitive(False)
66
def cb_toolbar_clicked(self, toolbar, action):
68
self.__current_callback(self.__entry.get_text())
69
self.__entry.set_text('')
70
self.set_sensitive(False)
72
def question(self, callback, prompt='?'):
73
self.set_sensitive(True)
74
self.__current_callback = callback
78
"""A treeview control with niceness."""
79
#The fields for the model.
80
FIELDS = (gobject.TYPE_STRING,
81
gobject.TYPE_PYOBJECT,
84
#The columns for the view.
85
COLUMNS = [[gtk.CellRendererText, 'markup', 2],
86
[gtk.CellRendererText, '', '']]
88
#The signals for the widget.
89
__gsignals__ = {'clicked' : (
90
gobject.SIGNAL_RUN_LAST,
92
(gobject.TYPE_PYOBJECT,)),
94
gobject.SIGNAL_RUN_LAST,
96
(gobject.TYPE_PYOBJECT,)),
98
gobject.SIGNAL_RUN_LAST,
100
(gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
102
gobject.SIGNAL_RUN_LAST,
104
(gobject.TYPE_PYOBJECT,)),
106
gobject.SIGNAL_RUN_LAST,
110
gobject.SIGNAL_RUN_LAST,
114
gobject.SIGNAL_RUN_LAST,
118
gproperty('has-edit-box', bool, default=True)
119
gproperty('headers-visible', bool, default=True)
120
gproperty('icons-visible', bool, default=False)
121
gproperty('markup-format-string', str, default='%(key)s')
123
def do_get_property(self, prop):
124
return self.__properties.setdefault(prop.name, prop.default_value)
126
def do_set_property(self, prop, value):
127
self.__properties[prop.name] = value
132
HEADERS_VISIBLE = False
136
SORT_AVAILABLE = None
137
SORT_CONTROLS = False
142
self.__init_signals()
143
self.__properties = {}
145
def __init_view(self):
146
"""Load the widgets."""
147
gtk.VBox.__init__(self)
148
self.__sw = gtk.ScrolledWindow()
149
self.pack_start(self.__sw)
150
self.__sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
151
self.__view = gtk.TreeView(self.__model)
152
self.__view.set_rules_hint(True)
153
self.__view.set_enable_search(False)
154
self.__view.set_model(self.__model)
155
self.__sw.add(self.__view)
156
self.__view.set_headers_visible(False)
157
for column in self.__init_renderers():
158
self.__view.append_column(column)
159
if self.SORT_CONTROLS == True:
161
self.pack_start(sb, expand=False)
162
l = gtk.Label('Sort')
163
sb.set_label_widget(l)
166
self.__sortcombo = gtk.combo_box_new_text()
167
hb.pack_start(self.__sortcombo)
168
if self.SORT_AVAILABLE is not None:
169
self.sort_available = dict(self.SORT_AVAILABLE)
170
for attr, val in self.SORT_AVAILABLE:
171
self.__sortcombo.append_text(attr)
172
self.__sortdir = gtk.ToggleToolButton(
173
stock_id=gtk.STOCK_SORT_DESCENDING)
174
hb.pack_start(self.__sortdir, expand=False)
175
self.__sortdir.set_active(True)
176
self.__sortcombo.set_active(0)
177
self.__sortdir.connect('toggled', self.cb_sortdir_toggled)
178
self.__sortcombo.connect('changed', self.cb_sortcombo_changed)
179
self.__user_initiated_sort()
180
if self.EDIT_BOX == True:
181
self.__editbox = QuestionBox()
182
self.pack_start(self.__editbox, expand=False)
183
if self.EDIT_BUTTONS == True:
184
self.__toolbar = toolbar.Toolbar()
185
self.__toolbar.add_button('new', 'new', 'new', True)
186
self.__toolbar.add_button('delete', 'delete', 'delete', True)
187
self.__toolbar.add_button('edit', 'edit', 'Edit this item.', True)
188
self.__toolbar.connect('clicked', self.cb_toolbar_clicked)
189
self.pack_start(self.__toolbar, expand=False)
190
self.__init_signals()
191
if self.SORT_BY is not None:
192
self.sort_by([self.SORT_BY])
193
if self.SORT_LIST is not None:
194
self.sort_by(self.SORT_LIST)
197
def __init_model(self):
198
"""Initialise and return the model for the data."""
199
self.__model = gtk.TreeStore(*self.FIELDS)
202
def __init_signals(self):
203
def cb_toolbar_clicked(toolbar, action):
205
self.emit('new-item')
206
elif action == 'delete':
207
self.emit('delete-item')
208
elif action == 'edit':
209
self.emit('edit-item')
210
self.__toolbar.emit_stop_by_name('clicked')
211
if self.EDIT_BUTTONS == True:
212
self.__toolbar.connect('clicked', cb_toolbar_clicked)
213
def cb_cursor_changed(view):
214
self.emit('clicked', self.selected)
215
self.__view.emit_stop_by_name('cursor-changed')
216
self.__view.connect('cursor-changed', cb_cursor_changed)
217
def cb_row_activated(view, path, column):
218
self.emit('double-clicked', self.selected)
219
self.__view.emit_stop_by_name('row-activated')
220
self.__view.connect_after('row-activated', cb_row_activated)
221
def cb_button_press_event(source, event):
222
if event.button == 3:
223
pathinf = self.__view.get_path_at_pos(int(event.x), int(event.y))
224
self.__view.emit_stop_by_name('button-press-event')
225
if pathinf is not None:
226
path, col, cellx, celly = pathinf
227
ite = self.__model.get_iter(path)
228
item = self.__model.get_value(ite, 1)
229
self.__view.grab_focus()
230
self.__view.set_cursor(path, None, 0)
231
self.emit('right-clicked', item, event)
233
self.emit('right-clicked', None, event)
235
if event.button == 2:
236
pathinf = self.__view.get_path_at_pos(int(event.x), int(event.y))
237
if pathinf is not None:
238
path, col, cellx, celly = pathinf
239
self.__view.grab_focus()
240
self.__view.set_cursor(path, None, 0)
241
self.__view.emit_stop_by_name('button-press-event')
242
self.emit('middle-clicked', self.selected)
244
self.__view.add_events(gtk.gdk.BUTTON_PRESS_MASK)
245
self.__view.connect('button-press-event', cb_button_press_event)
247
def __init_renderers(self):
248
"""Initialise the renderers."""
249
for renderer_type, attribute, field in self.COLUMNS:
250
renderer = renderer_type()
251
renderer.set_property('ypad', 1)
253
kw = {attribute:field}
256
column = gtk.TreeViewColumn(attribute, renderer, **kw)
257
column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
260
def add_item(self, item, key=None, parent=None):
261
"""Add an item to the tree."""
266
titem = TreeItem(key, item)
267
row = [key, titem, self.__get_markup(item)]
268
niter = self.model.append(parent, row)
270
self.model.set_value(niter, 2, self.get_markup(item))
271
titem.reset_markup = reset
272
item.reset_markup = reset
275
def __get_markup(self, item):
277
markup_fmt = self.get_property('markup-format-string')
280
unmangled[k] = getattr(item, k)
281
markup_string = markup_fmt % unmangled
283
get_markup = __get_markup
285
def add_items(self, items):
286
"""Add items to the tree."""
291
"""Clear the tree."""
295
"""Removes the currently selected item from the tree."""
296
selected_path = self.__get_selected_path()
298
self.__model.emit('row-deleted', selected_path)
300
def set_items(self, items):
301
"""Set the items in the tree from the item list."""
303
self.add_items(items)
305
def question(self, callback, prompt):
306
self.__editbox.question(callback, prompt)
308
def sort_by(self, attrnames,
309
sortcolid=0, columnid=1,
311
def comparemethod(model, iter1, iter2):
312
v1 = model.get_value(iter1, columnid)
313
v2 = model.get_value(iter2, columnid)
314
def cmpvs(attrname, v1, v2):
317
attr1 = getattr(v1.value, attrname, None)
319
attr2 = getattr(v2.value, attrname, None)
320
if attr1 is None and attr2 is None:
327
return cmp(attr2, attr1)
328
for attrname in attrnames + ['key']:
329
compd = cmpvs(attrname, v1, v2)
331
return direction * compd
333
self.__model.set_sort_column_id(sortcolid, gtk.SORT_DESCENDING)
334
self.__model.set_sort_func(sortcolid, comparemethod)
335
#self.__view.set_model(self.__model)
337
def __get_model(self):
338
"""Return the Tree Model."""
340
model = property(__get_model)
342
def __get_view(self):
344
view = property(__get_view)
346
def __get_selected(self, column=1):
347
"""Get the selected item."""
348
ite = self.__get_selected_iter()
350
return self.__get(ite, column)
351
selected = property(__get_selected)
353
def get_selected_key(self):
354
return self.__get_selected(0)
355
selected_key = property(get_selected_key)
357
def set_selected(self, key):
358
"""Set the selected item to the first item matching the key."""
360
for row in self.model:
362
self.__view.set_cursor(row.path)
366
def __get_selected_iter(self):
367
"""Get the selected Tree Model Iter."""
368
path = self.__get_selected_path()
370
return self.__model.get_iter(path)
371
selected_iter = property(__get_selected_iter)
373
def __get_selected_path(self):
374
"""Get the selected Tree View Path."""
375
return self.__view.get_cursor()[0]
376
selected_path = property(__get_selected_path)
378
def __get(self, niter, column):
379
"""Get a cell's vlue from the Tree Model."""
380
return self.__model.get_value(niter, column)
383
def __set(self, niter, column, value):
384
self.__model.set_value(niter, column, value)
387
def cb_toolbar_clicked(self, toolbar, action):
389
self.emit('new-item')
390
elif action == 'edit':
391
self.emit('edit-item')
392
elif action == 'delete':
393
self.emit('delete-item')
394
toolbar.emit_stop_by_name('clicked')
396
def cb_sortcombo_changed(self, combo):
397
self.__user_initiated_sort()
399
def cb_sortdir_toggled(self, toggle):
400
self.__user_initiated_sort()
402
def __user_initiated_sort(self):
403
text = self.__sortcombo.get_active_text()
404
if self.__sortdir.get_active():
405
direction = 1#gtk.SORT_DESCENDING
407
direction = -1#gtk.SORT_ASCENDING
408
self.sort_by([self.sort_available[text]], direction=direction)
410
gobject.type_register(Tree)
412
class IconTreeItem(TreeItem):
413
"""I tree item with an icon."""
414
def __init__(self, key, value, image=None, icon_filename=None):
415
TreeItem.__init__(self, key, value)
416
if image is not None:
418
elif icon_filename is not None:
419
self.__image = gtk.Image()
420
self.__image.set_from_file(icon_filename)
422
self.__image = gtk.Image()
426
icon = property(get_icon)
428
def get_pixbuf(self):
429
return getattr(self.icon, 'get_pixbuf', lambda: self.icon)()
430
pixbuf = property(get_pixbuf)
433
class IconTree(Tree):
434
"""Tree with icons."""
435
FIELDS = (gobject.TYPE_STRING,
436
gobject.TYPE_PYOBJECT,
440
COLUMNS = [[gtk.CellRendererPixbuf, 'pixbuf', 3],
441
[gtk.CellRendererText, 'markup', 2]]
443
def add_item(self, item, key=None, parent=None):
449
titem = TreeItem(key, item)
450
row = [key, titem, self.get_markup(item), pixbuf]
451
niter = self.model.append(parent, row)
453
self.model.set_value(niter, 2, self.get_markup(item))
454
self.model.set_value(niter, 3, item.pixbuf)
455
titem.reset_markup = reset
456
item.reset_markup = reset
459
class ToggleTree(Tree):
461
FIELDS = (gobject.TYPE_STRING,
462
gobject.TYPE_PYOBJECT,
464
gobject.TYPE_BOOLEAN)
466
COLUMNS = [[gtk.CellRendererText, 'markup', 2],
467
[gtk.CellRendererToggle, 'active', 3] ]
470
def add_item(self, item, key=None, parent=None):
476
titem = TreeItem(key, item)
477
row = [key, titem, self.get_markup(item), active]
478
niter = self.model.append(parent, row)
480
self.model.set_value(niter, 2, self.get_markup(item))
481
self.model.set_value(niter, 3, item.active)
482
titem.reset_markup = reset
483
item.reset_markup = reset
492
b = gtk.Button('foo')
493
v.pack_start(b, False)
496
def _clicked(button):
497
t.selected.value.key = 'ali'
498
t.selected.reset_markup()
499
b.connect('clicked', _clicked)