Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- class Treeview(Base, ttk.Treeview):
- def __init__(self, parent, key, **kwargs):
- Base.__init__(self, parent, key, **kwargs)
- self.columns = self.get_setting('columns', {})
- self.headings = self.get_setting('headings', {})
- self.popup_menus_config = self.get_setting('popup_menus', {})
- ttk.Treeview.__init__(self, parent, columns=tuple(range(1, len(self.columns))), **self.get_options())
- self.bindings = {}
- self.column_sort = {}
- self.column_number = '#0'
- self.dlg = \
- self.xsb = \
- self.ysb = \
- self.levels = \
- self.largest = \
- self.was_cut = \
- self.xscroll = \
- self.yscroll = \
- self.pointer_x = \
- self.pointer_y = \
- self.direction = \
- self.selections = \
- self.root_selection = None
- self.init()
- def init(self):
- def set_columns():
- for index, column in enumerate(self.columns):
- self.column(f'#{index}', **dict(column))
- def set_headings():
- for index, heading in enumerate(self.headings):
- self.column_sort[f'#{index}'] = True
- self.heading(f'#{index}', **dict(heading))
- def set_scrolling():
- scroll = self.get_setting('scroll')
- if scroll:
- self.add_scrollbars(*scroll)
- set_columns()
- set_headings()
- set_scrolling()
- self.set_bindings()
- self.populate('', **self.get_setting('content', {}))
- self.render()
- def separator_double_click(self, column):
- def get_size(item):
- frame = ttk.Frame(self.app)
- text = self.item(item).get('text') if column == '#0' else self.set(item, column)
- lbl = tk.Label(frame, text=text)
- lbl.grid()
- frame.update_idletasks()
- size = lbl.winfo_width()
- lbl.grid_forget()
- return size
- def walk(children):
- for child in children:
- size = get_size(child) + 8
- if size + (self.levels * 20) > self.largest:
- self.largest = size
- if len(child.split('_')) > self.levels:
- self.levels = len(child.split('_'))
- if self.item(child)['open']:
- walk(self.get_children(child))
- return self.levels, self.largest
- self.levels = 1
- self.largest = 0
- depth, width = walk(self.get_children())
- if column == '#0':
- width += depth * 20
- self.column(column, width=width)
- ######################################################################
- def tags_add(self, item, *tags):
- _tags = list(self.item(item)['tags'])
- _tags = [] if not _tags else _tags
- for tag in tags[0]:
- if tag not in _tags:
- _tags.append(tag)
- self.item(item, tags=_tags)
- def tags_remove(self, item, *tags):
- _tags = []
- for tag in tags[0]:
- _tags = list(self.item(item)['tags'])
- if tag in _tags:
- _tags.pop(_tags.index(tag))
- self.item(item, tags=_tags)
- def tags_remove_all(self, *tags):
- def walk(_child):
- self.tags_remove(_child, list(tags))
- _children = self.get_children(_child)
- if _children:
- for node in _children:
- walk(node)
- children = self.get_children()
- for child in children:
- walk(child)
- def tags_refresh(self, _=None):
- def walk(_child, _tag):
- self.tags_remove(_child, ['odd', 'even', 'selected'])
- _tags = list(self.item(_child)['tags'])
- _tags.append(_tag)
- self.item(_child, tags=_tags)
- for node in self.get_children(_child):
- _prev = self.prev(node)
- _children = self.get_children(node)
- self.tags_remove(node, ['odd', 'even', 'selected'])
- _tag = 'even' if _tag == 'odd' else 'odd'
- _tags = list(self.item(node)['tags'])
- _tags.append(_tag)
- self.item(node, tags=_tags)
- if _children and self.item(node)['open']:
- _tag = walk(node, _tag)
- return _tag
- tag = 'even'
- for child in self.get_children():
- prev = self.prev(child)
- if prev and self.item(prev)['open']:
- prev = self.get_last_node(prev)
- if prev:
- tag = 'even' if 'odd' in self.item(prev)['tags'] else 'odd'
- elif prev:
- tags = self.item(prev)['tags']
- tag = 'even' if 'odd' in tags else 'odd'
- walk(child, tag)
- def cut(self, _=None):
- self.copy()
- self.was_cut = True
- def copy(self):
- def get_items(_node):
- def get_nodes(child):
- _items = []
- for child in self.get_children(child):
- _items.append(child)
- if self.get_children(child):
- _items += get_items(child)
- return _items
- if self.item(node)['open']:
- results = [] if next((x for x in selections if x.startswith(_node)), False) else get_nodes(_node)
- else:
- results = get_nodes(_node)
- return results
- self.tags_remove_all('copy', 'selected')
- self.tags_refresh()
- items = []
- selections = sorted(self.selection())
- while selections:
- node = selections.pop(0)
- items.append(node)
- if self.item(node)['values'][0].lower() == 'menu':
- items += get_items(node)
- for item in items:
- self.tags_add(item, ['copy', 'selected'])
- self.tags_remove(item, ['odd', 'even'])
- self.popup.enable_items(['paste'])
- self.pointer_x, self.pointer_y = self.winfo_toplevel().winfo_pointerxy()
- def paste(self):
- def set_items(parent, _node):
- for child in self.get_children(_node):
- if child in nodes:
- nodes.pop(nodes.index(child))
- _item = self.item(child)
- _iid = self.append(parent, _item['values'][0], _item['text'], _item['values'])
- set_items(_iid, child)
- for selection in self.selection():
- nodes = sorted(self.tag_has('copy'))
- while nodes:
- node = nodes.pop(0)
- item = self.item(node)
- iid = self.append(selection, item['values'][0], item['text'], item['values'])
- if item['values'][0].lower() == 'menu':
- set_items(iid, node)
- if self.was_cut:
- self.was_cut = False
- self.delete(*self.tag_has('copy'))
- self.tags_remove_all('selected')
- self.tags_refresh()
- def insert(self, parent, index=tk.END, **kwargs):
- iid = kwargs['iid'] = self.get_iid(parent)
- text = kwargs.get('text')
- config = kwargs.pop('config', {'type': kwargs.pop('type', None)})
- try:
- for child in self.get_children(parent):
- if text == self.item(child)['text']:
- raise NameError(text)
- item = super(Treeview, self).insert(parent, index, **kwargs)
- self.update_config(iid, **config)
- self.tags_refresh()
- return item
- except NameError as text:
- messagebox.showerror(
- title='Name Error',
- message=f'The name you entered, ({text}) already exists. '
- f'Choose another name and try again.'
- )
- def append(self, parent, _type, text, values, **kwargs):
- kwargs['type'] = _type
- kwargs['text'] = text
- kwargs['values'] = values
- return self.insert(parent, **kwargs)
- def remove(self):
- if not self.selection():
- return
- def delete(uri):
- target = uri.rsplit('/').pop()
- content = self.get_setting('content', {})
- def walk(_content):
- data = _content.copy()
- for key in data.keys():
- if key == target:
- del _content[key]
- break
- if isinstance(data[key], dict):
- walk(_content[key])
- walk(content)
- self.set_setting('content', content)
- for selection in sorted(self.selection(), reverse=True):
- delete(tvi2uri(selection))
- selected = self.selection()[0]
- _prev = self.prev(selected)
- _next = self.next(selected)
- _parent = super(Treeview, self).parent(selected)
- for selection in sorted(self.selection(), reverse=True):
- self.delete(selection)
- if _next and self.exists(_next):
- self.selection_set(_next)
- self.focus(_next)
- elif _prev and self.exists(_prev) and not self.item(_prev)['open']:
- self.selection_set(_prev)
- self.focus(_prev)
- else:
- if _parent and not _prev:
- node = _parent
- elif self.get_children(_prev):
- node = list(self.get_children(_prev)).pop()
- else:
- return
- if node:
- self.selection_set(node)
- self.refresh()
- def escape(self, _):
- self.was_cut = False
- self.tags_remove_all('selected')
- self.selection_set('')
- self.tags_refresh()
- def expand(self, _):
- self.after(0, self.tags_refresh)
- def actions(self, event):
- region = self.identify('region', event.x, event.y)
- if region == 'tree':
- item = self.identify('item', event.x, event.y)
- def refresh(self):
- font = tkfont.nametofont('TkTextFont')
- self.style.configure('Treeview', rowheight=font.metrics('linespace') + 5)
- self.style.configure("Treeview.Heading", font=tkfont.nametofont('TkHeadingFont'))
- self.set_column_widths()
- def collapse(self, _):
- self.after(0, self.tags_refresh)
- def populate(self, parent, index=tk.END, **data):
- def walk(_parent, _data):
- for _iid, value in _data.copy().items():
- if isinstance(value, dict):
- _iid = f'{parent}_{_iid}'.strip("_")
- _attrs = {
- 'iid': _iid,
- 'text': value.get('text', ''),
- 'image': value.get('image', None),
- 'values': value.get('values', []),
- 'open': value.get('open', 0),
- 'tags': value.get('tags', []),
- 'type': value.get('type', None),
- }
- self.insert(_parent, index, **_attrs)
- walk(_iid, value)
- for _id, data in data.items():
- if isinstance(data, dict):
- iid = f'{parent}_{_id}'.strip("_")
- attrs = {
- 'iid': iid,
- 'text': data.get('text', ''),
- 'image': data.get('image', None),
- 'values': data.get('values', []),
- 'open': data.get('open', 0),
- 'tags': data.get('tags', []),
- 'type': data.get('type', None),
- }
- self.insert(parent, tk.END, **attrs)
- walk(iid, data)
- self.refresh()
- def get_iid(self, parent=''):
- idx = len(self.get_children(parent))
- iid = f'{parent}_!{idx}'.strip('_')
- while iid in self.get_children(parent):
- parts = iid.rsplit('_', 1)
- value = int(parts.pop().lstrip('!'))
- iid = f'{parts.pop()}_!{value-1}'
- return iid
- def get_last_node(self, _child):
- if self.get_children(_child):
- _child = list(self.get_children(_child)).pop()
- if self.item(_child)['open']:
- _child = self.get_last_node(_child)
- return _child
- return None
- def set_bindings(self):
- def control_a(_):
- def walk(children):
- for child in children:
- self.selection_add(child)
- _children = self.get_children(child)
- if _children:
- walk(_children)
- walk(self.get_children())
- def control_x(_):
- self.copy()
- self.remove()
- def control_c(_):
- self.copy()
- def control_v(_):
- self.paste()
- def control_z(e):
- tree = e.widget
- tree.selection_set(super(Treeview, self).parent(tree.focus()))
- self.paste()
- def key_press(event):
- tree = event.widget
- if 'Shift' in event.keysym:
- if tree.selection() and len(tree.selection()) <= 1:
- self.root_selection = tree.focus()
- def shift_up(_):
- cur_item = self.focus()
- get_parent = super(Treeview, self).parent
- if not get_parent(cur_item) and not self.index(cur_item):
- return 'break'
- if not self.direction:
- self.direction = 'up'
- def prev_item(node):
- _prev = self.prev(node)
- if not _prev:
- _prev = get_parent(node)
- elif self.get_children(_prev) and self.item(_prev)['open']:
- def walk(_node):
- if self.get_children(_node) and self.item(_node)['open']:
- return walk(list(self.get_children(_node)).pop())
- return _node
- _prev = walk(list(self.get_children(_prev)).pop())
- return _prev
- item = prev_item(cur_item)
- if self.direction == 'up':
- if item:
- self.selection_add(item)
- self.focus(item)
- else:
- _parent = get_parent(item)
- if not self.item(_parent)['open']:
- item = _parent
- else:
- self.selection_remove(cur_item)
- self.focus(item)
- if item == self.root_selection:
- self.direction = None
- return 'break'
- def shift_down(_):
- cur_item = self.focus()
- if not self.direction:
- self.direction = 'down'
- get_parent = super(Treeview, self).parent
- def next_item(node):
- _next = self.next(node)
- if self.get_children(node) and self.item(node)['open']:
- _next = self.get_children(node)[0]
- elif not _next:
- def walk(_node):
- _parent = get_parent(_node)
- _item = self.next(_parent)
- if not _item and get_parent(_parent):
- return walk(get_parent(_parent))
- return _item
- if get_parent(cur_item):
- _next = walk(cur_item)
- return _next
- item = next_item(cur_item)
- if not item:
- return
- if self.direction == 'down':
- self.selection_add(item)
- self.focus(item)
- else:
- self.selection_remove(cur_item)
- self.focus(cur_item)
- if item == self.root_selection and item != self.focus():
- self.direction = None
- self.focus(item)
- return 'break'
- def header_dbl_click(event):
- region = self.identify("region", event.x, event.y)
- if region == "separator":
- column_number = self.identify_column(event.x)
- self.separator_double_click(column_number)
- bindings = {
- '<Key>': key_press,
- '<Control-a>': control_a,
- '<Control-x>': control_x,
- '<Control-c>': control_c,
- '<Control-v>': control_v,
- '<Control-z>': control_z,
- # '<Up>': self.clear_selections,
- # '<Down>': self.clear_selections,
- '<Escape>': self.escape,
- '<Button-1>': self.actions,
- '<Button-3>': self.do_popup_menu,
- '<Double-1>': header_dbl_click,
- '<Shift-Up>': shift_up,
- '<Shift-Down>': shift_down,
- '<<TreeviewOpen>>': self.expand,
- '<<TreeviewClose>>': self.collapse,
- '<ButtonRelease-1>': self.update_column_widths,
- }
- for command, callback in bindings.items():
- self.bindings[command] = self.bind(command, callback)
- def set_column_widths(self):
- column_widths = self.get_setting('column_widths')
- if column_widths:
- for idx in range(len(self.cget('columns')) + 1):
- self.column(f'#{idx}', width=column_widths[idx])
- def set_content(self, iid, **kwargs):
- def do_set(data):
- for key, value in data.items():
- if not isinstance(value, dict):
- continue
- if key == iid:
- value.update(kwargs)
- return 'break'
- if do_set(value) == 'break':
- break
- do_set(self.get_setting('content', {}))
- def get_content(self, iid=None):
- def do_get(data):
- for _key, _value in data.items():
- if not isinstance(_value, dict):
- continue
- if _key == iid:
- return _value
- do_get(_value)
- return None
- content = self.get_setting('content', {})
- if not iid or not content:
- return content
- elif iid in content:
- _data = {}
- for key, value in content[iid].items():
- if not isinstance(value, dict):
- _data[key] = value
- return _data
- return do_get(content)
- def update_config(self, iid, **config):
- entries = {**self.item(iid), **{
- 'type': config.pop('type', None),
- 'state': config.pop('state', tk.NORMAL),
- 'inherit': (('colors', '0'), ),
- 'options': config.get('options', ()) if config else (),
- }}
- config = self.get_setting('content', {})
- uri = tvi2uri(iid)
- parts = uri.split('/')
- _id = parts.pop(0)
- if config and _id in config:
- data = config[_id]
- while parts:
- _id = parts.pop(0)
- if not parts:
- data[_id] = entries
- else:
- data = data[_id]
- else:
- config[uri] = entries
- self.set_setting('content', config)
- def update_column_widths(self, _=None):
- values = []
- for idx in range(len(self.cget('columns')) + 1):
- values.append(self.column(f'#{idx}', 'width'))
- self.set_setting('column_widths', values)
- def add_scrollbars(self, xscroll, yscroll):
- if not xscroll and not yscroll:
- return
- self.xscroll, self.yscroll = xscroll, yscroll
- if xscroll:
- sb = self.parent.add_element('scrollbar1', **scrollbar1)
- sb.parent = self
- sb.configure(command=self.xview)
- self.configure(xscrollcommand=sb.set_scroll)
- if yscroll:
- sb = self.parent.add_element('scrollbar0', **scrollbar0)
- sb.parent = self
- sb.configure(command=self.yview)
- self.configure(yscrollcommand=sb.set_scroll)
- def do_scroll(self, event):
- wdg = event.widget
- options = wdg.get_options()
- if wdg.type == 'Scrollbar' and options.get('orient', tk.HORIZONTAL) == tk.HORIZONTAL:
- event.state = (event.state | 1)
- c_width, c_height = self.winfo_width(), self.winfo_height()
- autosave_state = self.get_setting('autosave_state', True)
- if event.state & 0x1 and self.xscroll:
- if event.num == 4:
- if c_width <= self.winfo_reqwidth():
- self.xview_scroll(-1, tk.UNITS)
- elif event.num == 5:
- self.xview_scroll(1, tk.UNITS)
- if autosave_state:
- self.set_setting('xview', self.xview())
- elif self.yscroll:
- if event.num == 4:
- if c_height <= self.winfo_reqheight():
- self.yview_scroll(-1, tk.UNITS)
- elif event.num == 5:
- self.yview_scroll(1, tk.UNITS)
- if autosave_state:
- self.set_setting('yview', self.yview())
- def do_popup_menu(self, event):
- item = self.identify('item', event.x, event.y)
- if not self.popup:
- return
- self.popup.tk_popup(event.x_root, event.y_root)
- # This needs to be fixed not portable.
- items = ['add_project', 'cut', 'copy', 'delete', 'add_item', 'add_menu', 'select_all', 'save', 'save_all']
- if item:
- self.popup.enable_items(items)
- else:
- items.pop(0)
- items.append('paste')
- self.popup.disable_items(items)
- if item not in self.selection():
- self.selection_set('')
- self.selection_add(item)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement