#!/usr/bin/python # -*- coding: utf-8 -*- "gui2py's Tree Model-View-Controller control (uses wx.TreeCtrl)" __author__ = "Mariano Reingart (reingart@gmail.com)" __copyright__ = "Copyright (C) 2013- Mariano Reingart" # where applicable # Initial implementation was based on PythonCard's Tree component, # but redesigned and overhauled a lot (specs renamed, events refactorized, etc.) # Note: pythoncard version was trivial, Model and View code are completely new import wx from ..event import TreeEvent from ..component import Control, SubComponent from ..spec import Spec, EventSpec, InitSpec, StyleSpec, InternalSpec from .listbox import ItemContainerControl from .. import images class TreeView(Control): "A tree (wx.TreeCtrl)" _wx_class = wx.TreeCtrl _style = wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_SIBLINGS _image = images.tree_ctrl def __init__(self, parent=None, **kwargs): # default sane values (if not init'ed previously): if not hasattr(self, "_items"): self._max_columns = 99 Control.__init__(self, parent, **kwargs) # Emulate some listBox methods def clear(self): self._items.clear() def get_selected_items(self): return [it for it in self.nodes if it.selected] def _get_items(self): return self._items def _set_items(self, model=None): if model is None: model = TreeModel(self) elif not isinstance(model, (TreeModel, )): raise AttributeError("unsupported type, TreeMoel expected") else: # TODO: rebuild the wx tree items pass self._items = model has_buttons = StyleSpec(wx.TR_HAS_BUTTONS, doc="Show + and - buttons to the left of parent items.") no_lines = StyleSpec(wx.TR_NO_LINES, doc="Hide vertical level connectors") row_lines = StyleSpec(wx.TR_ROW_LINES, doc="Draw a contrasting border between displayed rows.") multiselect = StyleSpec(wx.TR_MULTIPLE, default=True, doc="Allow multiple selection") hide_root = StyleSpec(wx.TR_HIDE_ROOT, default=False, doc="Suppress the display of the root node") default_style = StyleSpec(wx.TR_DEFAULT_STYLE, doc="Closest to the defaults for the native control") items = InternalSpec(_get_items, _set_items) # events: onitemselected = EventSpec('item_selected', binding=wx.EVT_TREE_SEL_CHANGED, kind=TreeEvent) onitemactivated = EventSpec('item_activated', binding=wx.EVT_TREE_ITEM_ACTIVATED, kind=TreeEvent) onitemcollapsed = EventSpec('item_collapsed', binding=wx.EVT_TREE_ITEM_COLLAPSED, kind=TreeEvent) onitemcollapsing = EventSpec('item_collapsing', binding=wx.EVT_TREE_ITEM_COLLAPSING, kind=TreeEvent) onitemexpanded = EventSpec('item_expanded', binding=wx.EVT_TREE_ITEM_EXPANDED, kind=TreeEvent) onitemexpanding = EventSpec('list_expanding', binding=wx.EVT_TREE_ITEM_EXPANDING, kind=TreeEvent) class TreeModel(dict): "TreeView Items (nodes) model map {wx item data: TreeItem}" def __init__(self, _tree_view): self._tree_view = _tree_view self.clear() def __setitem__(self, key, kwargs): self.add(key, kwargs) def add(self, parent=None, text="", key=None): if key is None: key = self._new_key() # create the wx item if parent is None: wx_item = self._tree_view.wx_obj.AddRoot(text) else: wx_item = self._tree_view.wx_obj.AppendItem(parent.wx_item, text) # associate the key so we can look for it in the future (ie, __iter__) data = wx.TreeItemData(key) self._tree_view.wx_obj.SetItemData(wx_item, data) # create the new item item = TreeItem(self, key, wx_item, parent) dict.__setitem__(self, key, item) # add the key/value to the dict return item def __delitem__(self, it): dict.__delitem__(self, it) self._tree_view.wx_obj.DeleteItem(it) def __call__(self, wx_item=None): "Look for a item based on the wx_item or return a key/value pair" if wx_item is not None: key = self._tree_view.wx_obj.GetPyData(wx_item) return self[key] else: return self.items() # shortcut! def __iter__(self): "Return a iterable for all the nodes" # This is not really useful except to perform global actions # (i.e., reseting the selection of all items) # use TreeItem.__iter__ to browse the nodes hierarchy return self.itervalues() def clear(self): "Remove all items and reset internal structures" dict.clear(self) self._key = 0 if hasattr(self._tree_view, "wx_obj"): self._tree_view.wx_obj.DeleteAllItems() def _new_key(self): "Create a unique key for this list control (currently: just a counter)" self._key += 1 return self._key class TreeItem(object): "Represents a item node in the TreeModel" def __init__(self, _tree_model, key, wx_item, parent_node): self._tree_model = _tree_model self.key = key # key used in model self.parent = parent_node self.wx_item = wx_item # TreeItemId returned by Add/Append def _get_text(self): return self._tree_model._tree_view.wx_obj.GetItemText(self.wx_item) def _set_text(self, new_text): return self._tree_model._tree_view.wx_obj.SetItemText(self.wx_item, new_text) text = property(_get_text, _set_text, doc="Get or change the item label") def _is_selected(self): return self._tree_model._tree_view.wx_obj.IsSelected(self.wx_item) def _select(self, on): self._tree_model._tree_view.wx_obj.SelectItem(self.wx_item, on) selected = property(_is_selected, _select) def ensure_visible(self): self._tree_model._tree_view.wx_obj.EnsureVisible(self.wx_item) def focus(self): self._tree_model._tree_view.wx_obj.SetFocusedItem(self.wx_item) def get_children_count(self): return self._tree_model._tree_view.wx_obj.GetChildrenCount(self.wx_item) def set_has_children(self, has_children=True): "Force appearance of the button next to the item" # This is useful to allow the user to expand the items which don't have # any children now, but instead adding them only when needed, thus # minimizing memory usage and loading time. self._tree_model._tree_view.wx_obj.SetItemHasChildren(self.wx_item, has_children) def __iter__(self): "look for children and convert them to TreeItem if any" tree = self._tree_model._tree_view.wx_obj wx_item, cookie = tree.GetFirstChild(self.wx_item) if wx_item.IsOk(): key = tree.GetPyData(wx_item) yield self._tree_model[key] while True: wx_item, cookie = tree.GetNextChild(self.wx_item, cookie) if not wx_item.IsOk(): break key = tree.GetPyData(wx_item) yield self._tree_model[key] if __name__ == "__main__": import sys # basic test until unit_test import gui app = wx.App(redirect=False) w = gui.Window(title="hello world", name="frmTest", tool_window=False, resizable=True, visible=False, pos=(180, 0)) tv = TreeView(w, name="treeview", has_buttons=True, default_style=True) root = tv.items.add(text="Root") child1 = tv.items.add(parent=root, text="Child 1") child2 = tv.items.add(parent=root, text="Child 2") child3 = tv.items.add(parent=root, text="Child 3") child11 = tv.items.add(parent=child1, text="Child 11") child11.ensure_visible() child2.set_has_children() def expand_item(event): "lazy evaluation example: virtually add children at runtime" if not event.detail.get_children_count(): for i in range(5): it = tv.items.add(parent=event.detail, text="lazy child %s" % i) it.set_has_children() # allow to lazy expand this child too # assign some event handlers: tv.onitemexpanding = expand_item tv.onitemselected = "print 'selected TreeItem:', event.detail.text" w.show() # basic tests: # iterate on the root node: for i, nodx in enumerate(root): assert nodx.text == "Child %s" % (i + 1) nodx.text = "hello %s" % i assert nodx.text == "hello %s" % i if i == 0: assert nodx.get_children_count() == 1 assert root.get_children_count() == 4 from gui.tools.inspector import InspectorTool InspectorTool().show(w) app.MainLoop()