summaryrefslogtreecommitdiff
path: root/jfr_playoff/gui/frames
diff options
context:
space:
mode:
Diffstat (limited to 'jfr_playoff/gui/frames')
-rw-r--r--jfr_playoff/gui/frames/__init__.py508
-rw-r--r--jfr_playoff/gui/frames/match.py856
-rw-r--r--jfr_playoff/gui/frames/network.py194
-rw-r--r--jfr_playoff/gui/frames/team.py545
-rw-r--r--jfr_playoff/gui/frames/translations.py49
-rw-r--r--jfr_playoff/gui/frames/visual.py452
6 files changed, 2604 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..052e832
--- /dev/null
+++ b/jfr_playoff/gui/frames/__init__.py
@@ -0,0 +1,508 @@
+#coding=utf-8
+
+from functools import partial
+import types
+
+import tkinter as tk
+from tkinter import ttk
+import tkMessageBox
+
+from ..variables import NotifyStringVar, NotifyIntVar
+from ..variables import NotifyBoolVar, 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,
+ onAdd=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.onAdd = onAdd
+ 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()
+ if self.onAdd is not None:
+ self.onAdd(widget)
+
+ 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] = NotifyBoolVar()
+ 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)
diff --git a/jfr_playoff/gui/frames/match.py b/jfr_playoff/gui/frames/match.py
new file mode 100644
index 0000000..2cf88bf
--- /dev/null
+++ b/jfr_playoff/gui/frames/match.py
@@ -0,0 +1,856 @@
+#coding=utf-8
+
+from collections import OrderedDict
+
+import tkinter as tk
+from tkinter.font import Font
+from tkinter import ttk
+
+from ..frames import GuiFrame, RepeatableFrame, ScrollableFrame
+from ..frames import WidgetRepeater, RepeatableEntry, NumericSpinbox
+from ..frames import SelectionFrame, SelectionButton, RefreshableOptionMenu
+from ..frames.team import DBSelectionField, TeamSelectionFrame
+from ..frames.team import TeamSelectionButton
+from ..frames.visual import PositionsSelectionFrame
+from ..variables import NotifyStringVar, NotifyIntVar
+from ..variables import NotifyNumericVar, NotifyBoolVar
+
+class SwissSettingsFrame(RepeatableFrame):
+ SOURCE_LINK = 0
+ SOURCE_DB = 1
+
+ def _setPositionInfo(self, *args):
+ tournamentFrom = self.setFrom.get(default=1)
+ tournamentTo = min(
+ self.setTo.get(default=1) \
+ if self.setToEnabled.get() else 9999,
+ len(self.winfo_toplevel().getTeams()))
+ swissFrom = self.fetchFrom.get(default=1)
+ swissTo = swissFrom + tournamentTo - tournamentFrom
+ if tournamentTo < tournamentFrom:
+ self.positionsInfo.configure(text='brak miejsc do ustawienia')
+ else:
+ self.positionsInfo.configure(text='%d-%d -> %d-%d' % (
+ swissFrom, swissTo, tournamentFrom, tournamentTo))
+
+ def _setFields(self, *args):
+ checkFields = [self.setToEnabled, self.fetchFromEnabled]
+ for child in self.winfo_children():
+ info = child.grid_info()
+ row = int(info['row'])
+ if row in [1, 2] and not isinstance(child, ttk.Radiobutton):
+ child.configure(
+ state=tk.NORMAL if self.source.get() == 2 - row \
+ else tk.DISABLED)
+ elif row in [5, 6] and isinstance(child, tk.Spinbox):
+ child.configure(
+ state=tk.NORMAL if checkFields[row-5].get() \
+ else tk.DISABLED)
+
+ def renderContent(self):
+ self.source = NotifyIntVar()
+ self.fetchDB = NotifyStringVar()
+ self.fetchLink = NotifyStringVar()
+ self.setFrom = NotifyNumericVar()
+ self.setToEnabled = NotifyBoolVar()
+ self.setTo = NotifyNumericVar()
+ self.fetchFromEnabled = NotifyBoolVar()
+ self.fetchFrom = NotifyNumericVar()
+ self.linkLabel = NotifyStringVar()
+ self.linkRelPath = NotifyStringVar()
+
+ self.columnconfigure(1, weight=1)
+ self.columnconfigure(2, weight=1)
+ self.columnconfigure(3, weight=1)
+
+ (ttk.Label(self, text='Źródło danych:')).grid(
+ row=0, column=0, sticky=tk.W)
+ (ttk.Radiobutton(
+ self, text='Baza danych',
+ variable=self.source, value=self.SOURCE_DB)).grid(
+ row=1, column=0, sticky=tk.W)
+ self.fetchDBField = DBSelectionField(
+ self, self.fetchDB, self.fetchDB.get())
+ self.fetchDBField.grid(row=1, column=1, sticky=tk.W+tk.E)
+ (ttk.Radiobutton(
+ self, text='Strona turnieju',
+ variable=self.source, value=self.SOURCE_LINK)).grid(
+ row=2, column=0, sticky=tk.W)
+ (ttk.Entry(self, textvariable=self.fetchLink, width=20)).grid(
+ row=2, column=1, sticky=tk.W+tk.E)
+
+ (ttk.Separator(self, orient=tk.HORIZONTAL)).grid(
+ row=3, column=0, columnspan=6, sticky=tk.E+tk.W)
+
+ (ttk.Label(
+ self, text='Ustaw od miejsca: ')).grid(
+ row=4, column=0, sticky=tk.W, padx=18)
+ (NumericSpinbox(
+ self, textvariable=self.setFrom,
+ from_=1, to=999, width=5)).grid(
+ row=4, column=1, sticky=tk.W)
+ (ttk.Checkbutton(
+ self, variable=self.setToEnabled,
+ text='Ustaw do miejsca: ')).grid(
+ row=5, column=0, sticky=tk.W)
+ (NumericSpinbox(
+ self, textvariable=self.setTo,
+ from_=1, to=999, width=5)).grid(
+ row=5, column=1, sticky=tk.W)
+ (ttk.Checkbutton(
+ self, variable=self.fetchFromEnabled,
+ text='Pobierz od miejsca: ')).grid(
+ row=6, column=0, sticky=tk.W)
+ (NumericSpinbox(
+ self, textvariable=self.fetchFrom,
+ from_=1, to=999, width=5)).grid(
+ row=6, column=1, sticky=tk.W)
+
+ (ttk.Label(self, text='Miejsca w swissie')).grid(
+ row=4, column=2)
+ (ttk.Label(self, text='Miejsca w klasyfikacji')).grid(
+ row=4, column=3)
+ self.positionsInfo = ttk.Label(self, text=' -> ', font=Font(size=16))
+ self.positionsInfo.grid(row=5, column=2, columnspan=2, rowspan=2)
+
+ (ttk.Label(self, text='Etykieta linku:')).grid(
+ row=8, column=0, sticky=tk.E)
+ (ttk.Entry(self, textvariable=self.linkLabel, width=20)).grid(
+ row=8, column=1, sticky=tk.W+tk.E)
+ (ttk.Label(self, text='(domyślnie: "Turniej o #. miejsce")')).grid(
+ row=8, column=2, sticky=tk.W)
+
+ (ttk.Label(self, text='Względna ścieżka linku do swissa:')).grid(
+ row=1, column=2, sticky=tk.E)
+ (ttk.Entry(self, textvariable=self.linkRelPath, width=20)).grid(
+ row=1, column=3, sticky=tk.W+tk.E)
+
+ (ttk.Separator(self, orient=tk.HORIZONTAL)).grid(
+ row=7, column=0, columnspan=6, sticky=tk.E+tk.W)
+ (ttk.Separator(self, orient=tk.HORIZONTAL)).grid(
+ row=9, column=0, columnspan=6, sticky=tk.E+tk.W)
+
+ self._setFields()
+ self._setPositionInfo()
+
+ self.fetchFromEnabled.trace('w', self._setFields)
+ self.setToEnabled.trace('w', self._setFields)
+ self.source.trace('w', self._setFields)
+ self.setFrom.trace('w', self._setPositionInfo)
+ self.setTo.trace('w', self._setPositionInfo)
+ self.fetchFrom.trace('w', self._setPositionInfo)
+ self.fetchFromEnabled.trace('w', self._setPositionInfo)
+ self.setToEnabled.trace('w', self._setPositionInfo)
+ self.winfo_toplevel().bind(
+ '<<TeamListChanged>>', self._setPositionInfo, add='+')
+
+ def setValue(self, value):
+ if 'database' in value:
+ self.source.set(self.SOURCE_DB)
+ self.fetchDB.set(value['database'])
+ self.fetchLink.set('')
+ else:
+ self.source.set(self.SOURCE_LINK)
+ self.fetchDB.set('')
+ if 'link' in value:
+ self.fetchLink.set(value['link'])
+ else:
+ self.fetchLink.set('')
+ self.setFrom.set(value['position'] if 'position' in value else 1)
+ if 'position_to' in value:
+ self.setToEnabled.set(1)
+ self.setTo.set(value['position_to'])
+ else:
+ self.setToEnabled.set(0)
+ self.setTo.set(1)
+ if 'swiss_position' in value:
+ self.fetchFromEnabled.set(1)
+ self.fetchFrom.set(value['swiss_position'])
+ else:
+ self.fetchFromEnabled.set(0)
+ self.fetchFrom.set(1)
+ self.linkLabel.set(value['label'] if 'label' in value else '')
+ self.linkRelPath.set(
+ value['relative_path'] if 'relative_path' in value else '')
+
+ def getValue(self):
+ config = OrderedDict()
+ if self.source.get() == self.SOURCE_DB:
+ config['database'] = self.fetchDB.get()
+ if self.linkRelPath.get():
+ config['relative_path'] = self.linkRelPath.get()
+ if self.source.get() == self.SOURCE_LINK:
+ config['link'] = self.fetchLink.get()
+ if self.linkLabel.get():
+ config['label'] = self.linkLabel.get()
+ config['position'] = self.setFrom.get()
+ if self.setToEnabled.get():
+ config['position_to'] = self.setTo.get()
+ if self.fetchFromEnabled.get():
+ config['swiss_position'] = self.fetchFrom.get()
+ return config
+
+class SwissesFrame(ScrollableFrame):
+ def renderContent(self, container):
+ self.swisses = WidgetRepeater(container, SwissSettingsFrame)
+ self.swisses.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ def setValues(self, values):
+ self.swisses.setValue(values)
+
+ def getValues(self):
+ return self.swisses.getValue()
+
+class MatchSelectionButton(SelectionButton):
+ @property
+ def defaultPrompt(self):
+ return 'Wybierz mecze:'
+
+ @property
+ def title(self):
+ return 'Wybór meczów'
+
+ @property
+ def errorMessage(self):
+ return 'W turnieju nie zdefiniowano żadnych meczów'
+
+ def getOptions(self):
+ matches = self.winfo_toplevel().getMatches()
+ values = []
+ prev_match = None
+ for match in matches:
+ if prev_match is not None:
+ if prev_match.getPhase() != match.getPhase():
+ values.append(None)
+ values.append(match)
+ prev_match = match
+ return values
+
+
+class MatchSelectionFrame(SelectionFrame):
+ def renderOption(self, container, option, idx):
+ if option is not None:
+ (ttk.Label(
+ container, text='[%d]' % (self._mapValue(idx, option)))).grid(
+ row=idx+1, column=0)
+ (ttk.Checkbutton(
+ container, text=option.label,
+ variable=self.values[self._mapValue(idx, option)]
+ )).grid(row=idx+1, column=1, sticky=tk.W)
+ else:
+ (ttk.Separator(
+ container, orient=tk.HORIZONTAL)).grid(
+ row=idx+1, column=0, columnspan=2,
+ sticky=tk.E+tk.W, pady=2)
+
+ def _mapValue(self, idx, value):
+ if self.options[idx] is not None:
+ return self.options[idx].getMatchID()
+
+
+class SelectedTeamList(RefreshableOptionMenu):
+ VALUE_LABELS = {
+ 'none': u'%s',
+ 'winner': u'Zwycięzca meczu %d',
+ 'loser': u'Przegrany meczu %d',
+ 'place': u'Drużyna z miejsca %d'
+ }
+
+ def __init__(self, *args, **kwargs):
+ RefreshableOptionMenu.__init__(self, *args, **kwargs)
+ self.master.bind(
+ '<<BracketConfigChanged>>', self.refreshOptions, add='+')
+
+ def getValues(self):
+ config = self.master.getConfig()
+ values = [('none', '')]
+ if isinstance(config, dict):
+ for key in ['winner', 'loser', 'place']:
+ if key in config:
+ for value in config[key]:
+ values.append((key, value))
+ return values
+
+ def getLabel(self, value):
+ return self.VALUE_LABELS[value[0]] % (value[1])
+
+ def getVarValue(self, value):
+ return unicode(value)
+
+
+class BracketMatchSettingsFrame(GuiFrame):
+ SOURCE_TEAM=0
+ SOURCE_BRACKET=1
+ LIST_WIDGETS = {'place': 5, 'winner': 1, 'loser': 3}
+
+ def _enablePanels(self, *args):
+ for widget in self.teamWidgets:
+ widget.configure(
+ state=tk.NORMAL if self.source.get() == self.SOURCE_TEAM
+ else tk.DISABLED)
+ for widget in self.bracketWidgets:
+ widget.configure(
+ state=tk.NORMAL if self.source.get() == self.SOURCE_BRACKET
+ else tk.DISABLED)
+ if self.source.get() == self.SOURCE_BRACKET \
+ and isinstance(widget, SelectedTeamList) \
+ and not self.selected.get():
+ widget.configure(state=tk.DISABLED)
+
+ def _configChangeNotify(self, *args):
+ self.event_generate('<<BracketConfigChanged>>', when='tail')
+
+ def _setPositions(self, positions):
+ self.positions = positions
+ self._configChangeNotify()
+
+ def _setLosers(self, matches):
+ self.losers = matches
+ self._configChangeNotify()
+
+ def _setWinners(self, matches):
+ self.winners = matches
+ self._configChangeNotify()
+
+ def _setTeams(self, teams):
+ if not self._lockTeams:
+ allTeams = [team[0] for team in self.teamWidgets[0].getOptions()]
+ self.teams = [allTeams[idx-1] for idx in teams]
+
+ def renderContent(self):
+ self.source = NotifyIntVar()
+ self.source.trace('w', self._enablePanels)
+ self.source.trace('w', self._configChangeNotify)
+ self.selected = NotifyBoolVar()
+ self.selected.trace('w', self._enablePanels)
+ self.selectedIndex = NotifyStringVar()
+ self.positions = []
+ self.winners = []
+ self.losers = []
+ self.teams = []
+ self._lockTeams = True
+ self.winfo_toplevel().bind(
+ '<<TeamListChanged>>', self._onTeamListChange, add='+')
+ self.winfo_toplevel().bind(
+ '<<MatchListChanged>>', self._onMatchListChange, add='+')
+
+ buttons = [
+ ttk.Radiobutton(
+ self, variable=self.source, value=self.SOURCE_TEAM,
+ text='Konkretne teamy'),
+ ttk.Radiobutton(
+ self, variable=self.source, value=self.SOURCE_BRACKET,
+ text='Z drabinki')]
+ self.teamWidgets = [
+ TeamSelectionButton(
+ self, prompt='Wybierz drużyny:',
+ dialogclass=TeamSelectionFrame,
+ callback=self._setTeams)]
+ self.bracketWidgets = [
+ ttk.Label(self, text='Zwycięzcy meczów:'),
+ MatchSelectionButton(
+ self, prompt='Wybierz mecze:',
+ dialogclass=MatchSelectionFrame,
+ callback=self._setWinners),
+ ttk.Label(self, text='Przegrani meczów:'),
+ MatchSelectionButton(
+ self, prompt='Wybierz mecze:',
+ dialogclass=MatchSelectionFrame,
+ callback=self._setLosers),
+ ttk.Label(self, text='Pozycje początkowe:'),
+ TeamSelectionButton(
+ self, prompt='Wybierz pozycje początkowe:',
+ dialogclass=PositionsSelectionFrame,
+ callback=self._setPositions),
+ ttk.Checkbutton(
+ self, text='Uczestnik został wybrany:',
+ variable=self.selected),
+ SelectedTeamList(self, self.selectedIndex)
+ ]
+
+ for idx, button in enumerate(buttons):
+ button.grid(row=idx, column=0, sticky=tk.W)
+ self.teamWidgets[0].grid(row=0, column=1, sticky=tk.W)
+ for idx, widget in enumerate(self.bracketWidgets):
+ widget.grid(row=1+idx/2, column=1+idx%2, sticky=tk.W)
+
+ self._enablePanels()
+
+ self._lockTeams = False
+
+ def _onTeamListChange(self, *args):
+ teamsToSet = []
+ teams = [team[0] for team in self.teamWidgets[0].getOptions()]
+ for team in self.teams:
+ try:
+ teamsToSet.append(teams.index(team)+1)
+ except ValueError:
+ pass
+ self._lockTeams = True
+ self.teamWidgets[0].setPositions(teamsToSet)
+ self._lockTeams = False
+
+ def _onMatchListChange(self, *args):
+ try:
+ matches = [
+ match.getMatchID() for match
+ in self.bracketWidgets[
+ self.LIST_WIDGETS['winner']].getOptions()
+ if match is not None]
+ self.bracketWidgets[self.LIST_WIDGETS['winner']].setPositions([
+ winner for winner in self.winners if winner in matches])
+ self.bracketWidgets[self.LIST_WIDGETS['loser']].setPositions([
+ loser for loser in self.losers if loser in matches])
+ except tk.TclError as e:
+ # we're probably trying to update our widget when
+ # WE'RE the match that's being destroyed
+ pass
+
+ def setValue(self, value):
+ if isinstance(value, (str, unicode)):
+ value = [value]
+ if isinstance(value, list):
+ self.source.set(self.SOURCE_TEAM)
+ self.teams = list(set(value))
+ for idx in self.LIST_WIDGETS.values():
+ self.bracketWidgets[idx].setPositions([])
+ else:
+ self.source.set(self.SOURCE_BRACKET)
+ self.teams = []
+ for key, idx in self.LIST_WIDGETS.iteritems():
+ self.bracketWidgets[idx].setPositions(
+ value[key]
+ if key in value and isinstance(value[key], list)
+ else [])
+
+ def setSelectedTeam(self, team):
+ if team > -1:
+ self.selectedIndex.set(self.bracketWidgets[7].getValues()[team+1])
+ self.selected.set(1)
+ else:
+ self.selectedIndex.set(('none', ''))
+ self.selected.set(0)
+
+ def getSelectedTeam(self):
+ if self.selected.get():
+ try:
+ return self.bracketWidgets[7].getValues().index(
+ self.selectedIndex.get())
+ except ValueError:
+ return -1
+ else:
+ return -1
+
+ def getConfig(self):
+ if self.source.get() == self.SOURCE_TEAM:
+ return self.teams
+ else:
+ config = OrderedDict()
+ lists = {
+ 5: self.positions,
+ 1: self.winners,
+ 3: self.losers
+ }
+ for key, idx in self.LIST_WIDGETS.iteritems():
+ values = lists[idx]
+ if len(values) > 0:
+ config[key] = values
+ return config
+
+ def getValue(self):
+ return self.getConfig()
+
+class MatchSettingsFrame(RepeatableFrame):
+ SCORE_SOURCE_DB = 0
+ SCORE_SOURCE_LINK = 1
+ SCORE_SOURCE_CUSTOM = 2
+
+ def destroy(self, *args, **kwargs):
+ self.winfo_toplevel().event_generate(
+ '<<MatchListChanged>>', when='tail')
+ RepeatableFrame.destroy(self, *args, **kwargs)
+
+ def _enablePanels(self, *args):
+ for val, fields in self.scoreWidgets.iteritems():
+ for field in fields:
+ field.configure(
+ state=tk.NORMAL if self.source.get() == val
+ else tk.DISABLED)
+ if not self.scoreNotFinished.get():
+ self.scoreWidgets[self.SCORE_SOURCE_CUSTOM][-2].configure(
+ state=tk.DISABLED)
+
+ def _updateName(self, *args):
+ self.nameLabel.configure(text=self.label)
+
+ def _setWinnerPositions(self, values):
+ self.winnerPositions = values
+
+ def _setLoserPositions(self, values):
+ self.loserPositions = values
+
+ def renderContent(self):
+ self.nameLabel = ttk.Label(self)
+ self.matchID = NotifyIntVar()
+ self.matchID.trace('w', self._updateName)
+ self.matchID.set(self.winfo_toplevel().getNewMatchID(self))
+ self.winfo_toplevel().bind(
+ '<<PhaseRenamed>>', self._updateName, add='+')
+ self.link = NotifyStringVar()
+
+ self.source = NotifyIntVar()
+ self.source.trace('w', self._enablePanels)
+
+ self.scoreDB = NotifyStringVar()
+ self.scoreRound = NotifyNumericVar()
+ self.scoreTable = NotifyNumericVar()
+ self.scoreCustom = [NotifyStringVar(), NotifyStringVar()]
+ self.scoreNotFinished = NotifyBoolVar()
+ self.scoreNotFinished.trace('w', self._enablePanels)
+ self.scoreBoards = NotifyNumericVar()
+
+ self.winnerPositions = []
+ self.loserPositions = []
+
+ self.columnconfigure(1, weight=1)
+ self.columnconfigure(2, weight=1)
+
+ self.nameLabel.grid(row=0, column=0, sticky=tk.W)
+ (ttk.Label(self, text='Link:')).grid(row=0, column=1, sticky=tk.E)
+ (ttk.Entry(self, textvariable=self.link)).grid(
+ row=0, column=2, sticky=tk.W+tk.E)
+
+ bracketGroup = ttk.LabelFrame(self, text='Dane drabinki')
+ bracketGroup.grid(row=1, column=0, columnspan=3, sticky=tk.W+tk.E)
+
+ bracketGroup.columnconfigure(0, weight=1)
+ bracketGroup.columnconfigure(1, weight=1)
+
+ homeTeam = ttk.LabelFrame(bracketGroup, text='Team gospodarzy')
+ homeTeam.grid(row=0, column=0, sticky=tk.W+tk.E)
+ awayTeam = ttk.LabelFrame(bracketGroup, text='Team gości')
+ awayTeam.grid(row=0, column=1, sticky=tk.W+tk.E)
+
+ teamFrames = [homeTeam, awayTeam]
+ self.bracketSettings = []
+ for frame in teamFrames:
+ bracket = BracketMatchSettingsFrame(frame)
+ bracket.grid(row=0, column=0, sticky=tk.N+tk.S+tk.W+tk.E)
+ self.bracketSettings.append(bracket)
+
+ scoreGroup = ttk.LabelFrame(self, text='Dane wyniku meczu')
+ scoreGroup.grid(row=4, column=0, columnspan=3, sticky=tk.W+tk.E)
+
+ scoreGroup.columnconfigure(1, weight=1)
+ scoreGroup.columnconfigure(3, weight=1)
+
+ self.scoreWidgets = {
+ self.SCORE_SOURCE_DB: [
+ DBSelectionField(scoreGroup, self.scoreDB, self.scoreDB.get()),
+ ttk.Label(scoreGroup, text='Runda:'),
+ NumericSpinbox(
+ scoreGroup, width=3,
+ textvariable=self.scoreRound, from_=1, to=999),
+ ttk.Label(scoreGroup, text='Stół:'),
+ NumericSpinbox(
+ scoreGroup, width=3,
+ textvariable=self.scoreTable, from_=1, to=999)
+ ],
+ self.SCORE_SOURCE_LINK: [
+ ttk.Entry(scoreGroup, textvariable=self.link),
+ # TODO: TC support (Round/Session)
+ #ttk.Label(scoreGroup, text='Sesja:'),
+ #NumericSpinbox(
+ # scoreGroup,
+ #textvariable=self.scoreSession, from_=1, to=999),
+ #ttk.Label(scoreGroup, text='Runda:'),
+ #NumericSpinbox(
+ # scoreGroup,
+ #textvariable=self.scoreRound, from_=1, to=999),
+ ttk.Label(scoreGroup, text='Stół:'),
+ NumericSpinbox(
+ scoreGroup, width=3,
+ textvariable=self.scoreTable, from_=1, to=999)
+ ],
+ self.SCORE_SOURCE_CUSTOM: [
+ ttk.Entry(
+ scoreGroup, textvariable=self.scoreCustom[0],
+ width=10, justify=tk.RIGHT),
+ ttk.Label(scoreGroup, text=':'),
+ ttk.Entry(
+ scoreGroup, textvariable=self.scoreCustom[1],
+ width=10, justify=tk.RIGHT),
+ ttk.Checkbutton(
+ scoreGroup, variable=self.scoreNotFinished,
+ text='mecz nie został zakończony, rozegrano:'),
+ NumericSpinbox(
+ scoreGroup, width=3,
+ textvariable=self.scoreBoards, from_=0, to=999),
+ ttk.Label(scoreGroup, text='rozdań')
+ ]
+ }
+
+ (ttk.Radiobutton(
+ scoreGroup, variable=self.source, value=self.SCORE_SOURCE_DB,
+ text='Baza danych')).grid(row=0, column=0, sticky=tk.W)
+ self.scoreWidgets[self.SCORE_SOURCE_DB][0].grid(
+ row=0, column=1, columnspan=3, sticky=tk.W+tk.E)
+ for idx in range(1, 5):
+ self.scoreWidgets[self.SCORE_SOURCE_DB][idx].grid(
+ row=0, column=idx+3)
+ (ttk.Radiobutton(
+ scoreGroup, variable=self.source, value=self.SCORE_SOURCE_LINK,
+ text='Strona z wynikami')).grid(row=1, column=0, sticky=tk.W)
+ self.scoreWidgets[self.SCORE_SOURCE_LINK][0].grid(
+ row=1, column=1, columnspan=3, sticky=tk.W+tk.E)
+ self.scoreWidgets[self.SCORE_SOURCE_LINK][1].grid(
+ row=1, column=4)
+ self.scoreWidgets[self.SCORE_SOURCE_LINK][2].grid(
+ row=1, column=5)
+ (ttk.Radiobutton(
+ scoreGroup, variable=self.source, value=self.SCORE_SOURCE_CUSTOM,
+ text='Ustaw ręcznie')).grid(row=2, column=0, sticky=tk.W)
+ for idx in range(0, 3):
+ self.scoreWidgets[self.SCORE_SOURCE_CUSTOM][idx].grid(
+ row=2, column=idx+1, sticky=tk.E if idx == 0 else tk.W)
+ self.scoreWidgets[self.SCORE_SOURCE_CUSTOM][3].grid(
+ row=2, column=4, columnspan=4)
+ for idx in range(4, 6):
+ self.scoreWidgets[self.SCORE_SOURCE_CUSTOM][idx].grid(
+ row=2, column=idx+5)
+
+ (ttk.Label(bracketGroup, text='Zwycięzca zajmie miejsca:')).grid(
+ row=1, column=0, sticky=tk.E)
+ self.winnerPositionsBtn = TeamSelectionButton(
+ bracketGroup, prompt='Wybierz pozycje końcowe:',
+ dialogclass=PositionsSelectionFrame,
+ callback=self._setWinnerPositions)
+ self.winnerPositionsBtn.grid(row=1, column=1, sticky=tk.W)
+ (ttk.Label(bracketGroup, text='Przegrany zajmie miejsca:')).grid(
+ row=2, column=0, sticky=tk.E)
+ self.loserPositionsBtn = TeamSelectionButton(
+ bracketGroup, prompt='Wybierz pozycje końcowe:',
+ dialogclass=PositionsSelectionFrame,
+ callback=self._setLoserPositions)
+ self.loserPositionsBtn.grid(row=2, column=1, sticky=tk.W)
+
+ self._enablePanels()
+
+ self.winfo_toplevel().event_generate(
+ '<<MatchListChanged>>', when='tail')
+
+ @classmethod
+ def info(cls):
+ return 'Nowy mecz'
+
+ def getMatchID(self):
+ return self.matchID.get()
+
+ def getPhase(self):
+ obj = self
+ while not isinstance(obj, MatchPhaseFrame):
+ obj = obj.master
+ if obj is None:
+ break
+ return obj
+
+ @property
+ def label(self):
+ try:
+ phase = self.getPhase()
+ return 'Mecz #%d [faza: %s]' % (
+ self.getMatchID(),
+ phase.master.tab(phase)['text'].strip()
+ if phase is not None else '')
+ except tk.TclError:
+ # we're probably just being created, ignore
+ return ''
+
+ def setValue(self, value):
+ self.matchID.set(value['id'] if 'id' in value else 0)
+ self.link.set(value['link'] if 'link' in value else '')
+
+ self.scoreDB.set(value['database'] if 'database' in value else '')
+ self.scoreRound.set(value['round'] if 'round' in value else 1)
+ self.scoreTable.set(value['table'] if 'table' in value else 1)
+
+ if 'score' in value:
+ for idx in range(0, 2):
+ self.scoreCustom[idx].set(
+ value['score'][idx]
+ if isinstance(value['score'], list)
+ and len(value['score']) > 1
+ else 0)
+ self.scoreNotFinished.set(
+ 'running' in value and value['running'] >= 0)
+ self.scoreBoards.set(
+ value['running'] if 'running' in value
+ and value['running'] >= 0 else 0)
+ else:
+ self.scoreNotFinished.set(0)
+ self.scoreBoards.set(0)
+
+ self.source.set(
+ self.SCORE_SOURCE_DB if 'database' in value else (
+ self.SCORE_SOURCE_CUSTOM if 'table' not in value
+ else self.SCORE_SOURCE_LINK
+ ))
+
+ if 'teams' in value and isinstance(value['teams'], list):
+ for idx, val in enumerate(value['teams']):
+ if idx < 2:
+ self.bracketSettings[idx].setValue(val)
+ else:
+ for idx in range(0, 2):
+ self.bracketSettings[idx].setValue({})
+
+ self.winnerPositionsBtn.setPositions(
+ value['winner']
+ if 'winner' in value and isinstance(value['winner'], list)
+ else [])
+ self.loserPositionsBtn.setPositions(
+ value['loser']
+ if 'loser' in value and isinstance(value['loser'], list)
+ else [])
+
+ if 'selected_teams' in value \
+ and isinstance(value['selected_teams'], list):
+ for idx, val in enumerate(value['selected_teams']):
+ if idx < 2:
+ self.bracketSettings[idx].setSelectedTeam(val)
+ else:
+ for idx in range(0, 2):
+ self.bracketSettings[idx].setSelectedTeam(-1)
+
+ def getValue(self):
+ config = OrderedDict()
+ config['id'] = self.matchID.get()
+ if self.link.get():
+ config['link'] = self.link.get()
+
+ config['teams'] = [bracket.getValue()
+ for bracket in self.bracketSettings]
+
+ if len(self.winnerPositions):
+ config['winner'] = self.winnerPositions
+ if len(self.loserPositions):
+ config['loser'] = self.loserPositions
+
+ selected = [bracket.getSelectedTeam()
+ for bracket in self.bracketSettings]
+ if len([s for s in selected if s > -1]):
+ config['selected_teams'] = selected
+
+ if self.source.get() == self.SCORE_SOURCE_DB:
+ config['database'] = self.scoreDB.get()
+ config['round'] = self.scoreRound.get()
+ if self.source.get() != self.SCORE_SOURCE_CUSTOM:
+ config['table'] = self.scoreTable.get()
+
+ if self.source.get() == self.SCORE_SOURCE_CUSTOM:
+ config['score'] = []
+ for score in self.scoreCustom:
+ try:
+ config['score'].append(float(score.get()))
+ except ValueError:
+ config['score'].append(0.0)
+ if self.scoreNotFinished.get():
+ config['running'] = self.scoreBoards.get()
+
+ return config
+
+ if 'selected_teams' in value \
+ and isinstance(value['selected_teams'], list):
+ for idx, val in enumerate(value['selected_teams']):
+ if idx < 2:
+ self.bracketSettings[idx].setSelectedTeam(val)
+ else:
+ for idx in range(0, 2):
+ self.bracketSettings[idx].setSelectedTeam(-1)
+
+
+
+class MatchSeparator(RepeatableFrame):
+ def renderContent(self):
+ (ttk.Separator(self, orient=tk.HORIZONTAL)).pack(
+ side=tk.TOP, fill=tk.X, expand=True)
+
+ @classmethod
+ def info(cls):
+ return 'Odstęp między meczami'
+
+
+class MatchPhaseFrame(ScrollableFrame):
+ def _updateLinks(self, *args):
+ for match in self.matches.widgets:
+ if isinstance(match, MatchSettingsFrame):
+ match_link = match.link.get()
+ if not len(match_link) or match_link == self.previousLink:
+ match.link.set(self.link.get())
+ self.previousLink = self.link.get()
+
+ def _signalPhaseRename(self, *args):
+ self.winfo_toplevel().event_generate('<<PhaseRenamed>>', when='tail')
+
+ def renderContent(self, container):
+ self.name = NotifyStringVar()
+ self.link = NotifyStringVar()
+ self.previousLink = ''
+
+ headerFrame = tk.Frame(container)
+ headerFrame.pack(side=tk.TOP, fill=tk.X, expand=True)
+ (ttk.Label(headerFrame, text='Nazwa:')).pack(side=tk.LEFT)
+ (ttk.Entry(headerFrame, textvariable=self.name)).pack(
+ side=tk.LEFT, fill=tk.X, expand=True)
+ (ttk.Label(headerFrame, text='Link:')).pack(side=tk.LEFT)
+ (ttk.Entry(headerFrame, textvariable=self.link)).pack(
+ side=tk.LEFT, fill=tk.X, expand=True)
+
+ self.matches = WidgetRepeater(
+ container, [MatchSettingsFrame, MatchSeparator],
+ onAdd=self._matchAdded)
+ self.matches.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ self.link.trace('w', self._updateLinks)
+ self.name.trace('w', self._signalPhaseRename)
+
+ def _matchAdded(self, widget):
+ self.after(100, self.canvas.yview_moveto, 1.0)
+
+ def setValues(self, values):
+ matches = values['matches'] if 'matches' in values else []
+ dummies = values['dummies'] if 'dummies' in values else []
+ objects = [(MatchSeparator, None)] * (len(matches) + len(dummies))
+ idx = 0
+ for match in matches:
+ while idx in dummies:
+ idx += 1
+ objects[idx] = (MatchSettingsFrame, match)
+ idx += 1
+ self.matches.setValue(objects)
+ self.link.set(values['link'] if 'link' in values else '')
+ self.name.set(values['title'] if 'title' in values else '')
+ self.winfo_toplevel().event_generate(
+ '<<MatchListChanged>>', when='tail')
+
+ def getConfig(self):
+ config = OrderedDict()
+ if self.name.get():
+ config['title'] = self.name.get()
+ if self.link.get():
+ config['link'] = self.link.get()
+ values = self.matches.getValue()
+ dummies = []
+ matches = []
+ for idx, value in enumerate(values):
+ if value is None:
+ dummies.append(idx)
+ else:
+ matches.append(value)
+ if len(dummies):
+ config['dummies'] = dummies
+ config['matches'] = matches
+ return config
+
+
+__all__ = ['SwissesFrame', 'MatchPhaseFrame', 'MatchSettingsFrame']
diff --git a/jfr_playoff/gui/frames/network.py b/jfr_playoff/gui/frames/network.py
new file mode 100644
index 0000000..fb27434
--- /dev/null
+++ b/jfr_playoff/gui/frames/network.py
@@ -0,0 +1,194 @@
+#coding=utf-8
+
+import socket
+from collections import OrderedDict
+
+import tkinter as tk
+from tkinter import ttk
+import tkMessageBox as tkmb
+
+from ...db import PlayoffDB
+from ..frames import RepeatableEntry, WidgetRepeater, NumericSpinbox
+from ..frames import GuiFrame, ScrollableFrame
+from ..variables import NotifyStringVar, NotifyNumericVar, NotifyBoolVar
+
+def network_test(connFunction, testLabel):
+ try:
+ connFunction()
+ testLabel.configure(text='✓')
+ testLabel.configure(foreground='green')
+ return None
+ except Exception as e:
+ testLabel.configure(text='✗')
+ testLabel.configure(foreground='red')
+ return unicode(str(e).decode('utf-8', errors='replace'))
+
+class MySQLConfigurationFrame(GuiFrame):
+ DEFAULT_PORT = 3306
+
+ def getConfig(self):
+ if len(self.host.get().strip()):
+ return OrderedDict({
+ 'host': self.host.get().strip(),
+ 'port': self.port.get(default=3306),
+ 'user': self.user.get().strip(),
+ 'pass': self.pass_.get().strip()
+ })
+ return None
+
+ def _testDB(self):
+ def test():
+ dbConfig = self.getConfig()
+ if dbConfig is None:
+ raise AttributeError('Database not configured')
+ db = PlayoffDB(dbConfig)
+ self.dbError = network_test(test, self.dbTestLabel)
+
+ def _dbError(self, event):
+ if self.dbError is not None:
+ tkmb.showerror('Błąd połączenia z bazą danych', self.dbError)
+
+ def _changeNotify(self, *args):
+ self.winfo_toplevel().event_generate(
+ '<<DBSettingsChanged>>', when='tail')
+
+ def renderContent(self):
+ self.host = NotifyStringVar()
+ self.host.trace('w', self._changeNotify)
+ self.port = NotifyNumericVar()
+ self.port.trace('w', self._changeNotify)
+ self.user = NotifyStringVar()
+ self.user.trace('w', self._changeNotify)
+ self.pass_ = NotifyStringVar()
+ self.pass_.trace('w', self._changeNotify)
+
+ self.columnconfigure(0, weight=1)
+
+ frame = ttk.LabelFrame(self, text='Ustawienia MySQL')
+ frame.grid(row=0, column=0, columnspan=4, sticky=tk.E+tk.W+tk.N+tk.S)
+
+ (ttk.Label(frame, text='Host:')).grid(
+ row=0, column=0, sticky=tk.E)
+ (ttk.Entry(frame, textvariable=self.host)).grid(
+ row=0, column=1, sticky=tk.E+tk.W)
+
+ (ttk.Label(frame, text='Port:')).grid(
+ row=0, column=2, sticky=tk.E)
+ (NumericSpinbox(
+ frame, textvariable=self.port, width=5,
+ from_=0, to=65535)).grid(row=0, column=3, sticky=tk.W)
+
+ (ttk.Label(frame, text='Użytkownik:')).grid(
+ row=1, column=0, sticky=tk.E)
+ (ttk.Entry(frame, textvariable=self.user)).grid(
+ row=1, column=1, sticky=tk.E+tk.W)
+
+ (ttk.Button(
+ frame, text='Testuj ustawienia', command=self._testDB)).grid(
+ row=1, column=3)
+ self.dbError = None
+ self.dbTestLabel = ttk.Label(frame)
+ self.dbTestLabel.grid(row=1, column=4)
+ self.dbTestLabel.bind('<Button-1>', self._dbError)
+
+ (ttk.Label(frame, text='Hasło:')).grid(
+ row=2, column=0, sticky=tk.E)
+ (ttk.Entry(frame, textvariable=self.pass_, show='*')).grid(
+ row=2, column=1, sticky=tk.E+tk.W)
+
+ self.setValues({})
+
+ def setValues(self, values):
+ self.host.set(values['host'] if 'host' in values else '')
+ self.port.set(
+ values['port'] if 'port' in values else self.DEFAULT_PORT)
+ self.user.set(values['user'] if 'user' in values else '')
+ self.pass_.set(values['pass'] if 'pass' in values else '')
+
+class GoniecConfigurationFrame(GuiFrame):
+ DEFAULT_HOST = 'localhost'
+ DEFAULT_PORT = 8090
+
+ def _enableWidgets(self, *args):
+ for field in [self.portField, self.hostField, self.testButton]:
+ field.configure(
+ state=tk.NORMAL if self.enable.get() else tk.DISABLED)
+
+ def _test(self):
+ def test():
+ goniec = socket.socket()
+ goniec.connect(
+ (self.host.get().strip(),
+ self.port.get(default=self.DEFAULT_PORT)))
+ goniec.close()
+ self.testError = network_test(test, self.testLabel)
+
+ def _testError(self, event):
+ if self.testError is not None:
+ tkmb.showerror('Błąd połączenia z Gońcem', self.testError)
+
+ def renderContent(self):
+ self.enable = NotifyBoolVar()
+ self.enable.trace('w', self._enableWidgets)
+ self.host = NotifyStringVar()
+ self.port = NotifyNumericVar()
+
+ self.columnconfigure(0, weight=1)
+
+ frame = ttk.LabelFrame(self, text='Konfiguracja Gońca:')
+ frame.grid(row=0, column=0, columnspan=4, sticky=tk.W+tk.E+tk.N+tk.S)
+ (ttk.Checkbutton(
+ frame, text='Włącz obsługę Gońca', variable=self.enable)).grid(
+ row=0, column=0, columnspan=2, sticky=tk.W)
+
+ (ttk.Label(frame, text='Host:')).grid(row=1, column=0)
+ self.hostField = ttk.Entry(frame, textvariable=self.host)
+ self.hostField.grid(row=1, column=1)
+
+ (ttk.Label(frame, text='Port:')).grid(row=1, column=2)
+ self.portField = NumericSpinbox(
+ frame, textvariable=self.port, width=5)
+ self.portField.grid(row=1, column=3)
+
+ self.testButton = ttk.Button(
+ frame, text='Testuj ustawienia', command=self._test)
+ self.testButton.grid(row=2, column=1, sticky=tk.E)
+ self.testError = None
+ self.testLabel = ttk.Label(frame)
+ self.testLabel.grid(row=2, column=2, sticky=tk.W)
+ self.testLabel.bind('<Button-1>', self._testError)
+
+ self.setValues({})
+
+ def setValues(self, values):
+ self.host.set(
+ values['host'] if 'host' in values else self.DEFAULT_HOST)
+ self.port.set(
+ values['port'] if 'port' in values else self.DEFAULT_PORT)
+ self.enable.set(values['enabled'] if 'enabled' in values else 0)
+
+ def getValues(self):
+ config = OrderedDict({
+ 'enabled': self.enable.get()
+ })
+ if self.enable.get():
+ config['host'] = self.host.get()
+ config['port'] = self.port.get()
+ return config
+
+class RemoteConfigurationFrame(ScrollableFrame):
+ def renderContent(self, container):
+ frame = ttk.LabelFrame(container, text='Zdalne pliki konfiguracyjne:')
+ frame.pack(
+ side=tk.TOP, fill=tk.BOTH, expand=True)
+ self.repeater = WidgetRepeater(
+ frame, RepeatableEntry, classParams={'width':100})
+ self.repeater.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ def setValues(self, values):
+ self.repeater.setValue(values)
+
+ def getValues(self):
+ return self.repeater.getValue()
+
+__all__ = ['MySQLConfigurationFrame', 'GoniecConfigurationFrame', 'RemoteConfigurationFrame']
diff --git a/jfr_playoff/gui/frames/team.py b/jfr_playoff/gui/frames/team.py
new file mode 100644
index 0000000..b1d9841
--- /dev/null
+++ b/jfr_playoff/gui/frames/team.py
@@ -0,0 +1,545 @@
+#coding=utf-8
+
+from collections import OrderedDict
+
+import tkinter as tk
+from tkinter.font import Font
+from tkinter import ttk
+
+from ..frames import GuiFrame, RepeatableFrame, ScrollableFrame
+from ..frames import WidgetRepeater, RepeatableEntry, NumericSpinbox
+from ..frames import SelectionButton, SelectionFrame, RefreshableOptionMenu
+from ..frames import setPanelState
+from ..variables import NotifyStringVar, NotifyIntVar, NotifyNumericVar
+
+class ManualTeamRow(RepeatableFrame):
+ def renderContent(self):
+ self.fullname = NotifyStringVar()
+ self.shortname = NotifyStringVar()
+ self.flag = NotifyStringVar()
+ self.position = NotifyNumericVar()
+ for var in [self.fullname, self.shortname, self.flag, self.position]:
+ var.trace('w', self._changeNotify)
+
+ fullnameField = ttk.Entry(self, width=20, textvariable=self.fullname)
+ fullnameField.grid(row=0, column=0)
+ shortnameField = ttk.Entry(self, width=20, textvariable=self.shortname)
+ shortnameField.grid(row=0, column=1)
+ flagField = ttk.Entry(self, width=10, textvariable=self.flag)
+ flagField.grid(row=0, column=2)
+ positionField = ttk.Entry(self, width=10, textvariable=self.position)
+ positionField.grid(row=0, column=3)
+
+ self._changeNotify(None)
+
+ def getValue(self):
+ flag = self.flag.get().strip()
+ position = self.position.get()
+ return [
+ self.fullname.get().strip(), self.shortname.get().strip(),
+ flag if len(flag) else None, position
+ ]
+
+ def setValue(self, value):
+ self.fullname.set(value[0])
+ self.shortname.set(value[1])
+ if len(value) > 2:
+ if value[2] is not None:
+ self.flag.set(value[2])
+ if len(value) > 3:
+ if value[3] is not None:
+ self.position.set(value[3])
+
+ def _changeNotify(self, *args):
+ self.winfo_toplevel().event_generate(
+ '<<TeamSettingsChanged>>', when='tail')
+
+class TeamManualSettingsFrame(GuiFrame):
+ def renderContent(self):
+ headers = [
+ (ttk.Label, {'text': 'Pełna nazwa', 'width': 20}),
+ (ttk.Label, {'text': 'Skrócona nazwa', 'width': 20}),
+ (ttk.Label, {'text': 'Ikona', 'width': 10}),
+ (ttk.Label, {'text': 'Poz. końc.', 'width': 10}),
+ ]
+ self.repeater = WidgetRepeater(self, ManualTeamRow, headers=headers)
+ self.repeater.grid(row=1, column=0, columnspan=5)
+
+ def getTeams(self):
+ return [val for val in self.repeater.getValue() if len(val[0].strip())]
+
+ def setValues(self, values):
+ self.repeater.setValue(values)
+
+class TeamSelectionFrame(SelectionFrame):
+ def renderOption(self, container, option, idx):
+ (ttk.Label(
+ container, text='[%d]' % (self._mapValue(idx, option)))).grid(
+ row=idx+1, column=0)
+ (ttk.Checkbutton(
+ container, text=option[0],
+ variable=self.values[self._mapValue(idx, option)]
+ )).grid(row=idx+1, column=1, sticky=tk.W)
+
+class TeamSelectionButton(SelectionButton):
+ @property
+ def prompt(self):
+ return 'Wybierz teamy:'
+
+ @property
+ def title(self):
+ return 'Wybór teamów'
+
+ @property
+ def errorMessage(self):
+ return 'W turnieju nie ma teamów do wyboru'
+
+ def getOptions(self):
+ return self.winfo_toplevel().getTeams()
+
+
+class DBSelectionField(ttk.Entry):
+ def __init__(self, master, variable, value, *options, **kwargs):
+ kwargs['textvariable'] = variable
+ ttk.Entry.__init__(self, master, **kwargs)
+ self._variable = variable
+ self._variable.set(value)
+ self._optionDict = options if options is not None else []
+ self._matches = []
+ self._prevValue = None
+ self._setOptions()
+ self.bind('<KeyPress>', self._onPress)
+ self.bind('<KeyRelease>', self._onChange)
+ self.winfo_toplevel().bind(
+ '<<DBListChanged>>', self._setOptions, add='+')
+
+ def _setOptions(self, *args):
+ try:
+ self._optionDict = self.winfo_toplevel().getDBs()
+ except:
+ # some stuff may not be available yet
+ # don't worry, the event will fire when it is
+ self._optionDict = []
+
+ def _onPress(self, event):
+ if event.keysym == 'Tab':
+ try:
+ suggestion = self.selection_get()
+ if len(suggestion) > 0:
+ prefix = self._variable.get()[0:-len(suggestion)]
+ phrase = prefix + suggestion
+ next_suggestion = self._matches[
+ (self._matches.index(phrase)+1) % len(self._matches)]
+ prev_suggestion = self._matches[
+ (self._matches.index(phrase)-1) % len(self._matches)]
+ new_suggestion = prev_suggestion if event.state & 1 \
+ else next_suggestion
+ self.delete(0, tk.END)
+ self.insert(0, new_suggestion)
+ self.selection_range(len(prefix), tk.END)
+ return 'break'
+ except (tk.TclError, ValueError):
+ # no text selection or selection was altered, ignore
+ pass
+
+ def _onChange(self, event):
+ if self._prevValue == self._variable.get() or event.keysym == 'Tab':
+ return
+ self._prevValue = self._variable.get()
+ prefix = self._variable.get()
+ if len(prefix) > 0:
+ matches = [d for d in self._optionDict if d.startswith(prefix)]
+ if len(matches) > 0:
+ self._matches = matches
+ text_to_add = matches[0][len(prefix):]
+ self.insert(tk.END, text_to_add)
+ self.selection_range(len(prefix), tk.END)
+ return
+ self._matches = []
+
+class TeamFetchSettingsFrame(GuiFrame):
+ SOURCE_LINK = 0
+ SOURCE_DB = 1
+
+ def _setFinishingPositions(self, positions):
+ self.finishingPositions = positions
+ self._changeNotify(None)
+
+ def _changeNotify(self, *args):
+ self.winfo_toplevel().event_generate(
+ '<<TeamSettingsChanged>>', when='tail')
+
+ def getTeams(self):
+ teams = OrderedDict()
+ if self.fetchSource.get() == self.SOURCE_LINK:
+ teams['link'] = self.fetchLink.get()
+ elif self.fetchSource.get() == self.SOURCE_DB:
+ teams['database'] = self.fetchDB.get()
+ if len(self.finishingPositions):
+ teams['final_positions'] = self.finishingPositions
+ maxTeams = self.fetchLimit.get()
+ if maxTeams:
+ teams['max_teams'] = maxTeams
+ return teams
+
+ def _sourceChange(self, *args):
+ self.fetchDBField.configure(state=tk.DISABLED)
+ self.fetchLink.configure(state=tk.DISABLED)
+ if self.fetchSource.get() == self.SOURCE_LINK:
+ self.fetchLink.configure(state=tk.NORMAL)
+ elif self.fetchSource.get() == self.SOURCE_DB:
+ self.fetchDBField.configure(state=tk.NORMAL)
+
+ def renderContent(self):
+ self.fetchSource = NotifyIntVar()
+ self.fetchSource.trace('w', self._sourceChange)
+ self.fetchSource.trace('w', self._changeNotify)
+ self.fetchDB = NotifyStringVar()
+ self.fetchDB.trace('w', self._changeNotify)
+ self.link = NotifyStringVar()
+ self.link.trace('w', self._changeNotify)
+ self.fetchLimit = NotifyNumericVar()
+ self.fetchLimit.trace('w', self._changeNotify)
+
+ self.columnconfigure(3, weight=1)
+
+ (ttk.Label(self, text=' ')).grid(row=0, column=0, rowspan=2)
+
+ (ttk.Radiobutton(
+ self, text='Baza danych',
+ variable=self.fetchSource, value=self.SOURCE_DB)).grid(
+ row=0, column=1, columnspan=2, sticky=tk.W)
+ self.fetchDBField = DBSelectionField(
+ self, self.fetchDB, self.fetchDB.get())
+ self.fetchDBField.grid(row=0, column=3, sticky=tk.W+tk.E)
+
+ (ttk.Radiobutton(
+ self, text='Strona wyników',
+ variable=self.fetchSource, value=self.SOURCE_LINK)).grid(
+ row=1, column=1, columnspan=2, sticky=tk.W)
+ self.fetchLink = ttk.Entry(self, textvariable=self.link)
+ self.fetchLink.grid(row=1, column=3, sticky=tk.W+tk.E)
+
+ (ttk.Label(self, text='Pobierz do ')).grid(
+ row=2, column=0, columnspan=2, sticky=tk.W)
+ (NumericSpinbox(
+ self, from_=0, to=9999, width=5, justify=tk.RIGHT,
+ textvariable=self.fetchLimit)).grid(
+ row=2, column=2, sticky=tk.W)
+ (ttk.Label(self, text=' miejsca (0 = wszystkie)')).grid(
+ row=2, column=3, sticky=tk.W+tk.E)
+
+ (ttk.Label(self, text='Pozycje końcowe: ')).grid(
+ row=3, column=0, columnspan=3, sticky=tk.W+tk.E)
+ self.finishingPositionsBtn = TeamSelectionButton(
+ self, callback=self._setFinishingPositions,
+ prompt='Wybierz teamy, które zakończyły rozgrywki ' + \
+ 'na swojej pozycji:',
+ dialogclass=TeamSelectionFrame)
+ self.finishingPositionsBtn.grid(row=3, column=3, sticky=tk.W)
+ self.finishingPositionsBtn.setPositions([])
+
+ def setValues(self, values):
+ if 'database' in values:
+ self.fetchSource.set(self.SOURCE_DB)
+ self.fetchDB.set(values['database'])
+ self.link.set('')
+ else:
+ self.fetchSource.set(self.SOURCE_LINK)
+ self.fetchDB.set('')
+ self.link.set(values['link'] if 'link' in values else '')
+ self.fetchLimit.set(
+ values['max_teams'] if 'max_teams' in values else 0)
+ self.finishingPositionsBtn.setPositions(
+ values['final_positions'] if 'final_positions' in values else [])
+
+class TeamSettingsFrame(ScrollableFrame):
+ FORMAT_FETCH = 0
+ FORMAT_MANUAL = 1
+
+ def _enablePanels(self, *args):
+ panels = {self.FORMAT_FETCH: self.fetchSettingsFrame,
+ self.FORMAT_MANUAL: self.manualSettingsFrame}
+ for value, panel in panels.iteritems():
+ setPanelState(
+ frame=panel,
+ state=tk.NORMAL \
+ if self.teamFormat.get()==value else tk.DISABLED)
+
+ def _changeNotify(self, *args):
+ self.winfo_toplevel().event_generate(
+ '<<TeamSettingsChanged>>', when='tail')
+
+ def setTeams(self, event):
+ self.teams = self.winfo_toplevel().getTeams()
+
+ def renderContent(self, container):
+ self.teamFormat = NotifyIntVar()
+ self.teamFormat.trace('w', self._enablePanels)
+ self.teamFormat.trace('w', self._changeNotify)
+
+ container.columnconfigure(0, weight=1)
+
+ (ttk.Radiobutton(
+ container, text='Pobierz z JFR Teamy:',
+ variable=self.teamFormat, value=self.FORMAT_FETCH)).grid(
+ row=0, column=0, sticky=tk.W)
+
+ self.fetchSettingsFrame = TeamFetchSettingsFrame(container)
+ self.fetchSettingsFrame.grid(row=1, column=0, sticky=tk.W+tk.E)
+
+ (ttk.Separator(
+ container, orient=tk.HORIZONTAL)).grid(
+ row=2, column=0, sticky=tk.W+tk.E)
+
+ (ttk.Radiobutton(
+ container, text='Ustaw ręcznie:',
+ variable=self.teamFormat, value=self.FORMAT_MANUAL)).grid(
+ row=3, column=0, sticky=tk.W+tk.E)
+
+ self.manualSettingsFrame = TeamManualSettingsFrame(container)
+ self.manualSettingsFrame.grid(row=4, column=0, sticky=tk.W+tk.E)
+
+ self.teams = []
+ self.winfo_toplevel().bind(
+ '<<TeamListChanged>>', self.setTeams, add='+')
+
+ def getConfig(self):
+ if self.teamFormat.get() == self.FORMAT_MANUAL:
+ return self.manualSettingsFrame.getTeams()
+ elif self.teamFormat.get() == self.FORMAT_FETCH:
+ return self.fetchSettingsFrame.getTeams()
+ return []
+
+ def setValues(self, values):
+ if isinstance(values, list):
+ self.teamFormat.set(self.FORMAT_MANUAL)
+ self.manualSettingsFrame.setValues(values)
+ self.fetchSettingsFrame.setValues({})
+ else:
+ self.teamFormat.set(self.FORMAT_FETCH)
+ self.manualSettingsFrame.setValues([])
+ self.fetchSettingsFrame.setValues(values)
+
+class TeamList(RefreshableOptionMenu):
+ def __init__(self, *args, **kwargs):
+ RefreshableOptionMenu.__init__(self, *args, **kwargs)
+ self.winfo_toplevel().bind(
+ '<<TeamListChanged>>', self.refreshOptions, add='+')
+ self.configure(width=10)
+
+ def getLabel(self, team):
+ return team[0]
+
+ def getValues(self):
+ return self.winfo_toplevel().getTeams()
+
+
+class TeamAliasRow(RepeatableFrame):
+ def renderContent(self):
+ self.columnconfigure(0, weight=0)
+ self.columnconfigure(1, weight=1)
+ self.teamName = NotifyStringVar()
+ list = TeamList(self, self.teamName, self.teamName.get())
+ list.configure(width=20)
+ list.grid(
+ row=0, column=0, sticky=tk.W+tk.E+tk.N)
+ self.names = WidgetRepeater(self, RepeatableEntry)
+ self.names.grid(row=0, column=1, sticky=tk.W+tk.E)
+
+ def getValue(self):
+ return (
+ self.teamName.get().strip(),
+ [val.strip() for val in self.names.getValue()])
+
+ def setValue(self, value):
+ self.teamName.set(value[0])
+ self.names.setValue(value[1])
+
+
+class TeamAliasFrame(ScrollableFrame):
+ def renderContent(self, container):
+ container.columnconfigure(0, weight=1)
+ (ttk.Label(container, text='Aliasy teamów')).grid(
+ row=0, column=0, sticky=tk.W+tk.E)
+ self.repeater = WidgetRepeater(container, TeamAliasRow)
+ self.repeater.grid(row=1, column=0, sticky=tk.W+tk.E)
+
+ def getConfig(self):
+ return OrderedDict(
+ {val[0]: val[1] for val in self.repeater.getValue() if val[0]})
+
+ def setValues(self, values):
+ self.repeater.setValue(list(values.iteritems()))
+
+class TeamPreviewFrame(ScrollableFrame):
+ def __init__(self, *args, **kwags):
+ self.tieValues = []
+ self.tieFields = []
+ self.orderValues = []
+ self.orderFields = []
+ self.labels = []
+ ScrollableFrame.__init__(self, *args, **kwags)
+ self.winfo_toplevel().bind(
+ '<<TeamListChanged>>', self.refreshTeams, add='+')
+ self.winfo_toplevel().bind(
+ '<<TieConfigChanged>>', self._collectTieConfig, add='+')
+ self._tieConfig = []
+ self._lockTieValues = False
+ self.winfo_toplevel().bind(
+ '<<OrderConfigChanged>>', self._collectOrderConfig, add='+')
+ self._orderConfig = []
+ self._lockOrderValues = False
+
+ def setTeams(self, container, teams):
+ self.teamList.grid(
+ row=1, column=0, rowspan=len(teams)+2, sticky=tk.W+tk.E+tk.N+tk.S)
+ self.tieValues = self.tieValues[0:len(teams)]
+ for idx in range(len(teams), len(self.tieFields)):
+ self.tieFields[idx].destroy()
+ self.tieFields = self.tieFields[0:len(teams)]
+ self.orderValues = self.orderValues[0:len(teams)]
+ for idx in range(len(teams), len(self.orderFields)):
+ self.orderFields[idx].destroy()
+ self.orderFields = self.orderFields[0:len(teams)]
+ for label in self.labels:
+ label.destroy()
+ self.teamList.delete(*self.teamList.get_children())
+ for idx, team in enumerate(teams):
+ if len(team) > 2 and team[2] is None:
+ team[2] = ''
+ self.teamList.insert('', tk.END, values=team, tag=idx)
+ if idx >= len(self.tieFields):
+ self.tieValues.append(NotifyNumericVar())
+ self.tieValues[idx].trace('w', self._tieValueChangeNotify)
+ self.tieFields.append(
+ NumericSpinbox(
+ container, from_=0, to=9999,
+ width=5, font=Font(size=10),
+ textvariable=self.tieValues[idx]))
+ self.tieFields[idx].grid(
+ row=idx+2, column=1, sticky=tk.W+tk.E+tk.N)
+ container.rowconfigure(idx+2, weight=0)
+ if idx >= len (self.orderFields):
+ self.orderValues.append(NotifyNumericVar())
+ self.orderValues[idx].trace('w', self._orderValueChangeNotify)
+ self.orderFields.append(
+ NumericSpinbox(
+ container, from_=0, to=9999,
+ width=5, font=Font(size=10),
+ textvariable=self.orderValues[idx]))
+ self.orderFields[idx].grid(
+ row=idx+2, column=2, sticky=tk.W+tk.E+tk.N)
+ container.rowconfigure(idx+2, weight=0)
+ self.labels.append(ttk.Label(container, text=' '))
+ self.labels[-1].grid(row=1, column=1, pady=3)
+ self.labels.append(ttk.Label(container, text=' '))
+ self.labels[-1].grid(row=len(teams)+2, column=1)
+ container.rowconfigure(1, weight=0)
+ container.rowconfigure(len(teams)+2, weight=1)
+ self.labels.append(ttk.Label(
+ container,
+ text='Kolejność rozstrzygania remisów w klasyfikacji ' + \
+ 'pobranej z bazy JFR Teamy',
+ anchor=tk.E))
+ self.labels[-1].grid(row=len(teams)+3, column=0, sticky=tk.N+tk.E)
+ self.labels.append(ttk.Label(container, text='⬏', font=Font(size=20)))
+ self.labels[-1].grid(
+ row=len(teams)+3, column=1, sticky=tk.W+tk.N)
+ container.rowconfigure(len(teams)+3, weight=1)
+ self.labels.append(ttk.Label(
+ container,
+ text='Ręczne rozstrzyganie kolejności w klasyfikacji końcowej',
+ anchor=tk.E))
+ self.labels[-1].grid(row=len(teams)+4, column=0, columnspan=2, sticky=tk.N+tk.E)
+ self.labels.append(ttk.Label(container, text='⬏', font=Font(size=20)))
+ self.labels[-1].grid(
+ row=len(teams)+4, column=2, sticky=tk.W+tk.N)
+ container.rowconfigure(len(teams)+3, weight=1)
+
+ def renderContent(self, container):
+ container.columnconfigure(0, weight=1)
+ (ttk.Label(container, text='Podgląd listy teamów')).grid(
+ row=0, column=0, columnspan=2, sticky=tk.W+tk.E)
+ self.teamList = ttk.Treeview(
+ container, show='headings',
+ columns=['fullname','shortname','icon','position'],
+ selectmode='browse')
+ for col, heading in enumerate(
+ [('Nazwa', 100), ('Skrócona nazwa', 100),
+ ('Ikona', 20), ('Poz. końc.', 20)]):
+ self.teamList.heading(col, text=heading[0])
+ if heading[1]:
+ self.teamList.column(col, width=heading[1], stretch=True)
+ self.container = container
+
+ def _getTeams(self):
+ return self.winfo_toplevel().getTeams()
+
+ def getTieConfig(self):
+ teams = self._getTeams()
+ ties = [(teams[idx], val.get(default=0))
+ for idx, val in enumerate(self.tieValues)]
+ return [team[0][0] for team
+ in sorted(ties, key=lambda t: t[1])
+ if team[1] > 0]
+
+ def setTieConfig(self, values):
+ self._tieConfig = values
+ self.refreshTeams(None)
+
+ def _tieValueChangeNotify(self, *args):
+ if not self._lockTieValues:
+ self.winfo_toplevel().event_generate(
+ '<<TieConfigChanged>>', when='tail')
+
+ def _collectTieConfig(self, *args):
+ if not self._lockTieValues:
+ self._tieConfig = self.getTieConfig()
+
+ def getOrderConfig(self):
+ teams = self._getTeams()
+ order = [(teams[idx], val.get(default=0))
+ for idx, val in enumerate(self.orderValues)]
+ return [team[0][0] for team
+ in sorted(order, key=lambda t: t[1])
+ if team[1] > 0]
+
+ def setOrderConfig(self, values):
+ self._orderConfig = values
+ self.refreshTeams(None)
+
+ def _orderValueChangeNotify(self, *args):
+ if not self._lockOrderValues:
+ self.winfo_toplevel().event_generate(
+ '<<OrderConfigChanged>>', when='tail')
+
+ def _collectOrderConfig(self, *args):
+ if not self._lockOrderValues:
+ self._orderConfig = self.getOrderConfig()
+
+ def refreshTeams(self, event):
+ self._lockTieValues = True
+ self._lockOrderValues = True
+ teams = self._getTeams()
+ self.setTeams(self.container, teams)
+ for tidx, team in enumerate(teams):
+ self.tieValues[tidx].set(0)
+ for idx, tie in enumerate(self._tieConfig):
+ if team[0] == tie:
+ self.tieValues[tidx].set(idx+1)
+ break
+ for idx, order in enumerate(self._orderConfig):
+ if isinstance(order, int):
+ if tidx+1 == order:
+ self.orderValues[tidx].set(idx+1)
+ break
+ else:
+ if team[0] == order:
+ self.orderValues[tidx].set(idx+1)
+ break
+ self._lockOrderValues = False
+ self._lockTieValues = False
+
+
+__all__ = ['TeamSettingsFrame', 'TeamAliasFrame', 'TeamPreviewFrame']
diff --git a/jfr_playoff/gui/frames/translations.py b/jfr_playoff/gui/frames/translations.py
new file mode 100644
index 0000000..a369159
--- /dev/null
+++ b/jfr_playoff/gui/frames/translations.py
@@ -0,0 +1,49 @@
+#coding=utf-8
+
+import copy
+from collections import OrderedDict
+
+import tkinter as tk
+from tkinter import ttk
+
+from ..frames import RepeatableFrame, WidgetRepeater, ScrollableFrame
+from ...i18n import PLAYOFF_I18N_DEFAULTS
+from ..variables import NotifyStringVar
+
+class TranslationRow(RepeatableFrame):
+ def renderContent(self):
+ self.key = NotifyStringVar()
+ self.value = NotifyStringVar()
+
+ (ttk.Entry(self, textvariable=self.key, width=40)).pack(
+ side=tk.LEFT, fill=tk.BOTH, expand=True)
+ (ttk.Entry(self, textvariable=self.value, width=80)).pack(
+ side=tk.RIGHT, fill=tk.BOTH, expand=True)
+
+ def setValue(self, value):
+ self.key.set(value[0])
+ self.value.set(value[1])
+
+ def getValue(self):
+ return (self.key.get(), self.value.get())
+
+class TranslationConfigurationFrame(ScrollableFrame):
+
+ def setTranslations(self, translations):
+ default_translations = copy.copy(PLAYOFF_I18N_DEFAULTS)
+ default_translations.update(translations)
+ values = []
+ for value in default_translations.iteritems():
+ values.append(value)
+ self.repeater.setValue(values)
+
+ def getTranslations(self):
+ return OrderedDict({
+ key: value for key, value in self.repeater.getValue()
+ })
+
+ def renderContent(self, container):
+ self.repeater = WidgetRepeater(container, TranslationRow)
+ self.repeater.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+__all__ = ['TranslationConfigurationFrame']
diff --git a/jfr_playoff/gui/frames/visual.py b/jfr_playoff/gui/frames/visual.py
new file mode 100644
index 0000000..1cbe177
--- /dev/null
+++ b/jfr_playoff/gui/frames/visual.py
@@ -0,0 +1,452 @@
+#coding=utf-8
+
+from collections import OrderedDict
+
+import tkinter as tk
+from tkinter import ttk
+import tkColorChooser as tkcc
+
+from ..frames import GuiFrame, RepeatableFrame, ScrollableFrame
+from ..frames import WidgetRepeater
+from ..frames import SelectionFrame, RefreshableOptionMenu, NumericSpinbox
+from ..frames.team import TeamSelectionButton
+from ..variables import NotifyStringVar, NotifyNumericVar, NotifyBoolVar
+
+class VisualSettingsFrame(GuiFrame):
+ DEFAULT_VALUES = {
+ 'width': 250,
+ 'height': 80,
+ 'margin': 60,
+ 'starting_position_indicators': 0,
+ 'finishing_position_indicators': 0,
+ 'team_boxes': {
+ 'label_length_limit': 25,
+ 'predict_teams': 1,
+ 'label_separator': ' / ',
+ 'label_placeholder': '??',
+ 'label_ellipsis': '(...)',
+ 'name_separator': '<br />',
+ 'name_prefix': '&nbsp;&nbsp;',
+ 'sort_eligible_first': 1
+ }
+ }
+
+ def renderContent(self):
+ self.startingPositionIndicators = NotifyBoolVar()
+ self.finishingPositionIndicators = NotifyBoolVar()
+ self.boxWidth = NotifyNumericVar()
+ self.boxHeight = NotifyNumericVar()
+ self.boxMargin = NotifyNumericVar()
+ self.shortenTeamNames = NotifyBoolVar()
+ self.teamNameLength = NotifyNumericVar()
+ self.teamNameEllipsis = NotifyStringVar()
+ self.teamNamePredict = NotifyBoolVar()
+ self.teamNamePlaceholder = NotifyStringVar()
+ self.teamNameSortPredictions = NotifyBoolVar()
+ self.teamLabelSeparator = NotifyStringVar()
+ self.teamNameSeparator = NotifyStringVar()
+ self.teamNamePrefix = NotifyStringVar()
+
+ indicatorsFrame = ttk.LabelFrame(self, text='Znaczniki pozycji:')
+ indicatorsFrame.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S)
+ dimensionsFrame = ttk.LabelFrame(self, text='Wymiary tabelki meczu:')
+ dimensionsFrame.grid(row=0, column=1, sticky=tk.W+tk.E+tk.N+tk.S)
+ teamNamesFrame = ttk.LabelFrame(self, text='Nazwy teamów:')
+ teamNamesFrame.grid(row=1, column=0, sticky=tk.W+tk.E+tk.N+tk.S)
+ separatorsFrame = ttk.LabelFrame(self, text='Separatory nazw teamów:')
+ separatorsFrame.grid(row=1, column=1, sticky=tk.W+tk.E+tk.N+tk.S)
+
+ self._fieldsToEnable = []
+
+ (ttk.Checkbutton(
+ indicatorsFrame, text='początkowych',
+ variable=self.startingPositionIndicators)).grid(
+ row=0, column=0, sticky=tk.W)
+ (ttk.Checkbutton(
+ indicatorsFrame, text='końcowych',
+ variable=self.finishingPositionIndicators)).grid(
+ row=1, column=0, sticky=tk.W)
+
+ (NumericSpinbox(
+ dimensionsFrame, width=5, from_=1, to=999,
+ textvariable=self.boxWidth)).grid(
+ row=0, column=0, sticky=tk.W)
+ (ttk.Label(dimensionsFrame, text='x')).grid(row=0, column=1)
+ (NumericSpinbox(
+ dimensionsFrame, width=5, from_=1, to=999,
+ textvariable=self.boxHeight)).grid(
+ row=0, column=2, sticky=tk.W)
+ (ttk.Label(dimensionsFrame, text='odstępy')).grid(
+ row=1, column=0, columnspan=2, sticky=tk.E)
+ (NumericSpinbox(
+ dimensionsFrame, width=5, from_=1, to=999,
+ textvariable=self.boxMargin)).grid(
+ row=1, column=2, sticky=tk.W)
+
+ (ttk.Checkbutton(
+ teamNamesFrame, text='skracaj do',
+ variable=self.shortenTeamNames)).grid(
+ row=0, column=0, columnspan=2)
+ nameLength = NumericSpinbox(
+ teamNamesFrame, width=5, from_=1, to=999,
+ textvariable=self.teamNameLength)
+ nameLength.grid(row=0, column=2, sticky=tk.W)
+ lengthLabel = ttk.Label(teamNamesFrame, text='znaków')
+ lengthLabel.grid(row=0, column=3, sticky=tk.W)
+ ellipsisLabel = ttk.Label(teamNamesFrame, text='znacznik:')
+ ellipsisLabel.grid(row=1, column=0, columnspan=2, sticky=tk.E)
+ nameEllipsis = ttk.Entry(
+ teamNamesFrame, width=5,
+ textvariable=self.teamNameEllipsis)
+ nameEllipsis.grid(row=1, column=2, sticky=tk.W)
+ (ttk.Checkbutton(
+ teamNamesFrame,
+ text='przewiduj na podstawie trwających meczów',
+ variable=self.teamNamePredict)).grid(
+ row=2, column=0, columnspan=5)
+ placeholderLabel = ttk.Label(
+ teamNamesFrame, text='etykieta nieznanych teamów')
+ placeholderLabel.grid(row=3, column=1, columnspan=3, sticky=tk.W)
+ namePlaceholder = ttk.Entry(
+ teamNamesFrame, width=5,
+ textvariable=self.teamNamePlaceholder)
+ namePlaceholder.grid(row=3, column=4, sticky=tk.W)
+ predictSort = ttk.Checkbutton(
+ teamNamesFrame, text='wyświetlaj najpierw pewne teamy',
+ variable=self.teamNameSortPredictions)
+ predictSort.grid(row=4, column=1, columnspan=4, sticky=tk.W)
+ self._fieldsToEnable.append(
+ (self.shortenTeamNames,
+ [nameLength, nameEllipsis, lengthLabel, ellipsisLabel]))
+ self._fieldsToEnable.append(
+ (self.teamNamePredict,
+ [namePlaceholder, placeholderLabel, predictSort]))
+
+ (ttk.Label(separatorsFrame, text=' ')).grid(row=0, column=0)
+ (ttk.Label(separatorsFrame, text='w drabince (skrócone nazwy)')).grid(
+ row=0, column=1, sticky=tk.E)
+ (ttk.Entry(
+ separatorsFrame, width=8,
+ textvariable=self.teamLabelSeparator)).grid(
+ row=0, column=2, sticky=tk.W)
+ (ttk.Label(separatorsFrame, text='w "dymkach" (pełne nazwy)')).grid(
+ row=1, column=1, sticky=tk.E)
+ (ttk.Entry(
+ separatorsFrame, width=8,
+ textvariable=self.teamNameSeparator)).grid(
+ row=1, column=2, sticky=tk.W)
+ (ttk.Label(separatorsFrame, text='prefiks pełnych nazw')).grid(
+ row=2, column=1, sticky=tk.E)
+ (ttk.Entry(
+ separatorsFrame, width=8,
+ textvariable=self.teamNamePrefix)).grid(
+ row=2, column=2, sticky=tk.W)
+
+ for var, fields in self._fieldsToEnable:
+ var.trace('w', self._enableFields)
+ self._enableFields()
+
+ self.setValues({})
+
+ def _enableFields(self, *args):
+ for var, fields in self._fieldsToEnable:
+ for field in fields:
+ field.configure(state=tk.NORMAL if var.get() else tk.DISABLED)
+
+ def setValues(self, values):
+ default_values = self.DEFAULT_VALUES
+ if 'team_boxes' in values:
+ default_values['team_boxes'].update(values['team_boxes'])
+ del values['team_boxes']
+ default_values.update(values)
+ values = default_values
+
+ self.startingPositionIndicators.set(
+ values['starting_position_indicators'])
+ self.finishingPositionIndicators.set(
+ values['finishing_position_indicators'])
+ self.boxWidth.set(values['width'])
+ self.boxHeight.set(values['height'])
+ self.boxMargin.set(values['margin'])
+ self.shortenTeamNames.set(
+ values['team_boxes']['label_length_limit'] > 0)
+ self.teamNameLength.set(values['team_boxes']['label_length_limit'])
+ self.teamNameEllipsis.set(values['team_boxes']['label_ellipsis'])
+ self.teamNamePredict.set(values['team_boxes']['predict_teams'])
+ self.teamNamePlaceholder.set(values['team_boxes']['label_placeholder'])
+ self.teamNameSortPredictions.set(
+ values['team_boxes']['sort_eligible_first'])
+ self.teamLabelSeparator.set(values['team_boxes']['label_separator'])
+ self.teamNameSeparator.set(values['team_boxes']['name_separator'])
+ self.teamNamePrefix.set(values['team_boxes']['name_prefix'])
+
+ def getValues(self):
+ return OrderedDict(
+ {
+ 'width': self.boxWidth.get(default=250),
+ 'height': self.boxHeight.get(default=80),
+ 'margin': self.boxMargin.get(default=60),
+ 'starting_position_indicators': self.startingPositionIndicators.get(),
+ 'finishing_position_indicators': self.finishingPositionIndicators.get(),
+ 'team_boxes': {
+ 'label_length_limit': self.teamNameLength.get(default=25) if self.shortenTeamNames else 0,
+ 'predict_teams': self.teamNamePredict.get(),
+ 'label_separator': self.teamLabelSeparator.get(),
+ 'label_placeholder': self.teamNamePlaceholder.get(),
+ 'label_ellipsis': self.teamNameEllipsis.get(),
+ 'name_separator': self.teamNameSeparator.get(),
+ 'name_prefix': self.teamNamePrefix.get(),
+ 'sort_eligible_first': self.teamNameSortPredictions.get()
+ }
+ })
+
+
+class MatchList(RefreshableOptionMenu):
+ def __init__(self, *args, **kwargs):
+ RefreshableOptionMenu.__init__(self, *args, **kwargs)
+ self.winfo_toplevel().bind(
+ '<<MatchListChanged>>', self.refreshOptions, add='+')
+ self.configure(width=10)
+
+ def getLabel(self, match):
+ return match.label
+
+ def getValues(self):
+ try:
+ return self.winfo_toplevel().getMatches()
+ except tk.TclError:
+ # we're probably being destroyed, ignore
+ return []
+
+ def getVarValue(self, match):
+ return unicode(match.getMatchID())
+
+
+class BoxPositionFrame(RepeatableFrame):
+ def renderContent(self):
+ self.match = NotifyStringVar()
+ self.vertical = NotifyNumericVar()
+ self.horizontal = NotifyNumericVar()
+ self.matchBox = MatchList(self, self.match)
+ self.matchBox.configure(width=20)
+ self.matchBox.grid(row=0, column=0)
+
+ (ttk.Label(self, text=' w pionie:')).grid(row=0, column=1)
+ (NumericSpinbox(
+ self, textvariable=self.vertical, from_=0, to=9999,
+ width=5)).grid(
+ row=0, column=2)
+ (ttk.Label(self, text=' w poziomie (-1 = automatyczna):')).grid(
+ row=0, column=3)
+ (NumericSpinbox(
+ self, textvariable=self.horizontal, from_=-1, to=9999,
+ width=5)).grid(
+ row=0, column=4)
+ self.setValue([])
+
+ def setValue(self, value):
+ if len(value) > 1:
+ self.match.set(value[0])
+ self.vertical.set(value[1])
+ if len(value) > 2:
+ self.horizontal.set(value[2])
+ else:
+ self.horizontal.set(-1)
+ else:
+ self.match.set('')
+ self.vertical.set(0)
+ self.horizontal.set(-1)
+
+ def getValue(self):
+ horizontal = self.horizontal.get(default=-1)
+ vertical = self.vertical.get(default=0)
+ return (
+ self.match.get(),
+ [vertical, horizontal] if horizontal >= 0 else vertical)
+
+class BoxPositionsFrame(ScrollableFrame):
+ def renderContent(self, container):
+ (ttk.Label(container, text='Pozycje tabelek meczów:')).pack(
+ side=tk.TOP, anchor=tk.W)
+ self.positions = WidgetRepeater(container, BoxPositionFrame)
+ self.positions.pack(
+ side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ def setValues(self, values):
+ values = sorted(list(values.iteritems()), key=lambda v: int(v[0]))
+ values_to_set = []
+ for idx, val in enumerate(values):
+ value = [val[0]]
+ if isinstance(val[1], list):
+ value += val[1]
+ else:
+ value.append(val[1])
+ values_to_set.append(value)
+ self.positions.setValue(values_to_set)
+
+ def getValues(self):
+ return OrderedDict(
+ { match: config for match, config in self.positions.getValue() })
+
+class LineStyle(GuiFrame):
+ def _selectColour(self):
+ colour = tkcc.askcolor(self._getColour())
+ if colour is not None:
+ self._setColour(colour[1])
+
+ def _getColour(self):
+ return self.colourBtn.cget('bg')
+
+ def _setColour(self, colour):
+ self.colourBtn.configure(bg=colour)
+
+ def renderContent(self):
+ self.hOffset = NotifyNumericVar()
+ self.vOffset = NotifyNumericVar()
+
+ (ttk.Label(self, text='kolor:')).grid(row=0, column=0)
+ self.colourBtn = tk.Button(self, width=2, command=self._selectColour)
+ self.colourBtn.grid(row=0, column=1)
+ (ttk.Label(self, text='margines w poziomie:')).grid(row=0, column=2)
+ (NumericSpinbox(
+ self, textvariable=self.hOffset, from_=-50, to=50,
+ width=5)).grid(row=0, column=3)
+ (ttk.Label(self, text='margines w pionie:')).grid(row=0, column=4)
+ (NumericSpinbox(
+ self, textvariable=self.vOffset, from_=-50, to=50,
+ width=5)).grid(row=0, column=5)
+
+ def setValue(self, value):
+ self._setColour(value[0])
+ self.hOffset.set(value[1])
+ self.vOffset.set(value[2])
+
+ def getValue(self):
+ return [self._getColour(), self.hOffset.get(), self.vOffset.get()]
+
+class LineStylesFrame(GuiFrame):
+ DEFAULT_VALUES = [
+ ('winner', ('#00ff00', 5, -10),
+ 'Zwycięzcy meczów: '),
+ ('loser', ('#ff0000', 20, 10),
+ 'Przegrani meczów: '),
+ ('place_winner', ('#00dddd', 10, 2),
+ 'Pozycje startowe (wybierający): '),
+ ('place_loser', ('#dddd00', 15, 9),
+ 'Pozycje startowe (wybierani): '),
+ ('finish_winner', ('#00ff00', 5, -10),
+ 'Zwycięzcy meczów kończący rozgrywki: '),
+ ('finish_loser', ('#ff0000', 20, 10),
+ 'Przegrani meczów kończący rozgrywki: ')
+ ]
+ CONFIG_KEYS = ['colour', 'h_offset', 'v_offset']
+
+ def renderContent(self):
+ self.lines = OrderedDict()
+ for idx, line in enumerate(self.DEFAULT_VALUES):
+ self.lines[line[0]] = LineStyle(self)
+ self.lines[line[0]].grid(row=idx+1, column=1, sticky=tk.W)
+ (ttk.Label(self, text=line[2])).grid(
+ row=idx+1, column=0, sticky=tk.E)
+ (ttk.Label(self, text='Kolory linii')).grid(
+ row=0, column=0, columnspan=2, sticky=tk.W)
+
+ def setValues(self, values):
+ for line in self.DEFAULT_VALUES:
+ value = list(line[1])
+ for idx, key in enumerate(self.CONFIG_KEYS):
+ key = '%s_%s' % (line[0], key)
+ if key in values:
+ value[idx] = values[key]
+ self.lines[line[0]].setValue(value)
+
+ def getValues(self):
+ config = OrderedDict()
+ for line, widget in self.lines.iteritems():
+ value = widget.getValue()
+ for idx, key in enumerate(self.CONFIG_KEYS):
+ config['%s_%s' % (line, key)] = value[idx]
+ return config
+
+
+class PositionsSelectionFrame(SelectionFrame):
+ COLUMN_COUNT=10
+
+ def __init__(self, *args, **kwargs):
+ SelectionFrame.__init__(self, *args, **kwargs)
+ self.winfo_toplevel().geometry(
+ '%dx%d' % (
+ self.COLUMN_COUNT * 40,
+ (len(self.options) / self.COLUMN_COUNT + 2) * 25 + 30
+ ))
+
+ def renderHeader(self, container):
+ (ttk.Label(container, text=self.title)).grid(
+ row=0, column=0, columnspan=self.COLUMN_COUNT, sticky=tk.W)
+
+ def renderOption(self, container, option, idx):
+ (ttk.Checkbutton(
+ container, text=str(self._mapValue(idx, option)),
+ variable=self.values[self._mapValue(idx, option)]
+ )).grid(
+ row=(idx/self.COLUMN_COUNT)+1, column=idx%self.COLUMN_COUNT,
+ sticky=tk.W)
+
+class PositionStyleFrame(RepeatableFrame):
+ def _setPositions(self, values):
+ self.positions = values
+
+ def renderContent(self):
+ self.name = NotifyStringVar()
+ self.description = NotifyStringVar()
+
+ self.columnconfigure(1, weight=1)
+ self.columnconfigure(5, weight=1)
+
+ (ttk.Label(self, text='Styl:')).grid(row=0, column=0)
+ (ttk.Entry(self, textvariable=self.name)).grid(
+ row=0, column=1, sticky=tk.W+tk.E)
+
+ (ttk.Label(self, text='Pozycje końcowe:')).grid(row=0, column=2)
+ self.positionBtn = TeamSelectionButton(
+ self, prompt='Wybierz pozycje końcowe:',
+ dialogclass=PositionsSelectionFrame,
+ callback=self._setPositions)
+ self.positionBtn.grid(row=0, column=3)
+
+ (ttk.Label(self, text='Opis w legendzie:')).grid(row=0, column=4)
+ (ttk.Entry(self, textvariable=self.description)).grid(
+ row=0, column=5, sticky=tk.W+tk.E)
+
+ self.setValue({})
+
+ def setValue(self, value):
+ self.name.set(value['class'] if 'class' in value else '')
+ self.positionBtn.setPositions(
+ value['positions'] if 'positions' in value else [])
+ self.description.set(value['caption'] if 'caption' in value else '')
+
+ def getValue(self):
+ config = OrderedDict({
+ 'class': self.name.get(),
+ 'positions': self.positions
+ })
+ caption = self.description.get()
+ if caption:
+ config['caption'] = caption
+ return config
+
+class PositionStylesFrame(ScrollableFrame):
+ def renderContent(self, container):
+ (ttk.Label(container, text='Klasyfikacja końcowa')).pack(
+ side=tk.TOP, anchor=tk.W)
+ self.styles = WidgetRepeater(container, PositionStyleFrame)
+ self.styles.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
+
+ def setValues(self, values):
+ self.styles.setValue(values)
+
+ def getValues(self):
+ return self.styles.getValue()
+
+__all__ = ['VisualSettingsFrame', 'BoxPositionsFrame', 'LineStylesFrame', 'PositionStylesFrame']