~ubuntu-branches/ubuntu/utopic/pida/utopic

« back to all changes in this revision

Viewing changes to pida/utils/pgd/tree.py

  • Committer: Bazaar Package Importer
  • Author(s): Jan Luebbe
  • Date: 2007-04-17 16:08:06 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20070417160806-3ttlb6igf94x9i03
Tags: 0.4.4-1
* New upstream release (closes: #419129)
* Add dependency on python-glade2 (closes: #418716)
* Update copyright

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*- 
 
2
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
 
3
 
 
4
# Copyright (c) 2005-2006 The PIDA Project
 
5
 
 
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:
 
12
 
 
13
#The above copyright notice and this permission notice shall be included in
 
14
#all copies or substantial portions of the Software.
 
15
 
 
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
 
22
#SOFTWARE.
 
23
 
 
24
# gtk import(s)
 
25
import gtk
 
26
import gobject
 
27
 
 
28
from kiwi.utils import gsignal, gproperty
 
29
 
 
30
 
 
31
class TreeItem(object):
 
32
    """An item inside a tree-view."""
 
33
    def __init__(self, key, value):
 
34
        self.__key = key
 
35
        self.__value = value
 
36
 
 
37
    def __get_markup(self):
 
38
        """Return the markup for the item."""
 
39
        return self.__value
 
40
    markup = property(__get_markup)
 
41
 
 
42
    def __get_key(self):
 
43
        """Return the key for the treeview item."""
 
44
        return self.__key
 
45
    key = property(__get_key)
 
46
 
 
47
    def __get_value(self):
 
48
        """Return the value for the tree view item."""
 
49
        return self.__value
 
50
    value = property(__get_value)
 
51
 
 
52
    
 
53
class QuestionBox(gtk.HBox):
 
54
 
 
55
    def __init__(self):
 
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)
 
65
 
 
66
    def cb_toolbar_clicked(self, toolbar, action):
 
67
        if action == 'ok':
 
68
            self.__current_callback(self.__entry.get_text())
 
69
        self.__entry.set_text('')
 
70
        self.set_sensitive(False)
 
71
 
 
72
    def question(self, callback, prompt='?'):
 
73
        self.set_sensitive(True)
 
74
        self.__current_callback = callback
 
75
 
 
76
 
 
77
class Tree(gtk.VBox):
 
78
    """A treeview control with niceness."""
 
79
    #The fields for the model.
 
80
    FIELDS = (gobject.TYPE_STRING,
 
81
              gobject.TYPE_PYOBJECT,
 
82
              gobject.TYPE_STRING)
 
83
 
 
84
    #The columns for the view.
 
85
    COLUMNS = [[gtk.CellRendererText, 'markup', 2],
 
86
               [gtk.CellRendererText, '', '']]
 
87
 
 
88
    #The signals for the widget.
 
89
    __gsignals__ = {'clicked' : (
 
90
                        gobject.SIGNAL_RUN_LAST,
 
91
                        gobject.TYPE_NONE,
 
92
                        (gobject.TYPE_PYOBJECT,)),
 
93
                    'double-clicked' : (
 
94
                        gobject.SIGNAL_RUN_LAST,
 
95
                        gobject.TYPE_NONE,
 
96
                        (gobject.TYPE_PYOBJECT,)),
 
97
                    'right-clicked' : (
 
98
                        gobject.SIGNAL_RUN_LAST,
 
99
                        gobject.TYPE_NONE,
 
100
                        (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)),
 
101
                    'middle-clicked' : (
 
102
                        gobject.SIGNAL_RUN_LAST,
 
103
                        gobject.TYPE_NONE,
 
104
                        (gobject.TYPE_PYOBJECT,)),
 
105
                    'new-item' : (
 
106
                        gobject.SIGNAL_RUN_LAST,
 
107
                        gobject.TYPE_NONE,
 
108
                        ()),
 
109
                    'delete-item' : (
 
110
                        gobject.SIGNAL_RUN_LAST,
 
111
                        gobject.TYPE_NONE,
 
112
                        ()),
 
113
                    'edit-item' : (
 
114
                        gobject.SIGNAL_RUN_LAST,
 
115
                        gobject.TYPE_NONE,
 
116
                        ())}
 
117
 
 
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')
 
122
 
 
123
    def do_get_property(self, prop):
 
124
        return self.__properties.setdefault(prop.name, prop.default_value)
 
125
 
 
126
    def do_set_property(self, prop, value):
 
127
        self.__properties[prop.name] = value
 
128
 
 
129
    EDIT_BUTTONS = False
 
130
    EDIT_BOX = False
 
131
 
 
132
    HEADERS_VISIBLE = False
 
133
 
 
134
    SORT_BY = None
 
135
    SORT_LIST = None
 
136
    SORT_AVAILABLE = None
 
137
    SORT_CONTROLS = False
 
138
 
 
139
    def __init__(self):
 
140
        self.__init_model()
 
141
        self.__init_view()
 
142
        self.__init_signals()
 
143
        self.__properties = {}
 
144
 
 
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:
 
160
            sb = gtk.Expander()
 
161
            self.pack_start(sb, expand=False)
 
162
            l = gtk.Label('Sort')
 
163
            sb.set_label_widget(l)
 
164
            hb = gtk.HBox()
 
165
            sb.add(hb)
 
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)
 
195
        self.show_all()
 
196
 
 
197
    def __init_model(self):
 
198
        """Initialise and return the model for the data."""
 
199
        self.__model = gtk.TreeStore(*self.FIELDS)
 
200
        return self.__model
 
201
 
 
202
    def __init_signals(self):
 
203
        def cb_toolbar_clicked(toolbar, action):
 
204
            if action == 'new':
 
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)
 
232
                else:
 
233
                    self.emit('right-clicked', None, event)
 
234
                return True
 
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)
 
243
                    return True
 
244
        self.__view.add_events(gtk.gdk.BUTTON_PRESS_MASK)
 
245
        self.__view.connect('button-press-event', cb_button_press_event)
 
246
 
 
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)
 
252
            if attribute:
 
253
                kw = {attribute:field}
 
254
            else:
 
255
                kw = {}
 
256
            column = gtk.TreeViewColumn(attribute, renderer, **kw)
 
257
            column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
258
            yield column
 
259
 
 
260
    def add_item(self, item, key=None, parent=None):
 
261
        """Add an item to the tree."""
 
262
        if key is None:
 
263
            key = item.key
 
264
        else:
 
265
            item.key = key
 
266
        titem = TreeItem(key, item)
 
267
        row = [key, titem, self.__get_markup(item)]
 
268
        niter = self.model.append(parent, row)
 
269
        def reset():
 
270
            self.model.set_value(niter, 2, self.get_markup(item))
 
271
        titem.reset_markup = reset
 
272
        item.reset_markup = reset
 
273
        return niter
 
274
 
 
275
    def __get_markup(self, item):
 
276
        unmangled = {}
 
277
        markup_fmt = self.get_property('markup-format-string')
 
278
        for k in dir(item):
 
279
            if k in markup_fmt:
 
280
                unmangled[k] = getattr(item, k)
 
281
        markup_string = markup_fmt % unmangled
 
282
        return markup_string
 
283
    get_markup = __get_markup
 
284
    
 
285
    def add_items(self, items):
 
286
        """Add items to the tree."""
 
287
        for item in items:
 
288
            self.add_item(item)
 
289
 
 
290
    def clear(self):
 
291
        """Clear the tree."""
 
292
        self.__model.clear()
 
293
 
 
294
    def del_item(self):
 
295
        """Removes the currently selected item from the tree."""
 
296
        selected_path = self.__get_selected_path()
 
297
        if selected_path:
 
298
            self.__model.emit('row-deleted', selected_path)
 
299
        
 
300
    def set_items(self, items):
 
301
        """Set the items in the tree from the item list."""
 
302
        self.clear()
 
303
        self.add_items(items)
 
304
 
 
305
    def question(self, callback, prompt):
 
306
        self.__editbox.question(callback, prompt)
 
307
 
 
308
    def sort_by(self, attrnames,
 
309
                sortcolid=0, columnid=1,
 
310
                direction=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):
 
315
                attr1 = attr2 = None
 
316
                if v1 is not None:
 
317
                    attr1 = getattr(v1.value, attrname, None)
 
318
                if v2 is not None:
 
319
                    attr2 = getattr(v2.value, attrname, None)
 
320
                if attr1 is None and attr2 is None:
 
321
                    return 0
 
322
                elif attr1 is None:
 
323
                    return 0
 
324
                elif attr2 is None:
 
325
                    return 0
 
326
                else:
 
327
                    return cmp(attr2, attr1)
 
328
            for attrname in attrnames + ['key']:
 
329
                compd = cmpvs(attrname, v1, v2)
 
330
                if compd != 0:
 
331
                    return direction * compd
 
332
            return 0
 
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)
 
336
 
 
337
    def __get_model(self):
 
338
        """Return the Tree Model."""
 
339
        return self.__model
 
340
    model = property(__get_model)
 
341
 
 
342
    def __get_view(self):
 
343
        return self.__view
 
344
    view = property(__get_view)
 
345
    
 
346
    def __get_selected(self, column=1):
 
347
        """Get the selected item."""
 
348
        ite = self.__get_selected_iter()
 
349
        if ite:
 
350
            return self.__get(ite, column)
 
351
    selected = property(__get_selected)
 
352
    
 
353
    def get_selected_key(self):
 
354
        return self.__get_selected(0)
 
355
    selected_key = property(get_selected_key)
 
