diff options
Diffstat (limited to 'jfr_playoff/gui/frames/visual.py')
-rw-r--r-- | jfr_playoff/gui/frames/visual.py | 452 |
1 files changed, 452 insertions, 0 deletions
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': ' ', + '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'] |