summaryrefslogtreecommitdiff
path: root/jfr_playoff/gui/frames/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'jfr_playoff/gui/frames/__init__.py')
-rw-r--r--jfr_playoff/gui/frames/__init__.py503
1 files changed, 503 insertions, 0 deletions
diff --git a/jfr_playoff/gui/frames/__init__.py b/jfr_playoff/gui/frames/__init__.py
new file mode 100644
index 0000000..a02791d
--- /dev/null
+++ b/jfr_playoff/gui/frames/__init__.py
@@ -0,0 +1,503 @@
+#coding=utf-8
+
+from functools import partial
+import types
+
+import tkinter as tk
+from tkinter import ttk
+import tkMessageBox
+
+from ..variables import NotifyStringVar, NotifyIntVar, NotifyNumericVar, NumericVar
+
+def setPanelState(frame, state):
+ for child in frame.winfo_children():
+ if isinstance(child, tk.Frame):
+ setPanelState(child, state)
+ else:
+ child.configure(state=state)
+
+class WidgetRepeater(tk.Frame):
+ def __init__(self, master, widgetClass, headers=None, classParams=None,
+ *args, **kwargs):
+ widgetList = widgetClass
+ if not isinstance(widgetClass, list):
+ widgetList = [widgetClass]
+ for widget in widgetList:
+ if not issubclass(widget, RepeatableFrame):
+ raise AttributeError(
+ 'WidgetRepeater widget must be a RepeatableFrame')
+ tk.Frame.__init__(self, master, **kwargs)
+ self.widgetClass = widgetClass
+ self.widgetClassParams = classParams
+ self.widgets = []
+ self.headers = headers
+ self.headerFrame = None
+ self.addButton = ttk.Button(
+ self, text='[+]', width=5, command=self._addWidget)
+ self.renderContent()
+
+ def _findWidget(self, row, column):
+ for children in self.children.values():
+ info = children.grid_info()
+ if info['row'] == str(row) and info['column'] == str(column):
+ return children
+ return None
+
+ def _createWidget(self, widgetClass, widgetClassParams=None):
+ headeridx = int(self.headerFrame is not None)
+ removeButton = ttk.Button(
+ self, text='[-]', width=5,
+ command=lambda i=len(self.widgets): self._removeWidget(i))
+ removeButton.grid(
+ row=len(self.widgets)+headeridx, column=0, sticky=tk.N)
+ widget = widgetClass(self)
+ if widgetClassParams is not None:
+ widget.configureContent(**widgetClassParams)
+ self.widgets.append(widget)
+ self._updateGrid()
+
+ def _handleWidgetSelection(self, selected):
+ if selected < len(self.widgetClass):
+ params = None
+ if isinstance(self.widgetClassParams, list) and \
+ selected < len(self.widgetClassParams):
+ params = self.widgetClassParams[selected]
+ self._createWidget(self.widgetClass[selected], params)
+
+ def _widgetSelectionDialog(self):
+ dialog = tk.Toplevel(self)
+ dialog.title('Wybór elementu do dodania')
+ dialog.geometry('%dx%d' % (300, len(self.widgetClass) * 20 + 30))
+ dialog.grab_set()
+ dialog.focus_force()
+ frame = WidgetSelectionFrame(
+ dialog, vertical=True,
+ widgets=self.widgetClass, callback=self._handleWidgetSelection)
+ frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ def _addWidget(self):
+ if isinstance(self.widgetClass, list):
+ self._widgetSelectionDialog()
+ else:
+ self._createWidget(self.widgetClass, self.widgetClassParams)
+
+ def _removeWidget(self, idx):
+ self.widgets.pop(idx).destroy()
+ self._findWidget(row=len(self.widgets), column=0).destroy()
+ self._updateGrid()
+
+ def _updateGrid(self):
+ headeridx = int(self.headerFrame is not None)
+ for idx, widget in enumerate(self.widgets):
+ widget.grid(
+ row=idx+headeridx, column=1, sticky=tk.W+tk.E+tk.N+tk.S)
+ if self.headerFrame is not None:
+ self.headerFrame.grid(row=0, column=1, sticky=tk.W+tk.E+tk.N+tk.S)
+ self.addButton.grid(
+ row=len(self.widgets)+headeridx, column=0, columnspan=1,
+ sticky=tk.W+tk.N)
+
+ def _renderHeader(self):
+ if self.headers:
+ self.headerFrame = tk.Frame(self)
+ for idx, header in enumerate(self.headers):
+ self.headerFrame.columnconfigure(idx, weight=1)
+ widget = header[0](self.headerFrame, **header[1])
+ widget.grid(row=0, column=idx, sticky=tk.W+tk.E+tk.N)
+ (tk.Label(self, text=' ')).grid(
+ row=0, column=0, sticky=tk.W+tk.E+tk.N)
+
+ def renderContent(self):
+ self.columnconfigure(1, weight=1)
+ self._renderHeader()
+ self._updateGrid()
+
+ def getValue(self):
+ return [widget.getValue() for widget in self.widgets]
+
+ def _getParamsForWidgetClass(self, widgetClass):
+ if not isinstance(self.widgetClass, list):
+ return self.widgetClassParams
+ if not isinstance(self.widgetClassParams, list):
+ return self.widgetClassParams
+ for idx, widget in enumerate(self.widgetClass):
+ if widget == widgetClass:
+ return self.widgetClassParams[idx]
+ return None
+
+ def setValue(self, values):
+ for i, value in enumerate(values):
+ typedWidget = isinstance(value, tuple) \
+ and isinstance(value[0], (types.TypeType, types.ClassType))
+ if i >= len(self.widgets):
+ if typedWidget:
+ self._createWidget(
+ value[0], self._getParamsForWidgetClass(value[0]))
+ else:
+ self._addWidget()
+ self.widgets[i].setValue(value[1] if typedWidget else value)
+ for idx in range(len(values), len(self.widgets)):
+ self._removeWidget(len(self.widgets)-1)
+
+
+class GuiFrame(tk.Frame):
+ def __init__(self, *args, **kwargs):
+ tk.Frame.__init__(self, *args, **kwargs)
+ self.renderContent()
+
+ def renderContent(self):
+ pass
+
+class RepeatableFrame(tk.Frame):
+ def __init__(self, *args, **kwargs):
+ tk.Frame.__init__(self, *args, **kwargs)
+ self.renderContent()
+
+ def renderContent(self):
+ pass
+
+ def configureContent(self, **kwargs):
+ pass
+
+ def getValue(self):
+ pass
+
+ def setValue(self, value):
+ pass
+
+ @classmethod
+ def info(cls):
+ return cls.__name__
+
+class RepeatableEntry(RepeatableFrame):
+ def renderContent(self):
+ self.value = NotifyStringVar()
+ self.field = ttk.Entry(self, textvariable=self.value)
+ self.field.pack(expand=True, fill=tk.BOTH)
+
+ def configureContent(self, **kwargs):
+ for param, value in kwargs.iteritems():
+ self.field[param] = value
+
+ def getValue(self):
+ return self.value.get()
+
+ def setValue(self, value):
+ return self.value.set(value)
+
+class ScrollableFrame(tk.Frame):
+ def __init__(self, *args, **kwargs):
+ vertical = False
+ if 'vertical' in kwargs:
+ vertical = kwargs['vertical']
+ del kwargs['vertical']
+ horizontal = False
+ if 'horizontal' in kwargs:
+ horizontal = kwargs['horizontal']
+ del kwargs['horizontal']
+ tk.Frame.__init__(self, *args, **kwargs)
+ self.canvas = tk.Canvas(self, borderwidth=0, highlightthickness=0)
+ if horizontal:
+ hscroll = tk.Scrollbar(
+ self, orient=tk.HORIZONTAL, command=self.canvas.xview)
+ hscroll.pack(side=tk.BOTTOM, fill=tk.X)
+ self.canvas.configure(xscrollcommand=hscroll.set)
+ if vertical:
+ vscroll = tk.Scrollbar(
+ self, orient=tk.VERTICAL, command=self.canvas.yview)
+ vscroll.pack(side=tk.RIGHT, fill=tk.Y)
+ self.canvas.configure(yscrollcommand=vscroll.set)
+ frame = tk.Frame(self.canvas, borderwidth=0, highlightthickness=0)
+ self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+ self.canvasFrame = self.canvas.create_window(
+ (0,0), window=frame, anchor=tk.N+tk.W)
+ frame.bind('<Configure>', self._onFrameConfigure)
+ self.canvas.bind('<Configure>', self._onCanvasConfigure)
+ self.bind('<Enter>', partial(self._setScroll, value=True))
+ self.bind('<Leave>', partial(self._setScroll, value=False))
+ self.renderContent(frame)
+
+ def _setScroll(self, event, value):
+ if value:
+ self.bind_all('<MouseWheel>', self._onVscroll)
+ self.bind_all('<Shift-MouseWheel>', self._onHscroll)
+ else:
+ self.unbind_all('<MouseWheel>')
+ self.unbind_all('<Shift-MouseWheel>')
+
+ def _onHscroll(self, event):
+ self._onScroll(tk.X, -1 if event.delta > 0 else 1)
+
+ def _onVscroll(self, event):
+ self._onScroll(tk.Y, -1 if event.delta > 0 else 1)
+
+ def _onScroll(self, direction, delta):
+ getattr(
+ self.canvas, '%sview' % (direction))(tk.SCROLL, delta, tk.UNITS)
+
+ def _onFrameConfigure(self, event):
+ self.canvas.configure(scrollregion=self.canvas.bbox('all'))
+
+ def _onCanvasConfigure(self, event):
+ self.canvas.itemconfig(self.canvasFrame, width=event.width)
+
+ def renderContent(self, container):
+ pass
+
+class WidgetSelectionFrame(ScrollableFrame):
+ def __init__(self, *args, **kwargs):
+ self.widgets = []
+ self.callback = None
+ for var in ['widgets', 'callback']:
+ if var in kwargs:
+ setattr(self, var, kwargs[var])
+ del kwargs[var]
+ ScrollableFrame.__init__(self, *args, **kwargs)
+ addBtn = ttk.Button(
+ self.master, text='Dodaj', command=self._onConfirm)
+ addBtn.pack(side=tk.BOTTOM)
+
+ def renderContent(self, container):
+ self.value = NotifyIntVar()
+ for idx, widget in enumerate(self.widgets):
+ (ttk.Radiobutton(
+ container, variable=self.value, value=idx,
+ text=widget.info())).pack(side=tk.TOP, fill=tk.X, expand=True)
+
+ def _onConfirm(self):
+ if self.callback is not None:
+ self.callback(self.value.get())
+ self.winfo_toplevel().destroy()
+
+class SelectionButton(ttk.Button):
+ @property
+ def defaultPrompt(self):
+ pass
+
+ @property
+ def title(self):
+ pass
+
+ @property
+ def errorMessage(self):
+ pass
+
+ def getOptions(self):
+ pass
+
+ def __init__(self, *args, **kwargs):
+ for arg in ['callback', 'prompt', 'dialogclass']:
+ setattr(self, arg, kwargs[arg] if arg in kwargs else None)
+ if arg in kwargs:
+ del kwargs[arg]
+ kwargs['command'] = self._choosePositions
+ if self.prompt is None:
+ self.prompt = self.defaultPrompt
+ ttk.Button.__init__(self, *args, **kwargs)
+ self.setPositions([])
+
+ def setPositions(self, values):
+ self.selected = values
+ self.configure(
+ text='[wybrano: %d]' % (len(values)))
+ if self.callback is not None:
+ self.callback(values)
+
+ def _choosePositions(self):
+ options = self.getOptions()
+ if not len(options):
+ tkMessageBox.showerror(
+ self.title, self.errorMessage)
+ self.setPositions([])
+ else:
+ dialog = tk.Toplevel(self)
+ dialog.title(self.title)
+ dialog.grab_set()
+ dialog.focus_force()
+ selectionFrame = self.dialogclass(
+ dialog, title=self.prompt,
+ options=options,
+ selected=self.selected,
+ callback=self.setPositions, vertical=True)
+ selectionFrame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
+
+class SelectionFrame(ScrollableFrame):
+ def __init__(self, master, title='', options=[],
+ selected=None, callback=None, *args, **kwargs):
+ self.values = {}
+ self.title = title
+ self.options = options
+ self.selected = selected
+ self.callback = callback
+ ScrollableFrame.__init__(self, master=master, *args, **kwargs)
+ (ttk.Button(master, text='Zapisz', command=self._save)).pack(
+ side=tk.BOTTOM, fill=tk.Y)
+
+ def renderOption(self, container, option, idx):
+ pass
+
+ def _mapValue(self, idx, value):
+ return idx + 1
+
+ def _save(self):
+ if self.callback:
+ self.callback(
+ [idx for idx, value
+ in self.values.iteritems() if value.get()])
+ self.master.destroy()
+
+ def renderHeader(self, container):
+ container.columnconfigure(1, weight=1)
+ (ttk.Label(container, text=self.title)).grid(
+ row=0, column=0, columnspan=2)
+
+ def renderContent(self, container):
+ self.renderHeader(container)
+ for idx, option in enumerate(self.options):
+ key = self._mapValue(idx, option)
+ self.values[key] = NotifyIntVar()
+ self.renderOption(container, option, idx)
+ if self.selected and key in self.selected:
+ self.values[key].set(True)
+
+class RefreshableOptionMenu(ttk.OptionMenu):
+ def __init__(self, master, variable, *args, **kwargs):
+ self._valueVariable = variable
+ self._valueVariable.trace('w', self._valueSet)
+ self._lastValue = variable.get()
+ newVar = NotifyStringVar()
+ ttk.OptionMenu.__init__(self, master, newVar, *args, **kwargs)
+ self._valueLock = False
+ self.refreshOptions()
+
+ class _setit(tk._setit):
+ def __init__(self, var, valVar, label, value, callback=None):
+ tk._setit.__init__(self, var, label, callback)
+ self._valueVariable = valVar
+ self._properValue = value
+ def __call__(self, *args):
+ self.__var.set(self.__value)
+ self._valueVariable.set(self._properValue)
+ if self.__callback:
+ self.__callback(self._valueVariable, *args)
+
+ def refreshOptions(self, *args):
+ try:
+ options = self.getOptions()
+ self['menu'].delete(0, tk.END)
+ for label, option in options:
+ self['menu'].add_command(
+ label=label,
+ command=self._setit(
+ self._variable, self._valueVariable,
+ label, option, self._callback))
+ self._valueLock = True
+ self._valueVariable.set(
+ self._lastValue
+ if self._lastValue in [option[1] for option in options]
+ else '')
+ self._valueLock = False
+ except tk.TclError:
+ # we're probably being destroyed, ignore
+ pass
+
+ def getOptions(self):
+ return [
+ (self.getLabel(value), self.getVarValue(value))
+ for value in self.getValues()]
+
+ def getLabel(self, value):
+ pass
+
+ def getValues(self):
+ pass
+
+ def getVarValue(self, value):
+ return self.getLabel(value)
+
+ def _valueSet(self, *args):
+ if not self._valueLock:
+ self._lastValue = self._valueVariable.get()
+ options = self.getOptions()
+ value = self._valueVariable.get()
+ for label, val in options:
+ if unicode(value) == unicode(val):
+ tk._setit(self._variable, label)()
+ return
+ tk._setit(self._variable, '')()
+
+class TraceableText(tk.Text):
+ def __init__(self, *args, **kwargs):
+ self._variable = None
+ self._variableLock = False
+ if 'variable' in kwargs:
+ self._variable = kwargs['variable']
+ del kwargs['variable']
+ tk.Text.__init__(self, *args, **kwargs)
+ if self._variable is not None:
+ self._orig = self._w + '_orig'
+ self.tk.call('rename', self._w, self._orig)
+ self.tk.createcommand(self._w, self._proxy)
+ self._variable.trace('w', self._fromVariable)
+
+ def _fromVariable(self, *args):
+ if not self._variableLock:
+ self._variableLock = True
+ self.delete('1.0', tk.END)
+ self.insert(tk.END, self._variable.get())
+ self._variableLock = False
+
+ def _proxy(self, command, *args):
+ cmd = (self._orig, command) + args
+ # https://stackoverflow.com/a/53418346 <- it's his fault.
+ try:
+ result = self.tk.call(cmd)
+ except:
+ return None
+ if command in ('insert', 'delete', 'replace') and \
+ not self._variableLock:
+ text = self.get('1.0', tk.END).strip()
+ self._variableLock = True
+ self._variable.set(text)
+ self._variableLock = False
+ return result
+
+class NumericSpinbox(tk.Spinbox):
+ def __init__(self, *args, **kwargs):
+ kwargs['justify'] = tk.RIGHT
+ self._variable = None
+ if 'textvariable' in kwargs:
+ self._variable = kwargs['textvariable']
+ self._default = kwargs['from_'] if 'from_' in kwargs else 0
+ tk.Spinbox.__init__(self, *args, **kwargs)
+ if self._variable is not None:
+ if not isinstance(self._variable, NumericVar):
+ raise AttributeError(
+ 'NumericSpinbox variable must be NumericVar')
+ self._variable.trace('w', self._onChange)
+
+ def _onChange(self, *args):
+ val = self._variable.get()
+ if val is None:
+ self._variable.set(self._default)
+
+class LabelButton(ttk.Button):
+ def __init__(self, *args, **kwargs):
+ self.label = None
+ self.tooltip = None
+ self._prevTooltip = None
+ for param in ['label', 'tooltip']:
+ if param in kwargs:
+ setattr(self, param, kwargs[param])
+ del kwargs[param]
+ ttk.Button.__init__(self, *args, **kwargs)
+ if self.label and self.tooltip:
+ self.bind('<Enter>', self._onEnter)
+ self.bind('<Leave>', self._onLeave)
+
+ def _onEnter(self, *args):
+ self._prevTooltip = self.label.cget('text')
+ self.label.configure(text=self.tooltip)
+
+ def _onLeave(self, *args):
+ self.label.configure(text=self._prevTooltip)