356
 
 
357
    def set_selected(self, key):
 
358
        """Set the selected item to the first item matching the key."""
 
359
        key = str(key)
 
360
        for row in self.model:
 
361
            if row[0] == key:
 
362
                self.__view.set_cursor(row.path)
 
363
                return True
 
364
        return False
 
365
 
 
366
    def __get_selected_iter(self):
 
367
        """Get the selected Tree Model Iter."""
 
368
        path = self.__get_selected_path()
 
369
        if path:
 
370
            return self.__model.get_iter(path)
 
371
    selected_iter = property(__get_selected_iter)
 
372
        
 
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)
 
377
 
 
378
    def __get(self, niter, column):
 
379
        """Get a cell's vlue from the Tree Model."""
 
380
        return self.__model.get_value(niter, column)
 
381
    get = __get
 
382
 
 
383
    def __set(self, niter, column, value):
 
384
        self.__model.set_value(niter, column, value)
 
385
    set = __set
 
386
 
 
387
    def cb_toolbar_clicked(self, toolbar, action):
 
388
        if action == 'new':
 
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')
 
395
 
 
396
    def cb_sortcombo_changed(self, combo):
 
397
        self.__user_initiated_sort()
 
398
 
 
399
    def cb_sortdir_toggled(self, toggle):
 
400
        self.__user_initiated_sort()
 
401
 
 
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
 
406
        else:
 
407
            direction = -1#gtk.SORT_ASCENDING
 
408
        self.sort_by([self.sort_available[text]], direction=direction)
 
409
 
 
410
gobject.type_register(Tree)
 
411
 
 
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:
 
417
            self.__image = image
 
418
        elif icon_filename is not None:
 
419
            self.__image = gtk.Image()
 
420
            self.__image.set_from_file(icon_filename)
 
421
        else:
 
422
            self.__image = gtk.Image()
 
423
 
 
424
    def get_icon(self):
 
425
        return self.__image
 
426
    icon = property(get_icon)
 
427
 
 
428
    def get_pixbuf(self):
 
429
        return getattr(self.icon, 'get_pixbuf', lambda: self.icon)()
 
430
    pixbuf = property(get_pixbuf)
 
431
 
 
432
 
 
433
class IconTree(Tree):
 
434
    """Tree with icons.""" 
 
435
    FIELDS = (gobject.TYPE_STRING,
 
436
              gobject.TYPE_PYOBJECT,
 
437
              gobject.TYPE_STRING,
 
438
              gtk.gdk.Pixbuf)
 
439
    
 
440
    COLUMNS = [[gtk.CellRendererPixbuf, 'pixbuf', 3],
 
441
               [gtk.CellRendererText, 'markup', 2]]
 
442
 
 
443
    def add_item(self, item, key=None, parent=None):
 
444
        pixbuf = item.pixbuf
 
445
        if key is None:
 
446
            key = item.key
 
447
        else:
 
448
            item.key = key
 
449
        titem = TreeItem(key, item)
 
450
        row = [key, titem, self.get_markup(item), pixbuf]
 
451
        niter = self.model.append(parent, row)
 
452
        def reset():
 
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
 
457
        return niter
 
458
 
 
459
class ToggleTree(Tree):
 
460
 
 
461
    FIELDS = (gobject.TYPE_STRING,
 
462
              gobject.TYPE_PYOBJECT,
 
463
              gobject.TYPE_STRING,
 
464
              gobject.TYPE_BOOLEAN)
 
465
    
 
466
    COLUMNS = [[gtk.CellRendererText, 'markup', 2],
 
467
               [gtk.CellRendererToggle, 'active', 3] ]
 
468
 
 
469
    
 
470
    def add_item(self, item, key=None, parent=None):
 
471
        active = item.active
 
472
        if key is None:
 
473
            key = item.key
 
474
        else:
 
475
            item.key = key
 
476
        titem = TreeItem(key, item)
 
477
        row = [key, titem, self.get_markup(item), active]
 
478
        niter = self.model.append(parent, row)
 
479
        def reset():
 
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
 
484
        return niter
 
485
 
 
486
def test():
 
487
    w = gtk.Window()
 
488
    v = gtk.VBox()
 
489
    w.add(v)
 
490
    t = Tree()
 
491
    v.pack_start(t)
 
492
    b = gtk.Button('foo')
 
493
    v.pack_start(b, False)
 
494
    w.show_all()
 
495
 
 
496
    def _clicked(button):
 
497
        t.selected.value.key = 'ali'
 
498
        t.selected.reset_markup()
 
499
    b.connect('clicked', _clicked)
 
500
 
 
501
    class Dummy(object):
 
502
        key = "fooood"
 
503
        name = 'mess'
 
504
    for i in range(20):
 
505
        d = Dummy()
 
506
        t.add_item(d)
 
507
 
 
508
    gtk.main()
 
509