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.setting_get('columns', {})
- self.headings = self.setting_get('headings', {})
- ttk.Treeview.__init__(self, parent, columns=tuple(range(1, len(self.columns))), **self.options_get())
- self.undo_data = {}
- self.bindings = {}
- self.column_sort = {}
- self.levels = \
- self.x_root = \
- self.y_root = \
- self.largest = \
- self.xscroll = \
- self.yscroll = \
- self.pointer_x = \
- self.pointer_y = \
- self.direction = \
- self.detached = \
- self._selection_colors = \
- 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.setting_get('scroll')
- if scroll:
- self.scrollbars_add(*scroll)
- set_columns()
- set_headings()
- set_scrolling()
- self.bindings_set()
- self.populate()
- 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 get_iid(self, parent=''):
- nodes = self.get_children(parent)
- idx = len(nodes)
- iid = f'{parent}_!{idx}'.strip('_')
- while self.exists(iid):
- idx += 1
- iid = f'{parent}_!{idx}'.strip('_')
- return iid
- def parent_get(self, item):
- return super(Treeview, self).parent(item)
- def tags_add(self, item, tags):
- if isinstance(tags, str):
- tags = (tags,)
- _tags = list(self.item(item)['tags'])
- _tags = [] if not _tags else _tags
- for tag in tags:
- if tag not in _tags:
- _tags.append(tag)
- self.item(item, tags=_tags)
- def tags_remove(self, item, tags):
- if isinstance(tags, str):
- tags = (tags,)
- _tags = list(self.item(item)['tags'])
- for tag in tags:
- if tag in _tags:
- _tags.pop(_tags.index(tag))
- self.item(item, tags=_tags)
- def tags_refresh(self):
- def do_refresh(children, _tag):
- for child in children:
- item = self.item(child)
- _tag = 'even' if _tag == 'odd' else 'odd'
- _tags = list(self.item(child)['tags'])
- if f'selected_{_tag}' not in item['tags']:
- _tags.append(_tag)
- self.item(child, tags=_tags)
- if int(item['open']):
- _tag = do_refresh(self.get_children(child), _tag)
- return _tag
- self.tags_remove_all('odd', 'even', 'cut_odd', 'cut_even', 'copy_odd', 'copy_even')
- do_refresh(self.get_children(), 'odd')
- def tags_remove_all(self, *tags):
- def do_remove(node):
- self.tags_remove(node, tags)
- for node in self.get_children(node):
- do_remove(node)
- for child in self.get_children():
- do_remove(child)
- def tags_set_colors(self):
- def do_refresh(children):
- for child in children:
- _tags = self.item(child, 'tags')
- if 'odd' in _tags:
- self.tags_remove(child, 'odd')
- self.tags_add(child, f'{mode}_odd')
- elif 'even' in _tags:
- self.tags_remove(child, 'even')
- self.tags_add(child, f'{mode}_even')
- _item = self.item(child)
- nodes = self.get_children(child)
- if int(_item['open']):
- do_refresh(nodes)
- mode = 'cut' if self.tag_has('cut') else 'copy'
- items = sorted(self.tag_has(mode))
- if items:
- item = items.pop(0)
- tags = self.item(item, 'tags')
- if 'odd' in tags:
- self.tags_remove(item, 'odd')
- self.tags_add(item, f'{mode}_odd')
- elif 'even' in tags:
- self.tags_remove(item, 'even')
- self.tags_add(item, f'{mode}_even')
- do_refresh(self.get_children(item))
- def cut(self, _=None):
- self.copy('cut')
- def undo(self):
- for k, value in self.undo_data.items():
- self.content_set(k, **value)
- for item in sorted(self.detached):
- parent = '' if '_' not in item else item.rsplit('_', 1)[0]
- index = int(item.rsplit('_', 1).pop().lstrip('!'))
- self.move(item, parent, index)
- self.popup.disable_items('undo')
- def copy(self, mode='copy'):
- def get_items():
- items = []
- root = selections.pop(0)
- items.append(root)
- while selections:
- selection = selections.pop(0)
- if not selection.startswith(root):
- root = selection
- items.append(root)
- return items
- def tags_update(_item):
- _tag = ''
- tags = list(self.item(_item, 'tags'))
- if 'odd' in tags:
- self.tags_remove(_item, 'odd')
- _tag = f'{mode}_odd'
- else:
- self.tags_remove(_item, 'even')
- _tag = f'{mode}_even'
- self.tags_add(_item, _tag)
- if int(self.item(_item)['open']):
- for node in self.get_children(_item):
- tags_update(node)
- selections = sorted(self.selection())
- if not selections:
- return
- self.tags_remove_all('cut', 'copy', 'cut_odd', 'cut_even', 'copy_odd', 'copy_even')
- self.tags_refresh()
- for item in get_items():
- self.tags_add(item, mode)
- tags_update(item)
- self.pointer_x, self.pointer_y = self.winfo_toplevel().winfo_pointerxy()
- def paste(self):
- def get_items(parent):
- item_names = []
- for c in self.get_children(parent):
- item_names.append(self.item(c, 'text'))
- return item_names
- def add_nodes(parent, _node):
- _item = self.item(_node)
- if self.has_name(parent, _item['text']):
- _results = self.rename(_item['text'])
- _text = _results.get('text', '')
- while _results.get('text', '') in get_items(parent):
- _results = self.rename(_item['text'])
- if 'text' not in _results:
- return 'cancel'
- _item['text'] = _results['text']
- _iid = self.append(parent, _item['text'], _item['values'], open=_item['open'])
- if 'cut' in _item['tags']:
- self.item(_iid, tags=(self.item(_iid, 'tags'), 'copy'))
- for child in self.get_children(_node):
- add_nodes(_iid, child)
- selections = self.selection()
- if not selections:
- return
- selection = selections[0]
- mode = 'cut' if self.tag_has('cut') else 'copy'
- items = sorted(self.tag_has(mode))
- if items:
- if selection.startswith(items[0]):
- return
- for item in items:
- if add_nodes(selection, item) == 'cancel':
- self.tags_refresh()
- return
- if mode == 'cut':
- self.delete(*items)
- self.tags_refresh()
- self.tags_set_colors()
- def rename(self, value=''):
- def after_do_ok(dialog):
- def text_get(_text):
- while not _text:
- _data = entry_value_get()
- if 'text' not in _data:
- return
- _text = _data['text']
- return _text
- def item_name_get():
- _dlg = self.app.dialog_manager.dialog(dlg_key, **{
- 'options': {
- 'value': text,
- 'message': f'The name ({text}) already exists.\nPlease enter a different name and try again.',
- 'position': (self.x_root, self.y_root),
- },
- })
- self.x_root, self.y_root = _dlg.results['geometry'].split('+', 1).pop().split('+')
- return _dlg.results
- def entry_value_get():
- _dlg = self.app.dialog_manager.dialog(dlg_key, **{
- 'options': {
- 'value': '',
- 'message': 'You have not entered a name.\nPlease enter a name and try again.',
- 'position': (self.x_root, self.y_root),
- },
- })
- self.x_root, self.y_root = _dlg.results['geometry'].split('+', 1).pop().split('+')
- return _dlg.results
- text = dialog.results.get('text', '').strip(' ')
- target = self.selection()[0]
- text = text_get(text)
- while self.has_name(self.parent_get(target), text):
- data = item_name_get()
- if 'text' not in data:
- return
- text = text_get(data['text'])
- def before_exit(dialog):
- results = dialog.results
- self.x_root, self.y_root = results['geometry'].split('+', 1).pop().split('+')
- selection = self.selection()[0]
- target_item = self.item(selection)
- if not self.x_root or not self.y_root:
- self.x_root, self.y_root = \
- self.winfo_rootx()+self.bbox(selection)[0], self.winfo_rooty()+self.bbox(selection)[1]
- font = tkfont.nametofont('TkTextFont')
- self.x_root = self.x_root + self.column('#0')['width']
- self.y_root = self.y_root + font.metrics('linespace')
- dlg_key = 'rename'
- while True:
- dlg = self.app.dialog_manager.dialog(dlg_key, **{
- 'options': {
- 'value': value if value else target_item['text'],
- 'message': f'Enter a new name for the {target_item["values"][0].lower()}.',
- 'position': (self.x_root, self.y_root),
- },
- 'callbacks': {
- 'after_do_ok': after_do_ok,
- 'before_exit': before_exit,
- },
- })
- if 'text' not in dlg.results or ('text' in dlg.results and dlg.results['text']):
- break
- return dlg.results
- def insert(self, parent='', index=tk.END, **kwargs):
- kwargs.pop('iid', None)
- kwargs.pop('image', None)
- unique = kwargs.pop('unique', True)
- iid = self.get_iid(parent)
- try:
- if unique:
- for child in self.get_children(parent):
- if not kwargs['text'] or kwargs['text'] == self.item(child)['text']:
- raise NameError()
- item = super(Treeview, self).insert(parent, index, iid=iid, **kwargs)
- self.content_set(iid, **kwargs)
- return item
- except NameError:
- print(555555555555)
- return False
- def append(self, parent, text, values, **kwargs):
- kwargs['text'] = text
- kwargs['values'] = values
- return self.insert(parent, **kwargs)
- def remove(self, iid=None):
- def do_remove(uri):
- def walk(_content):
- data = _content.copy()
- for key in data.keys():
- if key == target:
- self.undo_data.update({key: _content[key]})
- del _content[key]
- break
- if isinstance(data[key], dict):
- walk(_content[key])
- target = uri.rsplit('/').pop()
- content = self.setting_get('content', {})
- walk(content)
- self.setting_set('content', content)
- selections = self.selection()
- if not iid and not selections:
- return
- elif iid:
- selections = [iid]
- for selection in sorted(selections):
- do_remove(tvi2uri(selection))
- selected = selections[0]
- _prev = self.prev(selected)
- _next = self.next(selected)
- _parent = super(Treeview, self).parent(selected)
- self.detached = []
- for selection in sorted(selections, reverse=True):
- self.detached.append(selection)
- self.detach(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()
- if self.undo_data:
- self.popup.enable_items('undo')
- self.tags_refresh()
- self.tags_set_colors()
- def escape(self, _):
- self.tags_remove_all('cut', 'copy', 'cut_odd', 'cut_even', 'copy_odd', 'copy_even')
- self.selection_set('')
- self.tags_refresh()
- def expand(self, _):
- def refresh():
- self.tags_refresh()
- self.after(0, refresh)
- def set_colors():
- tags = self.tag_has('copy')
- if not tags:
- tags = self.tag_has('cut')
- if tags:
- self.tags_set_colors()
- self.after(0, set_colors)
- 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.column_widths_set()
- def collapse(self, _):
- def refresh():
- self.tags_refresh()
- self.after(0, refresh)
- def set_colors():
- tags = self.tag_has('copy')
- if not tags:
- tags = self.tag_has('cut')
- if tags:
- self.tags_set_colors()
- self.after(0, set_colors)
- def populate(self):
- def walk(_parent, _data, _content):
- _idx = 0
- for _, _value in sorted(_data.items()):
- if isinstance(_value, dict):
- _iid = f'{_parent}_!{_idx}'
- _idx += 1
- _kwargs = {
- 'text': _value.get('text', ''),
- 'image': _value.get('image', None),
- 'values': _value.get('values', []),
- 'open': _value.get('open', 0),
- 'tags': _value.get('tags', []),
- }
- _content[_iid] = _kwargs
- self.insert(_parent, tk.END, iid=_iid, **_kwargs)
- walk(_iid, _value, _content[_iid])
- idx = 0
- parent = ''
- content = {}
- for _, value in sorted(self.setting_get('content', {}).items()):
- if isinstance(value, dict):
- iid = f'{parent}_!{idx}'.strip("_")
- idx += 1
- kwargs = {
- 'text': value.get('text', ''),
- 'image': value.get('image', None),
- 'values': value.get('values', []),
- 'open': value.get('open', 0),
- 'tags': value.get('tags', []),
- }
- self.insert(parent, tk.END, iid=iid, **kwargs)
- content[iid] = kwargs
- walk(iid, value, content[iid])
- self.setting_set('content', content)
- self.refresh()
- self.tags_refresh()
- def control_a(self):
- def walk(children):
- for child in children:
- self.selection_add(child)
- _children = self.get_children(child)
- if _children:
- walk(_children)
- selections = self.selection()
- if selections:
- walk(self.get_children(selections[0]))
- def has_name(self, parent, name):
- for iid in self.get_children(parent):
- if name == self.item(iid)['text']:
- return True
- return False
- def content_set(self, iid, **kwargs):
- def do_set(data):
- for key, value in data.items():
- if not isinstance(value, dict):
- continue
- elif key == iid:
- value.update(kwargs)
- return 'break'
- elif key == parent and iid not in value:
- value[iid] = kwargs
- return 'break'
- if do_set(value) == 'break':
- break
- content = self.setting_get('content', {})
- parent = '' if '_' not in iid else iid.rsplit('_', 1)[0]
- if '_' not in iid and iid not in content:
- content[iid] = kwargs
- else:
- do_set(content)
- def content_get(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.setting_get('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 bindings_set(self):
- 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 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(_):
- for selection in self.selection():
- 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 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,
- '<Double-1>': header_dbl_click,
- '<Shift-Up>': shift_up,
- '<Shift-Down>': shift_down,
- '<<TreeviewOpen>>': self.expand,
- '<<TreeviewClose>>': self.collapse,
- '<ButtonRelease-1>': self.column_widths_update,
- }
- for command, callback in bindings.items():
- self.bindings[command] = self.bind(command, callback)
- def column_widths_set(self):
- column_widths = self.setting_get('column_widths')
- if column_widths:
- for idx in range(len(self.cget('columns')) + 1):
- self.column(f'#{idx}', width=column_widths[idx])
- def column_widths_update(self, _=None):
- values = []
- for idx in range(len(self.cget('columns')) + 1):
- values.append(self.column(f'#{idx}', 'width'))
- self.setting_set('column_widths', values)
- def scrollbars_add(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.options_get()
- 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.setting_get('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.setting_set('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.setting_set('yview', self.yview())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement