Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ########################################################################################################################
- # File: base.py
- # Author: Dan Huckson, https://github.com/unodan
- # Date: 2018-08-24
- ########################################################################################################################
- from os import path
- from logging import basicConfig
- from bisect import bisect
- from tkinter import Tk, Toplevel, PanedWindow, Canvas
- from tkinter.ttk import Frame, LabelFrame, Scrollbar, Button
- from tkinter.font import nametofont
- from importlib import import_module
- from tkapp.Misc.uricache import URICache
- from tkapp.functions import make_directories
- from tkapp.constants import GRID_OPTIONS, CONFIG_OPTIONS, LOG_FILE, LOG_LEVEL, LOG_FORMAT, LOG_DATEFMT, CONFIG_RUNTIME
- class _Admin(Tk):
- def __init__(self, **kwargs):
- self.admin_open = kwargs.pop('admin_open', None)
- self.admin_close = kwargs.pop('admin_close', True)
- self.defaults = kwargs.pop('defaults')
- self.window_title = kwargs.pop('title', '')
- super().__init__(**kwargs)
- basicConfig(filename=LOG_FILE, level=LOG_LEVEL, format=LOG_FORMAT, datefmt=LOG_DATEFMT)
- self.withdraw()
- self.geometry('+2000+224')
- make_directories((
- 'logs',
- 'configs/backups',
- 'images/buttons',
- ))
- self.hooks = {'after_open': {}}
- self.cache = None
- self.windows = {}
- self.wm_stackorder = {}
- self.init_cache()
- self.title(self.window_title)
- self.protocol('WM_DELETE_WINDOW', self.exit_app)
- self.option_add('*Font', 'TkDefaultFont')
- self.font = nametofont("TkDefaultFont")
- def show_window(key):
- if key not in self.windows:
- self.windows[key] = Window(
- key,
- self.cache.get(key),
- before_open=self.before_open,
- after_open=self.after_open,
- before_close=self.before_close,
- after_close=self.after_close
- )
- Button(self, text='Window1', command=lambda: show_window('window1')).grid(row=0, column=0)
- Button(self, text='Window2', command=lambda: show_window('window2')).grid(row=0, column=1)
- Button(self, text='Window3', command=lambda: show_window('window3')).grid(row=0, column=2)
- Button(self, text='Window4', command=lambda: show_window('window4')).grid(row=0, column=3)
- self.config(padx=40, pady=50, bg='purple')
- if self.admin_open:
- self.deiconify()
- def exit_app(self):
- wm_stackorder = self.tk.eval('wm stackorder {}'.format(self)).split()
- if '.' in wm_stackorder:
- wm_stackorder.remove('.')
- stackorder = []
- for index, target in enumerate(wm_stackorder):
- for key, window in self.windows.items():
- if str(window) == target:
- stackorder.append(window.id)
- self.cache.set(stackorder, 'wm_stackorder')
- for key, window in self.windows.copy().items():
- window.close(True)
- self.destroy()
- exit()
- def get_hooks(self, window, name):
- hooks = []
- if name in self.hooks:
- for hook in self.hooks[name]:
- if window in hook:
- hooks.append(hook)
- return hooks
- def init_cache(self):
- if path.isfile(CONFIG_RUNTIME):
- self.cache = URICache(CONFIG_RUNTIME)
- else:
- self.cache = URICache(data=self.defaults)
- self.cache.save(CONFIG_RUNTIME)
- def get_window(self, key):
- if key in self.windows:
- return self.windows[key].container
- def init_windows(self):
- wm_stackorder = self.cache.get('wm_stackorder', {})
- for key in wm_stackorder:
- content = self.cache.get(key)
- if isinstance(content, dict):
- if content.get('is_visible') or content.get('auto_start'):
- Window(
- key,
- content,
- before_open=self.before_open,
- after_open=self.after_open,
- before_close=self.before_close,
- after_close=self.after_close,
- )
- def before_open(self, window):
- for hook in self.get_hooks(window.id, 'before_open'):
- if window.id in hook:
- self.hooks['before_open'][hook]()
- def after_open(self, window):
- self.windows[window.id] = window
- for hook in self.get_hooks(window.id, 'after_open'):
- if window.id in hook:
- self.hooks['after_open'][hook](window)
- def before_close(self, window):
- for hook in self.get_hooks(window.id, 'before_close'):
- if window.id in hook:
- self.hooks['before_close'][hook]()
- def after_close(self, window):
- if window.auto_save or window.save_geometry:
- if window.auto_save:
- self.cache.update(window.values())
- if window.save_geometry:
- self.cache.update({window.id: window.cache.get()})
- self.cache.save(CONFIG_RUNTIME)
- window.destroy()
- del self.windows[window.id]
- for hook in self.get_hooks(window.id, 'after_close'):
- if window.id in hook:
- self.hooks['after_close'][hook]()
- if not len(self.windows) and self.admin_close:
- self.destroy()
- exit()
- def register_callback(self, hook, target, callback):
- if hook not in self.hooks:
- self.hooks[hook] = {}
- self.hooks[hook].update({target: callback})
- class App(object):
- instance = None
- def __new__(cls, **kwargs):
- if not cls.instance:
- cls.instance = super(App, cls).__new__(cls)
- cls.instance.admin = _Admin(**kwargs)
- return cls.instance.admin
- def mainloop(self):
- self.instance.admin.mainloop()
- class Cell(object):
- def __init__(self, parent, key, data, **kwargs):
- self.key = key
- self.parent = parent
- self.members = {}
- self.grid_args = {}
- self.config_args = {}
- self.row = None
- self.rowspan = None
- self.column = None
- self.columnspan = None
- self.padx = None
- self.pady = None
- self.ipadx = None
- self.ipady = None
- self.index = None
- self.type = data.get('type', None)
- self.label = data.get('label', None)
- self.style = data.get('style', None)
- self.width = data.get('width', None)
- self.height = data.get('height', None)
- self.sticky = data.get('sticky', None)
- self.rowconfigure = data.get('rowconfigure', 0)
- self.columnconfigure = data.get('columnconfigure', 0)
- if self.type == 'LabelFrame':
- self.frame = LabelFrame(parent, text=self.label)
- else:
- self.frame = Frame(parent)
- self.frame.rowconfigure(self.rowconfigure, weight=1)
- self.frame.columnconfigure(self.columnconfigure, weight=1)
- self.frame.grid(sticky=self.sticky)
- def get(self, uri=None):
- if not uri:
- return self.frame
- uri = uri.replace('\\', '/').rstrip('/')
- if '/' in uri:
- def parse(parent, uri_part):
- parts = uri_part.split('/', 1)
- child = parent.get(parts[0])
- if len(parts) > 1:
- child = parse(child, parts[1])
- return child
- return parse(self, uri)
- if isinstance(self.members.get(uri, None), Field):
- return self.members.get(uri).field
- return self.members.get(uri, None)
- def add(self, data):
- key = next(iter(data.keys()))
- item = next(iter(data.values()))
- item_type = item.get('type')
- if key not in self.members:
- if item_type == 'PanedWindow':
- self.members[key] = Pane(self.frame, key, item)
- elif item_type in ('Frame', 'LabelFrame'):
- self.members[key] = Group(self.frame, key, item)
- else:
- self.members[key] = Field(self.frame, key, item)
- return self.members[key]
- def item(self, uri=None):
- return self.get(uri)
- def remove(self, uri):
- if not uri:
- return self.frame
- uri = uri.replace('\\', '/').rstrip('/')
- if '/' in uri:
- def parse(parent, uri_part):
- parts = uri_part.split('/', 1)
- child = parent.get(parts[0])
- if len(parts) > 1:
- child = parse(child, parts[1])
- return child
- return parse(self, uri)
- self.members[uri].grid_remove()
- del self.members[uri]
- def values(self, data=None):
- cache = URICache(data=data)
- def process(uri, members):
- for key, widget in members.items():
- if isinstance(widget, Field):
- target = uri + '/' + key + '/value'
- value = None if not data else cache.get(target, '')
- if widget.type in ('Treeview', 'Progressbar', 'Button', 'Separator', 'Label'):
- continue
- if widget.type == 'Notebook':
- for tab, page in widget.field.page.items():
- if hasattr(page, 'form'):
- process(uri + '/' + key + '/items/' + tab + '/content', page.form.members)
- elif widget.type == 'Text':
- if data:
- if value is None:
- value = ''
- widget.field.text.delete('1.0', 'end')
- widget.field.text.insert('end', value)
- else:
- cache.set(widget.field.text.get('1.0', 'end'), target)
- elif widget.type == 'Combobox':
- if data:
- item = ''
- if widget.items:
- if isinstance(value, str):
- value = 0
- item = widget.items[value]
- widget.field.set(item)
- if value:
- widget.field.combobox.current(value)
- else:
- cache.set(widget.items.index(widget.field.var.get()), target)
- else:
- if data:
- widget.field.set(value)
- else:
- cache.set(widget.field.var.get(), target)
- elif widget.type != 'PanedWindow':
- process(uri + '/' + key, widget.members)
- else:
- for k, v in widget.members.items():
- process(uri + '/' + key + '/' + k, v.members)
- process(self.key, self.members)
- return cache.get()
- class Field(Frame):
- def __init__(self, parent, key, data):
- super().__init__(parent)
- self.rowconfigure(0, weight=1)
- self.columnconfigure(0, weight=1)
- self.key = key
- self.data = data
- self.parent = parent
- self.row = data.get('row', 0)
- self.rowspan = data.get('rowspan')
- self.column = data.get('column', 0)
- self.columnspan = data.get('columnspan')
- self.type = data.get('type')
- self.padx = data.get('padx')
- self.pady = data.get('pady')
- self.label = data.get('label')
- self.items = data.get('items')
- self.sticky = data.get('sticky', 'W')
- module = import_module('tkapp')
- widget = getattr(module, '_' + self.type)
- self.field = widget(self, data)
- self.grid(
- row=self.row,
- column=self.column,
- rowspan=self.rowspan,
- columnspan=self.columnspan,
- padx=self.padx,
- pady=self.pady,
- sticky=self.sticky,
- )
- def __eq__(self, obj):
- if isinstance(self, obj):
- return self.value == obj.value
- else:
- return NotImplemented
- def value(self, value=None):
- if value is None:
- value = self.field.get()
- else:
- self.field.set(value)
- return value
- def insert(self, value, row, column):
- if self.type == 'Text':
- self.field.insert(value, row, column)
- def append(self, value):
- if self.type == 'Text':
- self.field.append(value)
- class ScrollFrame(Frame):
- def __init__(self, parent, **kwargs):
- title = kwargs.pop('title', None)
- scrollbars = kwargs.pop('scrollbars', None)
- super().__init__(parent, **kwargs)
- self.canvas = Canvas(parent, borderwidth=0, highlightthickness=0)
- self.canvas.grid(sticky=kwargs.get('sticky', 'nw'))
- if scrollbars == 'both' or scrollbars == 'vertical' or scrollbars is True:
- scrollbar_y = AutoScrollbar(parent, orient='vertical', command=self.canvas.yview)
- scrollbar_y.grid(row=0, column=1, sticky='ns')
- self.canvas.configure(yscrollcommand=scrollbar_y.set)
- if scrollbars == 'both' or scrollbars == 'horizontal' or scrollbars is True:
- scrollbar_x = AutoScrollbar(parent, orient='horizontal', command=self.canvas.xview)
- scrollbar_x.grid(row=1, column=0, sticky='ew')
- self.canvas.configure(xscrollcommand=scrollbar_x.set)
- if not title:
- self.content = Frame(self.canvas)
- else:
- self.content = LabelFrame(self.canvas, text=title)
- self.content.bind('<Configure>', self.do_configure)
- self.window_id = self.canvas.create_window((0, 0), window=self.content, anchor='nw')
- def do_configure(self, event):
- self.canvas.configure(
- width=self.content.winfo_width(),
- height=self.content.winfo_height(),
- scrollregion=self.canvas.bbox('all'),
- )
- return event
- def update_idletasks(self):
- self.content.update_idletasks()
- class Pane(Cell):
- def __init__(self, parent, key, data):
- super().__init__(parent, key, data)
- self.relief = data.get('relief', 'flat')
- self.orient = data.get('orient', None)
- self.sashwidth = data.get('sashwidth', None)
- self.showhandle = data.get('showhandle', None)
- self.background = data.get('background', None)
- self.borderwidth = data.get('borderwidth', 0)
- self.panedwindow = PanedWindow(
- self.frame,
- orient=self.orient,
- relief=self.relief,
- sashwidth=self.sashwidth,
- showhandle=self.showhandle,
- background=self.background,
- borderwidth=self.borderwidth,
- )
- self.panedwindow.id = key
- self.panedwindow.grid(sticky='nsew')
- for key, item in data.items():
- if isinstance(item, dict) and item.get('type') in ('Frame', 'LabelFrame'):
- self.members[key] = Group(self.panedwindow, key, item)
- self.panedwindow.add(self.members[key].frame)
- self.panedwindow.update()
- class Group(Cell):
- def __init__(self, parent, key, data):
- super().__init__(parent, key, data)
- for key, item in data.items():
- if isinstance(item, dict):
- item_type = item.get('type')
- if item_type in ('Frame', 'LabelFrame', 'PanedWindow'):
- if key not in self.members:
- self.members[key] = self.add({key: item})
- elif key not in self.members:
- self.members[key] = Field(self.frame, key, item)
- self.members[key].grid()
- else:
- setattr(self, key, item)
- if key in GRID_OPTIONS:
- self.grid_args[key] = item
- elif bisect(CONFIG_OPTIONS, key) and key not in ('type', 'label'):
- self.config_args[key] = item
- self.grid_args.update({
- 'padx': self.grid_args.pop('ipadx', None),
- 'pady': self.grid_args.pop('ipady', None),
- })
- self.frame.grid(**self.grid_args)
- class Container(Cell):
- def __init__(self, parent, key, content):
- super().__init__(parent, key, content)
- for key, item in content.items():
- if isinstance(item, dict):
- self.members[key] = self.add({key: item})
- else:
- setattr(self, key, item)
- if key in GRID_OPTIONS:
- self.grid_args[key] = item
- elif bisect(CONFIG_OPTIONS, key) and key not in ('type', 'label'):
- self.config_args[key] = item
- self.frame.grid(
- padx=self.grid_args.get('padx'),
- pady=self.grid_args.get('pady'),
- sticky=self.grid_args.get('sticky'),
- )
- class Window(Toplevel):
- def __init__(self, key, content, *args, **kwargs):
- self.auto_save = kwargs.pop('auto_save', False)
- self.save_geometry = kwargs.pop('save_geometry', False)
- self.before_open = kwargs.pop('before_open', None)
- self.after_open = kwargs.pop('after_open', None)
- self.before_close = kwargs.pop('before_close', None)
- self.after_close = kwargs.pop('after_close', None)
- super().__init__(*args, **kwargs)
- self.rowconfigure(0, weight=1)
- self.columnconfigure(0, weight=1)
- self.id = key
- self.cache = URICache(data=content)
- self.style = None
- self.theme = None
- self.themes = None
- self.theme_index = None
- self.container = None
- self.is_visible = None
- self.window_position = None
- self.window_geometry = None
- self.window_title = content.get('window_title', '')
- self.protocol('WM_DELETE_WINDOW', self.close)
- for attribute, value in content.items():
- if not isinstance(value, dict):
- setattr(self, attribute, value)
- self.open(key, content)
- self.title(self.window_title)
- def get(self, uri):
- return self.item(uri)
- def item(self, uri):
- return self.container.get(uri)
- def open(self, window, content):
- if self.before_open:
- self.before_open(self)
- geometry = self.cache.get('window_geometry', None)
- if geometry:
- self.geometry(geometry)
- self.container = Container(self, window, content)
- self.container.values({window: content})
- self.sash_coords(content, 1) # 1 = place/apply
- self.cache.set(True, 'is_visible')
- if self.after_open:
- self.after_open(self)
- def close(self, is_visible=False):
- if self.before_close:
- self.before_close(self)
- self.sash_coords(self.cache.get(), 0) # 0 = save/update
- self.cache.update({'is_visible': is_visible, 'window_geometry': self.geometry()})
- if self.after_close:
- self.after_close(self)
- def values(self):
- return self.container.values()
- def sash_coords(self, data, mode, uri=''):
- for key, item in data.items():
- if isinstance(item, dict) and item.get('type') == 'PanedWindow':
- cnt = 0
- panedwindow = self.container.get(uri + key).panedwindow
- panedwindow.grid(sticky='nsew')
- limit = len(panedwindow.panes()) - 1
- for _key, value in item.items():
- if isinstance(value, dict):
- if mode == 1 and 'sash_coord' in value:
- x, y = value.get('sash_coord')
- panedwindow.sash_place(cnt, x, y)
- panedwindow.update()
- elif mode == 0:
- coords = panedwindow.sash_coord(cnt)
- self.cache.set(coords, uri + key + '/' + _key + '/sash_coord')
- else:
- continue
- cnt += 1
- if cnt == limit:
- break
- self.sash_coords(item, mode, uri + key + '/')
- elif isinstance(item, dict) and item.get('type') in ('Frame', 'LabelFrame'):
- self.sash_coords(item, mode, uri + key + '/')
- class AutoScrollbar(Scrollbar):
- def set(self, lo, hi):
- if float(lo) <= 0.0 and float(hi) >= 1.0:
- self.grid_forget()
- else:
- if str(self.cget("orient")) == 'vertical':
- self.grid(row=0, column=1, sticky='NS')
- else:
- self.grid(row=1, column=0, sticky='EW')
- Scrollbar.set(self, lo, hi)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement