diff options
Diffstat (limited to 'jfr_playoff/gui/tabs.py')
-rw-r--r-- | jfr_playoff/gui/tabs.py | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/jfr_playoff/gui/tabs.py b/jfr_playoff/gui/tabs.py new file mode 100644 index 0000000..2a8c889 --- /dev/null +++ b/jfr_playoff/gui/tabs.py @@ -0,0 +1,506 @@ +#coding=utf-8 + +import os +from collections import OrderedDict + +import tkinter as tk +from tkinter import ttk +import tkFileDialog as tkfd +import tkMessageBox as tkmb + +from .frames import TraceableText, NumericSpinbox +from .frames.match import * +from .frames.network import * +from .frames.team import * +from .frames.translations import * +from .frames.visual import * +from .variables import NotifyStringVar, NotifyNumericVar, NotifyBoolVar + +from ..data import PlayoffData +from ..db import PlayoffDB + +class PlayoffTab(ttk.Frame): + def __init__(self, master): + ttk.Frame.__init__(self, master) + self.frame = ttk.Frame(self) + self.frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + self.initData() + self.renderContent(self.frame) + + @property + def title(self): + pass + + def initData(self): + pass + + def renderContent(self, container): + pass + + def setValues(self, config): + pass + + def getConfig(self): + pass + +class MainSettingsTab(PlayoffTab): + DEFAULT_INTERVAL = 60 + + @property + def title(self): + return 'Główne ustawienia' + + def initData(self): + self.outputPath = NotifyStringVar() + self.pageTitle = NotifyStringVar() + self.pageLogoh = NotifyStringVar() + self.refresh = NotifyBoolVar() + self.refresh.trace('w', self._updateRefreshFields) + self.refreshInterval = NotifyNumericVar() + + def _chooseOutputPath(self): + currentPath = self.outputPath.get() + filename = tkfd.asksaveasfilename( + initialdir=os.path.dirname(currentPath) if currentPath else '.', + title='Wybierz plik wyjściowy', + filetypes=(('HTML files', '*.html'),)) + if filename: + if not filename.lower().endswith('.html'): + filename = filename + '.html' + self.outputPath.set(filename) + + def _updateRefreshFields(self, *args): + self.intervalField.configure( + state=tk.NORMAL if self.refresh.get() else tk.DISABLED) + + def setValues(self, config): + self.outputPath.set(config['output'] if 'output' in config else '') + if 'page' in config: + self.pageTitle.set( + config['page']['title'] if 'title' in config['page'] else '') + self.pageLogoh.set( + config['page']['logoh'] if 'logoh' in config['page'] else '') + try: + interval = int(config['page']['refresh']) + if interval > 0: + self.refresh.set(1) + self.refreshInterval.set(interval) + else: + self.refresh.set(0) + self.refreshInterval.set(self.DEFAULT_INTERVAL) + except: + self.refresh.set(0) + self.refreshInterval.set(self.DEFAULT_INTERVAL) + else: + self.pageTitle.set('') + self.pageLogoh.set('') + self.refresh.set(0) + self.refreshInterval.set(self.DEFAULT_INTERVAL) + + def renderContent(self, container): + (ttk.Label(container, text='Plik wynikowy:')).grid( + row=0, column=0, sticky=tk.E, pady=2) + outputPath = tk.Frame(container) + outputPath.grid(row=0, column=1, sticky=tk.E+tk.W, pady=2) + (ttk.Entry(outputPath, width=60, textvariable=self.outputPath)).grid( + row=0, column=0, sticky=tk.W+tk.E) + (ttk.Button( + outputPath, + text='wybierz...', command=self._chooseOutputPath)).grid( + row=0, column=1) + outputPath.columnconfigure(0, weight=1) + + (ttk.Separator(container, orient=tk.HORIZONTAL)).grid( + row=1, column=0, columnspan=2, sticky=tk.E+tk.W, pady=2) + + pageSettings = ttk.LabelFrame( + container, text='Ustawienia strony') + pageSettings.grid( + row=2, column=0, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S, pady=5) + + pageSettings.columnconfigure(1, weight=1) + + (ttk.Label(pageSettings, text='Tytuł:')).grid( + row=0, column=0, sticky=tk.E, pady=2) + (tk.Entry(pageSettings, textvariable=self.pageTitle)).grid( + row=0, column=1, sticky=tk.W+tk.E, pady=2) + (ttk.Label(pageSettings, text='Logoh:')).grid( + row=1, column=0, sticky=tk.E+tk.N, pady=2) + (TraceableText(pageSettings, width=45, height=10, + variable=self.pageLogoh)).grid( + row=1, column=1, + sticky=tk.W+tk.N+tk.E+tk.S, pady=2) + + (ttk.Label(pageSettings, text='Odświeżaj:')).grid( + row=2, column=0, sticky=tk.E, pady=2) + refreshPanel = tk.Frame(pageSettings) + refreshPanel.grid(row=2, column=1, sticky=tk.W+tk.E, pady=2) + (ttk.Checkbutton( + refreshPanel, + command=self._updateRefreshFields, variable=self.refresh)).grid( + row=0, column=0) + (ttk.Label(refreshPanel, text='co:')).grid(row=0, column=1) + self.intervalField = NumericSpinbox( + refreshPanel, from_=30, to=3600, width=5, justify=tk.RIGHT, + textvariable=self.refreshInterval) + self.intervalField.grid(row=0, column=2) + (ttk.Label(refreshPanel, text='sekund')).grid(row=0, column=3) + + container.columnconfigure(1, weight=1) + container.rowconfigure(4, weight=1) + + def getConfig(self): + return OrderedDict({ + 'output': self.outputPath.get(), + 'page': OrderedDict({ + 'title': self.pageTitle.get(), + 'logoh': self.pageLogoh.get(), + 'refresh': self.refreshInterval.get() \ + if self.refresh.get() > 0 else 0 + }) + }) + +class TeamsTab(PlayoffTab): + @property + def title(self): + return 'Uczestnicy' + + def renderContent(self, container): + leftFrame = tk.Frame(container) + leftFrame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) + + self.settingsFrame = TeamSettingsFrame( + leftFrame, vertical=True, padx=5, pady=5) + self.settingsFrame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + (ttk.Separator( + leftFrame, orient=tk.HORIZONTAL)).pack( + side=tk.TOP, fill=tk.X) + + self.aliasFrame = TeamAliasFrame( + leftFrame, vertical=True, padx=5, pady=5) + self.aliasFrame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + self.previewFrame = TeamPreviewFrame( + container, vertical=True, padx=5, pady=5) + self.previewFrame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) + + self._teamList = [] + self._teamListFetcher = None + + self.winfo_toplevel().bind( + '<<TeamSettingsChanged>>', self.onTeamSettingsChange, add='+') + + def onTeamSettingsChange(self, event): + if self._teamListFetcher is not None: + self.after_cancel(self._teamListFetcher) + self._teamListFetcher = self.after(500, self._fetchTeamList) + + def _fetchTeamList(self): + config = self.collectConfig() + dbConfig = self.winfo_toplevel().getDbConfig() + if dbConfig is not None: + config['database'] = dbConfig + data = PlayoffData() + db = None + try: + db = PlayoffDB(dbConfig) + except Exception: + pass + self._teamList = data.fetch_team_list(config['teams'], db) + self.winfo_toplevel().event_generate( + '<<TeamListChanged>>', when='tail') + + def getTeams(self): + return self._teamList + + def collectConfig(self): + config = OrderedDict({ + 'teams': self.settingsFrame.getConfig(), + 'team_aliases': self.aliasFrame.getConfig() + }) + tieConfig = self.previewFrame.getTieConfig() + if tieConfig is not None and isinstance(config['teams'], dict): + config['teams']['ties'] = tieConfig + orderConfig = self.previewFrame.getOrderConfig() + if orderConfig: + config['custom_final_order'] = orderConfig + return config + + def setValues(self, config): + self.settingsFrame.setValues( + config['teams'] if 'teams' in config else []) + self.aliasFrame.setValues( + config['team_aliases'] if 'team_aliases' in config else {}) + self.previewFrame.setTieConfig( + config['teams']['ties'] + if 'teams' in config and 'ties' in config['teams'] else []) + self.previewFrame.setOrderConfig( + config.get('custom_final_order', [])) + + def getConfig(self): + return self.collectConfig() + +class MatchesTab(PlayoffTab): + @property + def title(self): + return 'Mecze' + + def addPhase(self): + phase = MatchPhaseFrame( + self.phaseFrame, vertical=True, padx=10, pady=10) + newPhase = max(self.phases.keys()) + 1 if len(self.phases) else 1 + self.phaseFrame.add(phase) + self.phases[newPhase] = phase + self._renameTabs() + self.phaseFrame.select(phase) + self.winfo_toplevel().event_generate( + '<<MatchListChanged>>', when='tail') + self.winfo_toplevel().event_generate( + '<<ValueChanged>>', when='tail') + return newPhase + + def removePhase(self, phase=None): + selected = self.phaseFrame.select() if phase is None \ + else self.phases[phase] + if selected: + self.phaseFrame.forget(selected) + key_to_delete = None + for key, tab in self.phases.iteritems(): + if str(selected) == str(tab): + key_to_delete = key + break + if key_to_delete: + self.phases.pop(key_to_delete) + self.winfo_toplevel().event_generate( + '<<MatchListChanged>>', when='tail') + + def _renameTabs(self, *args): + for idx, tab in self.phases.iteritems(): + title = tab.name.get().strip() + self.phaseFrame.tab( + tab, text=(title if len(title) else '') + ' (#%d)' % (idx)) + + def renderContent(self, container): + container.columnconfigure(1, weight=1) + container.rowconfigure(2, weight=1) + (ttk.Label(container, text='Fazy rozgrywek:')).grid( + row=0, column=0, columnspan=2, sticky=tk.W) + (ttk.Button( + container, text='+', command=self.addPhase, width=5)).grid( + row=1, column=0, sticky=tk.W) + (ttk.Button( + container, text='-', command=self.removePhase, width=5)).grid( + row=1, column=1, sticky=tk.W) + self.phases = {} + self.phaseFrame = ttk.Notebook(container) + self.phaseFrame.grid( + row=2, column=0, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S) + + self.winfo_toplevel().bind( + '<<PhaseRenamed>>', self._renameTabs, add='+') + + def getMatches(self): + matches = [] + for phase in self.phases.values(): + matches += [w for w in phase.matches.widgets + if isinstance(w, MatchSettingsFrame)] + return matches + + def setValues(self, config): + phases = config['phases'] if 'phases' in config else [] + for idx in self.phases.keys(): + self.removePhase(idx) + for phase in phases: + newPhase = self.addPhase() + self.phases[newPhase].setValues(phase) + for phase in self.phases.values(): + for match in phase.matches.widgets: + if isinstance(match, MatchSettingsFrame) \ + and match.getMatchID == 0: + match.matchID.set( + self.winfo_toplevel().getNewMatchID(match)) + + def getConfig(self): + return OrderedDict({ + 'phases': [phase.getConfig() for phase in self.phases.values()] + }) + +class SwissesTab(PlayoffTab): + @property + def title(self): + return 'Swissy' + + def renderContent(self, container): + self.swisses = SwissesFrame(container, vertical=True) + self.swisses.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + def setValues(self, config): + self.swisses.setValues(config['swiss'] if 'swiss' in config else []) + + def getConfig(self): + swisses = self.swisses.getValues() + if len(swisses): + return OrderedDict({ + 'swiss': swisses + }) + else: + return None + +class NetworkTab(PlayoffTab): + @property + def title(self): + return 'Sieć' + + def _onDBSettingsChange(self, event): + if self.dbFetchTimer is not None: + self.after_cancel(self.dbFetchTimer) + self.dbFetchTimer = self.after(1500, self._fetchDBList) + + def _fetchDBList(self): + self._dbList = [] + try: + db = PlayoffDB(self.getDB()) + for row in db.fetch_all( + 'information_schema', + 'SELECT TABLE_SCHEMA FROM information_schema.COLUMNS WHERE TABLE_NAME = "admin" AND COLUMN_NAME = "teamcnt" ORDER BY TABLE_SCHEMA;', {}): + self._dbList.append(row[0]) + except Exception as e: + pass + self.winfo_toplevel().event_generate('<<DBListChanged>>', when='tail') + + def getDBList(self): + return self._dbList + + def getDB(self): + return self.mysqlFrame.getConfig() + + def renderContent(self, container): + container.columnconfigure(0, weight=1) + container.columnconfigure(1, weight=1) + container.rowconfigure(1, weight=1) + + self.mysqlFrame = MySQLConfigurationFrame(container) + self.mysqlFrame.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S) + + self.goniecFrame = GoniecConfigurationFrame(container) + self.goniecFrame.grid(row=0, column=1, sticky=tk.W+tk.E+tk.N+tk.S) + + self.remoteFrame = RemoteConfigurationFrame(container, vertical=True) + self.remoteFrame.grid( + row=1, column=0, columnspan=2, sticky=tk.W+tk.E+tk.N+tk.S) + + self._dbList = [] + self.dbFetchTimer = None + self.winfo_toplevel().bind( + '<<DBSettingsChanged>>', self._onDBSettingsChange, add='+') + + def setValues(self, config): + self.mysqlFrame.setValues( + config['database'] if 'database' in config else {}) + self.goniecFrame.setValues( + config['goniec'] if 'goniec' in config else {}) + self.remoteFrame.setValues( + config['remotes'] if 'remotes' in config else []) + + def getConfig(self): + config = OrderedDict() + mysql = self.getDB() + if mysql is not None: + config['database'] = mysql + config['goniec'] = self.goniecFrame.getValues() + remotes = self.remoteFrame.getValues() + if len(remotes): + config['remotes'] = remotes + return config + +class VisualTab(PlayoffTab): + @property + def title(self): + return 'Wygląd' + + def renderContent(self, container): + container.columnconfigure(0, weight=1) + container.rowconfigure(1, weight=1) + + self.settingsFrame = VisualSettingsFrame(container) + self.settingsFrame.grid(row=0, column=0, sticky=tk.S+tk.N+tk.E+tk.W) + + self.positionFrame = BoxPositionsFrame(container, vertical=True) + self.positionFrame.grid(row=1, column=0, sticky=tk.S+tk.N+tk.E+tk.W) + + def setValues(self, config): + if 'page' in config: + self.settingsFrame.setValues(config['page']) + else: + self.settingsFrame.setValues({}) + if 'canvas' in config and 'box_positioning' in config['canvas']: + self.positionFrame.setValues(config['canvas']['box_positioning']) + else: + self.positionFrame.setValues({}) + + def getConfig(self): + config = OrderedDict({ + 'page': self.settingsFrame.getValues() + }) + boxConfig = self.positionFrame.getValues() + if boxConfig: + config['canvas'] = OrderedDict() + config['canvas']['box_positioning'] = boxConfig + return config + +class StyleTab(PlayoffTab): + @property + def title(self): + return 'Style' + + def renderContent(self, container): + self.linesFrame = LineStylesFrame(container) + self.linesFrame.pack(side=tk.TOP, anchor=tk.W) + + (ttk.Separator(container, orient=tk.HORIZONTAL)).pack( + side=tk.TOP, fill=tk.X) + + self.positionStylesFrame = PositionStylesFrame( + container, vertical=True) + self.positionStylesFrame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + def setValues(self, config): + if 'canvas' in config: + self.linesFrame.setValues(config['canvas']) + else: + self.linesFrame.setValues({}) + if 'position_styles' in config: + self.positionStylesFrame.setValues(config['position_styles']) + else: + self.positionStylesFrame.setValues([]) + + def getConfig(self): + return OrderedDict({ + 'canvas': self.linesFrame.getValues(), + 'position_styles': self.positionStylesFrame.getValues() + }) + +class TranslationsTab(PlayoffTab): + @property + def title(self): + return 'Tłumaczenia' + + def renderContent(self, container): + self.translationsFrame = TranslationConfigurationFrame( + container, vertical=True) + self.translationsFrame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + def setValues(self, config): + if 'i18n' in config: + self.translationsFrame.setTranslations(config['i18n']) + else: + self.translationsFrame.setTranslations({}) + + def getConfig(self): + return OrderedDict({ + 'i18n': self.translationsFrame.getTranslations() + }) + +__all__ = ['MainSettingsTab', 'TeamsTab', 'MatchesTab', 'SwissesTab', + 'NetworkTab', 'VisualTab', 'StyleTab', 'TranslationsTab'] |