1'''
2Defines behavior of the accessible tree view widget.
3
4@author: Peter Parente
5@author: Eitan Isaacson
6@organization: IBM Corporation
7@copyright: Copyright (c) 2006, 2007 IBM Corporation
8@license: BSD
9
10All rights reserved. This program and the accompanying materials are made
11available under the terms of the BSD which accompanies this distribution, and
12is available at U{http://www.opensource.org/licenses/bsd-license.php}
13'''
14
15from gi.repository import Gtk as gtk
16from gi.repository import Gdk as gdk
17from gi.repository import GdkPixbuf
18from gi.repository import GLib
19from gi.repository import GObject
20
21import pyatspi
22import os
23from . import ui_manager
24from time import sleep
25from .icons import getIcon
26from .node import Node
27from .tools import ToolsAccessor, getTreePathBoundingBox
28from .i18n import _
29
30COL_ICON = 0
31COL_NAME = 1
32COL_ROLE = 2
33COL_CHILDCOUNT = 3
34COL_FILLED = 4
35COL_DUMMY = 5
36COL_ACC = 6
37
38class AccessibleModel(gtk.TreeStore, ToolsAccessor):
39  '''
40  Stores the desktop accessible tree. Only populates sections of the tree
41  that are being viewed. This cuts short on a lot of potential overhead.
42
43  @ivar desktop: The desktop accessible. It holds references to all the
44  application L{Accessibility.Accessible}s
45  @type desktop: L{Accessibility.Accessible}
46  @ivar acc_cache: A list of L{Accessibility.Accessible}s that are currently
47  resident in the model. This helps with faster searching.
48  @type acc_cache: list
49  '''
50  __gsignals__ = {'row-filled' :
51                  (GObject.SignalFlags.RUN_FIRST,
52                   None,
53                   (GObject.TYPE_PYOBJECT,)),
54                  'start-populating' :
55                    (GObject.SignalFlags.RUN_FIRST,
56                     None,
57                     ()),
58                  'end-populating' :
59                    (GObject.SignalFlags.RUN_FIRST,
60                     None,
61                     ())}
62
63  def __init__(self, desktop_acc):
64    '''
65    Initializes the L{AccessibleModel} and all of the needed instant variables.
66    Connects required signals.
67    '''
68    self.acc_cache = [desktop_acc]
69    gtk.TreeStore.__init__(self, GdkPixbuf.Pixbuf, str, str, int, bool, bool, object)
70    self.connect('row-changed', self._onRowChanged)
71    self.connect('row-filled', self._onRowFilled)
72    self.desktop = desktop_acc
73    self._path_to_populate = None
74    self._populating_tasks = 0
75
76  def _onRowChanged(self, model, path, iter):
77    '''
78    A callback on "row-changed" that pre-populates a given row when it changes.
79
80    @param model: This model.
81    @type model: L{AccessibleModel}
82    @param path: The path to the row that changed.
83    @type path: tuple
84    @param iter: the iter of the row that changed.
85    @type iter: L{gtk.TreeIter}
86    '''
87    self._prepopLevel(model[iter][COL_ACC], iter)
88
89  def insert(self, iter, index, row):
90    '''
91    A method that overrides the L{gtk.TreeStore} insert method.
92    Pre-populate and add the new accessible to acc_cache.
93
94    @param iter: The parent iter of the newly inserted row.
95    @type iter: L{gtk.TreeIter}
96    @param index: The place in which to insert the new row.
97    @type index: integer
98    @param row: A list of columns o insert into the row,
99    @type row: list
100
101    @return: The newly created iter of the inserted row.
102    @rtype: L{gtk.TreeIter}
103    '''
104    acc = row[COL_ACC]
105    if acc:
106      self.acc_cache.append(acc)
107    new_iter = gtk.TreeStore.insert(self, iter, index, row)
108    self._prepopLevel(acc, new_iter)
109
110  def append(self, iter, row):
111    '''
112    A method that overrides the L{gtk.TreeStore} append method.
113    Pre-populate and add the new accessible to acc_cache.
114
115    @param iter: The parent iter of the newly inseted row.
116    @type iter: L{gtk.TreeIter}
117    @param row: A list of columns o insert into the row,
118    @type row: list
119
120    @return: The newly created iter of the inserted row.
121    @rtype: L{gtk.TreeIter}
122    '''
123    self.insert(iter, -1, row)
124
125  def remove(self, iter):
126    '''
127    A method that overrides the L{gtk.TreeStore} remove method.
128    Remove the row's accessible from acc_cache.
129
130    @param iter: The parent iter of the newly inserted row.
131    @type iter: L{gtk.TreeIter}
132
133    @return: True if L{iter} is still valid.
134    @rtype: boolean
135    '''
136    if self[iter][COL_ACC]:
137      self.acc_cache.remove(self[iter][COL_ACC])
138    return gtk.TreeStore.remove(self, iter)
139
140  def isInModel(self, acc):
141    '''
142    Checks if the given L{Accessibility.Accessible} is resident in the model.
143
144    @param acc: The L{Accessibility.Accessible} to check.
145    @type acc: L{Accessibility.Accessible}
146
147    @return: True if it is in the model.
148    @rtype: boolean
149    '''
150    return acc in self.acc_cache
151
152  def getChildrenAccs(self, iter):
153    '''
154    Get list of accessible children of a given row.
155
156    I{Note:} This method returns the accessible's children as they currently exist in
157    the model. The list of accessibles is not necessarily identical to the actual
158    children of the accessible at the given row.
159
160    @param iter: Th iter of the row that we want it's children
161    @type iter: L{gtk.TreeIter}
162
163    @return: List of children
164    @rtype: list
165    '''
166    if iter:
167      return [row[COL_ACC] for row in self[iter].iterchildren()]
168    else:
169      return [row[COL_ACC] for row in self]
170
171  def _prepopLevel(self, parent, iter):
172    '''
173    Pre-populate a row. If a L{Accessibility.Accessible} of the given row has children,
174    we need to add to it one dummy child row so that the expander will show and
175    enable the user to expand it. We populate the children rows at expansion time.
176
177    @param parent: The given row's accessible.
178    @type parent: L{Accessibility.Accessible}
179    @param iter: Th iter of the row that needs to be pre-populated
180    @type iter: L{gtk.TreeIter}
181    '''
182    if (parent and self.children_number(parent) > 0 and
183        not self.isMyApp(parent) and self.iter_n_children(iter) == 0):
184      row = self._buildRow(None, True)
185      self.append(iter, row)
186
187  def popLevel(self, iter):
188    '''
189    Populate a row with children rows, according to the row's accessible's children.
190
191    @param iter: Th iter of the row that needs to be populated
192    @type iter: L{gtk.TreeIter}
193    '''
194    if iter:
195      tree_path = self.get_path(iter)
196      path = tuple(tree_path.get_indices())
197
198      row_reference = gtk.TreeRowReference.new(self, tree_path)
199      #row_reference = gtk.TreeRowReference()
200    else:
201      row_reference = None
202    self._populating_tasks += 1
203    if self._populating_tasks == 1:
204      self.emit('start-populating')
205    GLib.idle_add(self._popOnIdle, row_reference)
206
207  def _childrenIndexesInParent(self, accessible):
208    '''
209    Gets ids from children (either all of them or only those that are not leaves).
210
211    @param accessible: An accessible from which we want to get children ids.
212    @type accessible: L{Accessibility.Accessible}
213
214    @return: A list with children ids.
215    @rtype: list
216    '''
217    return [i for i in range(accessible.childCount)]
218
219  def _selectChild(self, parent, index):
220    '''
221    Returns a child id.
222
223    @param parent: An accessible from which we want to select a child id.
224    @type parent: L{Accessibility.Accessible}
225    @param index: An index to retrieve the child id. In some cases, it is equal to the id.
226    @type index: integer
227
228    @return: A child id.
229    @rtype: integer
230    '''
231    return index
232
233  def children_number(self, parent):
234    '''
235    Returns how many children an accessible has (either all of them or only those that are not leaves).
236
237    @param parent: An accessible from which we want to get the number of children.
238    @type parent: L{Accessibility.Accessible}
239
240    @return: The number of children (including leaves or not) an accessible has.
241    @rtype: integer
242    '''
243    return parent.childCount
244
245  def _popOnIdle(self, row_reference):
246    '''
247    Idle callback for populating the children of a given row reference.
248
249    @param row_reference: The parent row reference.
250    @type row_reference: gtk.TreeRowReference
251
252    @return: False if task is done (to stop handler).
253    @rtype: boolean
254    '''
255    remove_iter = None
256    iter = None
257    parent = self.desktop
258
259    if row_reference:
260      if not row_reference.valid():
261        self._endPopTask()
262        return False
263      iter = self.get_iter(row_reference.get_path())
264      parent = self[iter][COL_ACC]
265      tree_path = row_reference.get_path()
266      path = tuple(tree_path.get_indices())
267      if self[path+(0,)][COL_DUMMY]:
268        remove_iter = self.iter_children(iter)
269
270    already_populated_num = self.iter_n_children(iter)
271
272    if already_populated_num >= self.children_number(parent) and \
273          not remove_iter:
274      if iter:
275        self[iter][COL_FILLED] = True
276      self.emit('row-filled', iter)
277      self._endPopTask()
278      return False
279    elif remove_iter:
280      already_populated_num -= 1
281    try:
282      index = self._selectChild(parent, already_populated_num)
283      child = parent.getChildAtIndex(index)
284    except LookupError:
285      child = None
286
287    row = self._buildRow(child)
288    self.append(iter, row)
289
290    if remove_iter:
291      self.remove(remove_iter)
292    return True
293
294  def _endPopTask(self):
295    '''
296    Convinience function for stopping a populating task.
297    '''
298    self._populating_tasks -= 1
299    if self._populating_tasks == 0:
300      self.emit('end-populating')
301
302  def popToPath(self, path):
303    '''
304    Populate the model with accessible nodes up to given path.
305
306    @param path: Path to populate model to.
307    @type path: tuple
308    '''
309    if not self._walkThroughFilled(path):
310      self._path_to_populate = path
311    else:
312      self.emit('row-filled', self.get_iter(path))
313      self._path_to_populate = None
314
315  def _walkThroughFilled(self, path):
316    '''
317    Reach node in path that is not populated yet, and populate it.
318
319    @param path: Path to fill.
320    @type path: tuple
321
322    @return: True if entire path is populated.
323    @rtype: boolean
324    '''
325    for i in range(1, len(path)):
326      if not self[path[:i]][COL_FILLED]:
327        self.popLevel(self.get_iter(path[:i]))
328        return False
329    return True
330
331  def _onRowFilled(self, model, iter):
332    '''
333    Callback for "row-filled" signal. If there is a specific path we need to populate,
334    we continue populating the next node.
335
336    @param model: Model that emitted signal (self).
337    @type model: L{AccessibleModel}
338    @param iter: Iter of row that has been populated.
339    @type iter: gtk.TreeIter
340    '''
341    if iter and self._path_to_populate:
342      tree_path = self.get_path(iter)
343      path = tuple(tree_path.get_indices())
344      if self._path_to_populate[:len(path)] == path:
345        if self._walkThroughFilled(self._path_to_populate):
346          self._path_to_populate = None
347
348  def getIndexInParent(self, child):
349    '''
350    Returns a position of a child in its parent.
351
352    @param child: Accessible for which we want to determine the index.
353    @type child: L{Accessibility.Accessible}
354
355    @return: The child's index or -1 if it wasn't found
356    @rtype: integer
357    '''
358    return child.getIndexInParent()
359
360  def getAccPath(self, acc):
361    '''
362    Get the tree path that a given accessible should have.
363
364    I{Note:} The accessible does not necessarily need to be resident in the model.
365
366    @param acc: The accessible we want a path of.
367    @type acc: L{Accessibility.Accessible}
368
369    @return: The path to the accessible.
370    @rtype: tuple
371    '''
372    path = ()
373    child = acc
374    while child.get_parent():
375      try:
376        index_in_parent = self.getIndexInParent(child)
377        if index_in_parent < 0:
378          break
379        path = (index_in_parent,) + path
380      except Exception as e:
381        return None
382      child = child.get_parent()
383
384    try:
385      path = (list(self.desktop).index(child),) + path
386    except Exception as e:
387      return None
388
389    return path
390
391  def _buildRow(self, accessible, dummy=False):
392    '''
393    Wrapper for building a row in the tree. Use this method instead of trying
394    to construct the row by hand as it will be synced with the design of the
395    model fields.
396
397    @param accessible: Accessible object
398    @type accessible: L{Accessibility.Accessible}
399    @param dummy: Is this a dummy row?
400    @type dummy: boolean
401    '''
402    if accessible is not None:
403      icon = getIcon(accessible)
404      if self.isMyApp(accessible):
405        name = "Accerciser"
406        role = "accerciser"
407        count = 0
408      else:
409        name = accessible.name
410        role = accessible.getLocalizedRoleName()
411        count = accessible.childCount
412    else:
413      icon = None
414      if not dummy:
415        name = _('<dead>')
416      else:
417        name = None
418      role = None
419      count = 0
420    return [icon, name, role, count, False, dummy, accessible]
421
422class AccessibleTreeView(gtk.TreeView, ToolsAccessor):
423  '''
424  The treeview for the desktop's accessibles. The treeview's model (L{AccessibleModel}
425  is only populated when the treeview is traversed and nodes are expanded. This class
426  listens for 'row-expanded' events in order to have a tree node populated. Nodes that
427  are selected are updated into the L{Node} instance variable. This treeview also
428  updates automatically in response to at-spi children change events.
429
430  @ivar desktop: The desktop accessible. It holds references to all the
431  application L{Accessibility.Accessible}s
432  @type desktop: L{Accessibility.Accessible}
433  @ivar node: An object with a reference to the currently selected accessible.
434  @type node: L{Node}
435  @ivar model: The data model of this treeview.
436  @type model: L{AccessibleModel}
437  '''
438  def __init__(self, node):
439    '''
440    Initialize the treeview. Build the proper columns.
441    Connect all of the proper signal handlers and at-spi event handlers.
442
443    @param node: The main application node.
444    @type node: L{Node}
445    '''
446    gtk.TreeView.__init__(self)
447
448    self.desktop = pyatspi.Registry.getDesktop(0)
449    self.node = node
450    self.node.update(self.desktop)
451    self._changed_handler = self.node.connect('accessible_changed',
452                                             self._onAccChanged)
453    self.connect('row-activated', self._onRowActivated)
454
455    self.model = AccessibleModel(self.desktop)
456    self.filter = self.model.filter_new()
457    self.filter.set_visible_func(self._filterNoChildApps, data=None)
458    self.set_model(self.filter)
459
460    crt = gtk.CellRendererText()
461    crp = gtk.CellRendererPixbuf()
462    tvc = gtk.TreeViewColumn(_('Name'))
463    tvc.pack_start(crp, False)
464    tvc.pack_start(crt, True)
465    tvc.add_attribute(crp, 'pixbuf', COL_ICON)
466    tvc.add_attribute(crt, 'text', COL_NAME)
467    tvc.set_resizable(True)
468    self.append_column(tvc)
469
470    crt= gtk.CellRendererText()
471    tvc = gtk.TreeViewColumn(_('Role'))
472    tvc.pack_start(crt, True)
473    tvc.add_attribute(crt, 'text', COL_ROLE)
474    tvc.set_resizable(True)
475    self.append_column(tvc)
476
477    crt = gtk.CellRendererText()
478    tvc = gtk.TreeViewColumn(_('Children'))
479    tvc.pack_start(crt, True)
480    tvc.add_attribute(crt, 'text', COL_CHILDCOUNT)
481    tvc.set_resizable(True)
482    self.append_column(tvc)
483
484    self.model.connect('row-filled', self._onRowFilled)
485    self.model.connect('start-populating', self._onStartPop)
486    self.model.connect('end-populating', self._onEndPop)
487    self._path_to_expand = None
488
489    self._refreshTopLevel()
490
491    selection = self.get_selection()
492    selection.unselect_all()
493    selection.connect('changed', self._onSelectionChanged)
494    self.connect('row-expanded', self._onExpanded)
495
496    pyatspi.Registry.registerEventListener(self._accEventChildChanged,
497                                           'object:children-changed')
498
499    pyatspi.Registry.registerEventListener(
500        self._accEventNameChanged,
501        'object:property-change:accessible-name')
502
503    self._hide_leaves = True
504    self.action_group = gtk.ActionGroup.new('TreeActions')
505    self.action_group.add_toggle_actions(
506      [('HideShowLeaves', None, _('_Show Applications without children'), None,
507        None, self._onHideShowLeaves, False)])
508    self.action_group.add_actions([
509        ('RefreshAll', gtk.STOCK_REFRESH, _('_Refresh Registry'),
510        # Translators: Appears as tooltip
511        #
512         None, _('Refresh all'), self._refreshTopLevel),
513        # Translators: Refresh current tree node's children.
514        #
515        ('RefreshCurrent', gtk.STOCK_JUMP_TO, _('Refresh _Node'),
516        # Translators: Appears as tooltip
517        #
518         None, _('Refresh selected node’s children'),
519         self._refreshCurrentLevel)])
520
521    self.refresh_current_action = self.action_group.get_action('RefreshCurrent')
522    self.refresh_current_action.set_sensitive(False)
523
524    self.connect('popup-menu', self._onPopup)
525    self.connect('button-press-event', self._onPopup)
526    self.connect('key-press-event', self._onKeyPress)
527
528    self.connect('cursor-changed', self._onCursorChanged)
529
530  def _filterNoChildApps(self, model, iter, data):
531    '''
532    Filter all rows of applications without children
533    '''
534    return not self._hide_leaves or model.iter_parent(iter) != None \
535      or model[iter][COL_CHILDCOUNT] != 0 or self.isMyApp(model[iter][COL_ACC])
536
537  def _onCursorChanged(self, tree):
538    '''
539    Set sensitivity of refresh function only if the tree cursor is
540    on an accessible.
541    '''
542    path = self.get_cursor()[0]
543    self.refresh_current_action.set_sensitive(path is not None)
544
545  def _onKeyPress(self, w, event):
546    '''
547    Expand or collapse a row on Left/Right key-press
548    '''
549    if event.state & \
550        (gdk.ModifierType.SHIFT_MASK
551        |gdk.ModifierType.CONTROL_MASK
552        |gdk.ModifierType.MOD1_MASK) == 0:
553      path, col = self.get_cursor()
554      if path is not None:
555        if event.keyval == gdk.KEY_Left:
556          if not self.collapse_row(path):
557            # if we ccouldn't collapse the current row, go to the parent
558            if path.up():
559              self.set_cursor(path)
560          return True
561        elif event.keyval == gdk.KEY_Right:
562          self.expand_row(path, False)
563          return True
564    return False
565
566  def _onPopup(self, w, event=None):
567    '''
568    Callback for popup button or right mouse button. Brings up a context
569    menu.
570    '''
571    if event:
572      if event.button != 3:
573        return False
574      info = self.get_path_at_pos(int(event.x), int(event.y))
575      if info is None:
576        return False
577      path = info[0]
578      if self.isMyApp(self.filter[self.filter.get_iter(path)][COL_ACC]):
579        return False
580      selection = self.get_selection()
581      selection.set_mode(gtk.SelectionMode.NONE)
582      self.set_cursor(path, None, False)
583      selection.set_mode(gtk.SelectionMode.SINGLE)
584      time = event.time
585      button = event.button
586      func = None
587      extra_data = None
588    else:
589      path, col= self.get_cursor()
590      time = gtk.get_current_event_time()
591      button = 0
592      extra_data = getTreePathBoundingBox(self, path, col)
593      func = lambda m, b: (b.x, b.y + (b.height/2), True)
594
595    menu = ui_manager.uimanager.get_widget(ui_manager.POPUP_MENU_PATH)
596    menu.popup(None, None, func, extra_data, button, time)
597    return True
598
599  def _refreshTopLevel(self, action=None, data=None):
600    '''
601    Refreshes the entire tree at the desktop level.
602
603    @param action: Action object that emitted this signal, if any.
604    @type: gtk.Action
605    '''
606    self.model.clear()
607    self.model.popLevel(None)
608    # iter over all apps in the desktop too
609
610  def _refreshCurrentLevel(self, action, data=None):
611    '''
612    Refreshes the current level. Selects and expands the parent of the level.
613
614    @param action: Action object that emitted this signal, if any.
615    @type: gtk.Action
616    '''
617    path = self.get_cursor()[0]
618    if path == None:
619      return
620    filter_iter = self.filter.get_iter(path)
621    if self.isMyApp(self.filter[filter_iter][COL_ACC]):
622      return
623    is_expanded = self.row_expanded(path)
624    self._refreshChildren(self.filter.convert_iter_to_child_iter(filter_iter))
625    if is_expanded:
626      self.expand_row(path, False)
627      self._onExpanded(self, self.filter.get_iter(path), path)
628
629  def _onHideShowLeaves(self, option):
630    '''
631    Hides/Shows all leaves (accessibles with no children) from the accessible treeview.
632    '''
633    self._hide_leaves = not self._hide_leaves
634    self.filter.refilter()
635
636  def _onExpanded(self, treeview, iter, path):
637    '''
638    Populates a level when it is expanded. Removes the previously added dummy
639    node.
640
641    @param treeview: The L{AccessibleTreeView} that emitted the signal.
642    @type treeview: L{AccessibleTreeView}
643    @param iter: The iter that has been expanded.
644    @type iter: L{gtk.TreeIter}
645    @param path: The path to the row that has been expanded.
646    @type path: tuple
647    '''
648    # don't repopulate if it has been filled before
649    if self.filter[iter][COL_FILLED]:
650      return
651    # populate this level
652    self.model.popLevel(self.filter.convert_iter_to_child_iter(iter))
653
654  def _accEventNameChanged(self, event):
655    '''
656    Event handler for "object:property-change:accessible-name".
657    Updates the treeview accordingly.
658
659    @param event: The event which triggered this handler.
660    @type event: L{pyatspi.event.Event}
661    '''
662    if self.isMyApp(event.source) or event.source == self.desktop:
663      # Bad karma
664      return
665    if self.model.isInModel(event.source):
666      try:
667        path = self.model.getAccPath(event.source)
668        iter = self.model.get_iter(path)
669      except:
670          pass
671      else:
672          self.model[iter][COL_NAME] = event.source.name
673
674  def _accEventChildChanged(self, event):
675    '''
676    Event handler for "object:children-changed". Updates the treeview accordingly.
677
678    @param event: The event which triggered this handler.
679    @type event: L{pyatspi.event.Event}
680    '''
681    if self.isMyApp(event.source):
682      # Bad karma
683      return
684    if self.model.isInModel(event.source):
685      try:
686        path = self.model.getAccPath(event.source)
687        iter = self.model.get_iter(path)
688      except:
689        iter = None
690        path = None
691      if (event.source == self.desktop) or \
692            (path and self.model[path][COL_FILLED]):
693        if event.type.minor == 'add':
694          self._addChild(iter, event.source)
695        elif event.type.minor == 'remove':
696          self._removeChild(iter, event.source)
697      if iter and self.model.iter_is_valid(iter):
698        self.model[iter][COL_CHILDCOUNT] = event.source.childCount
699
700  def _addChild(self, iter, parent):
701    '''
702    Add the new child to the given accessible.
703
704    @param iter: Th iter of the row that needs a child added.
705    @type iter: L{gtk.TreeIter}
706    @param parent: The given row's accessible.
707    @type parent: L{Accessibility.Accessible}
708    '''
709    old_children = set(self.model.getChildrenAccs(iter))
710    new_children = set(list(parent))
711
712    added = new_children.difference(old_children)
713    try:
714      new_child = added.pop()
715    except KeyError:
716      return
717    row = self.model._buildRow(new_child)
718    if new_child is None:
719      self.model.append(iter, row)
720    else:
721      self.model.insert(iter, self.model.getIndexInParent(new_child), row)
722      # We do this because an application won't have an icon loaded in
723      # the window manager when it is first registered to at-spi
724      if new_child == new_child.getApplication():
725        GLib.timeout_add(1000, self._refreshIcon, new_child)
726
727  def _refreshIcon(self, app):
728    '''
729    Refresh the icon of a given application's accessible. This is done because it
730    takes wnck a while to load an application's icon at application startup.
731
732    @param app: The given application's accessible.
733    @type app: L{Accessibility.Accessible}
734    '''
735    path = self.model.getAccPath(app)
736    try:
737      self.model[path][COL_ICON] = getIcon(app)
738    except:
739      pass
740    return False
741
742  def _removeChild(self, parent_iter, parent):
743    '''
744    Remove a child from the given accessible node.
745
746    @param parent_iter: Th iter of the row that needs a child removed.
747    @type parent_iter: L{gtk.TreeIter}
748    @param parent: The given row's accessible.
749    @type parent: L{Accessibility.Accessible}
750    '''
751    if parent_iter:
752      iter = self.model.iter_children(parent_iter)
753    else:
754      iter = self.model.get_iter_first()
755
756    while iter:
757      if self.model[iter][COL_ACC] not in parent:
758        cursor_path = self.get_cursor()[0]
759        if cursor_path != None:
760          (res, filter_iter) = self.filter.convert_child_iter_to_iter(iter)
761          if res:
762            filter_path = self.filter.get_path(filter_iter)
763            if filter_path.is_ancestor(cursor_path):
764              cursor_path = filter_path
765            if 0 == filter_path.compare(cursor_path):
766              if filter_path.prev() or filter_path.up():
767                self.set_cursor(filter_path, None, False)
768        if not self.model.remove(iter):
769          break
770      else:
771        iter = self.model.iter_next(iter)
772
773  def _refreshChildren(self, iter):
774    '''
775    Remove all of a given node's children from the model.
776
777    @param iter: The parent node.
778    @type iter: L{gtk.TreeIter}
779    '''
780    if not iter:
781      self._refreshTopLevel()
782      return
783    child_iter = self.model.iter_children(iter)
784    while child_iter:
785      if not self.model.remove(child_iter):
786        break
787    acc = self.model[iter][COL_ACC]
788    self.model[iter][COL_CHILDCOUNT] = acc.childCount
789    self.model[iter][COL_FILLED] = False
790
791  def refreshSelected(self):
792    '''
793    Manually refresh the selected node.
794    '''
795    selection = self.get_selection()
796    model, iter = selection.get_selected()
797    if not self.isMyApp(model[iter][COL_ACC]):
798      self._refreshChildren(iter)
799
800  def _onSelectionChanged(self, selection):
801    '''
802    Update the accessible according to the selected row.
803
804    @param selection: The selection object that emitted the the 'selection-changed'
805    signal.
806    @type selection: L{gtk.TreeSelection}
807    '''
808    self._path_to_expand = None
809    model, iter = selection.get_selected()
810    if iter:
811      new_acc = model[iter][COL_ACC]
812    else:
813      new_acc = self.desktop
814    if new_acc == self.node.acc:
815      return
816    self.node.handler_block(self._changed_handler)
817    self.node.update(new_acc)
818    self.node.handler_unblock(self._changed_handler)
819    if iter:
820      tree_path = model.get_path(iter)
821      path = tuple(tree_path.get_indices())
822
823      self.node.tree_path = list(path[1:])
824
825  def _onAccChanged(self, node, acc):
826    '''
827    Change the treeview's selection to the updated accessible in the L{Node}.
828
829    @param node: The L{node} that emitted the signal.
830    @type node: L{Node}
831    @param acc: The new accessible in the node.
832    @type acc: L{Accessibility.Accessible}
833    '''
834    if self.isMyApp(acc):
835      # Bad karma
836      return
837    path = self.model.getAccPath(acc)
838    if not path:
839      return
840    if len(path) >= 1:
841      self.selectNodeAtPath(path)
842      self.node.tree_path = list(path[1:])
843
844  def selectNodeAtPath(self, path):
845    '''
846    Select the node at the current path. The path does not need to exist in the model,
847    only in the accessibles tree. The model will get populated accordingly.
848
849    @param path: The path to select.
850    @type path: tuple
851    '''
852    try:
853      dummy = self.model[path][COL_DUMMY]
854    except:
855      dummy = True
856    if dummy:
857      self._path_to_expand = path
858      self.model.popToPath(path)
859    else:
860      self._selectExistingPath(path)
861
862  def _onRowFilled(self, model, iter):
863    '''
864    Callback for "row-filled" (populated) signal. Used for selecting a child node in
865    the given iter if L{selectNodeAtPath} was called on one of the given
866    iter's children.
867
868    @param model: Model that emitted this signal.
869    @type model: L{AccessibleModel}
870    @param iter: Iter of row that was populated with children.
871    @type iter: gtk.TreeIter
872    '''
873    if iter and self._path_to_expand and \
874          self._path_to_expand[:-1] == model.get_path(iter).get_indices():
875      self._selectExistingPath(self._path_to_expand)
876      self._path_to_expand = None
877
878  def _selectExistingPath(self, path):
879    '''
880    Select a path that already exists. Expand, scroll, and select.
881
882    @param path: Path to select.
883    @type path: tuple
884    '''
885    tree_path = gtk.TreePath(path[:-1])
886    if len(path) > 1:
887      self.expand_to_path(tree_path)
888    self.scroll_to_cell(path)
889    selection = self.get_selection()
890    selection.select_path(path)
891
892
893  def _onStartPop(self, model):
894    '''
895    Callback for when the model is populating, changes the cursor to a watch.
896
897    @param model: Model that emitted the signal.
898    @type model: L{AccessibleModel}
899    '''
900    if self.get_window():
901      window = self.get_window()
902      window.set_cursor(gdk.Cursor(gdk.CursorType.WATCH))
903
904  def _onEndPop(self, model):
905    '''
906    Callback for when the model stops populating, changes the cursor to an arrow.
907
908    @param model: Model that emitted the signal.
909    @type model: L{AccessibleModel}
910    '''
911    if self.get_window():
912      window = self.get_window()
913      window.set_cursor(gdk.Cursor(gdk.CursorType.TOP_LEFT_ARROW))
914
915  def _onRowActivated(self, treeview, path, view_column):
916    '''
917    When the row is activated (double clicked, or enter), blink the selected
918    accessible, if possible.
919
920    @param treeview: The L{AccessibleTreeView} that emitted the signal.
921    @type treeview: L{AccessibleTreeView}
922    @param path: The path to the selected row.
923    @type path: tuple
924    @param view_column: The column in the activated row.
925    @type view_column: L{gtk.TreeViewColumn}
926    '''
927    if not self.isMyApp(self.filter[self.filter.get_iter(path)][COL_ACC]):
928      self.node.highlight()
929