class CommandDropBox(QGroupBox): """QGroupBox for Nightbot command.""" _instances = set() _todelete = set() def __init__(self, controller, cmd="", msg="", parent=None): """Init dropbox.""" super().__init__(parent) self.controller = controller self._instances.add(weakref.ref(self)) self.ident = len(self._instances) layout = QHBoxLayout() self.command = MonitoredLineEdit() if not cmd: cmd = '' self.command.setText(cmd) self.command.setAlignment(Qt.AlignCenter) self.command.setPlaceholderText(("!command")) layout.addWidget(self.command) self.message = MonitoredLineEdit() self.message.setText(msg) self.message.setAlignment(Qt.AlignCenter) self.message.setPlaceholderText(_('message, e.g.,') + ' (URL)') self.message.setToolTip( _('Available placeholders:') + ' ' + ', '.join(self.controller.placeholders.available())) completer = Completer(self.controller.placeholders.available(), self.message) self.message.setCompleter(completer) layout.addWidget(self.message) self.pushButton1 = QPushButton(_('Test')) self.pushButton1.clicked.connect( lambda: self.testPlaceholder(self.message.text())) layout.addWidget(self.pushButton1) self.pushButton2 = QPushButton(_('Delete')) self.pushButton2.clicked.connect(self.remove) layout.addWidget(self.pushButton2) self.setLayout(layout) for ref in self._instances: obj = ref() if obj is not None: obj.setTitle() def connect(self, handler): """Connect handler.""" self.command.textModified.connect(handler) self.message.textModified.connect(handler) def setTitle(self): """Set the title.""" title = "Command {}".format(self.ident) super().setTitle(title) self.pushButton2.setDisabled(len(self._instances) == 1) def adjustIdent(self, removedIdent): """Adjust ident.""" if removedIdent < self.ident: self.ident -= 1 self.setTitle() def remove(self): """Remove command.""" self.parent().layout().removeWidget(self) cmd = self.command.text().strip() if cmd: self._todelete.add(cmd) self.deleteLater() self._instances.remove(weakref.ref(self)) for ref in self._instances: obj = ref() if obj is not None: obj.adjustIdent(self.ident) def testPlaceholder(self, string): """Test placeholders.""" string = self.controller.placeholders.replace(string) QMessageBox.information(self, _("Output:"), string) @classmethod def addDeletedCommand(cls, cmd): """Delete command to to-delete-list.""" cls._todelete.add(cmd.strip()) @classmethod def getData(cls): """Return commands.""" data = dict() for cmd in cls._todelete: data[cmd] = "__DELETE__" for inst_ref in cls._instances: inst = inst_ref() if inst is not None: cmd = inst.command.text().strip() msg = inst.message.text().strip() if cmd and msg: data[cmd] = msg return data @classmethod def clean(cls): """Clean command.""" cls._instances = set() cls._todelete = set()
class MainWindow(QMainWindow): """Show the main window of SCCT.""" EXIT_CODE_REBOOT = -123 def __init__(self, controller, app, showChangelog): """Init the main window.""" try: super().__init__() self._save = True self.tlock = TriggerLock() self.controller = controller self.max_no_sets = scctool.settings.max_no_sets self.scoreWidth = 35 self.raceWidth = 45 self.labelWidth = 25 self.mimumLineEditWidth = 130 self.createTabs() self.createMatchDataTabs() self.createHorizontalGroupBox() self.createLowerTabWidget() self.createMenuBar() mainLayout = QVBoxLayout() mainLayout.addWidget(self.tabs, 0) mainLayout.addWidget(self.matchDataTabWidget, 1) # mainLayout.addWidget(self.fromMatchDataBox, 1) mainLayout.addWidget(self.lowerTabWidget, 0) mainLayout.addWidget(self.horizontalGroupBox, 0) self.setWindowTitle("StarCraft Casting Tool v{}".format( scctool.__version__)) self.window = QWidget() self.window.setLayout(mainLayout) self.setCentralWidget(self.window) # self.size self.statusBar() self.leds = dict() for scope in self.controller.websocketThread.get_primary_scopes(): self.leds[scope] = LedIndicator(self) for key, led in self.leds.items(): self.controller.toogleLEDs(0, key, self) self.statusBar().addPermanentWidget(led) self.app = app self.controller.setView(self) self.controller.refreshButtonStatus() self.processEvents() self.settings = QSettings(ClientConfig.APP_NAME, ClientConfig.COMPANY_NAME) self.restoreGeometry( self.settings.value("geometry", self.saveGeometry())) self.restoreState( self.settings.value("windowState", self.saveState())) self.mysubwindows = dict() self.show() self.raise_() if showChangelog: self.openChangelog() except Exception as e: module_logger.exception("message") def showAbout(self): """Show subwindow with about info.""" html = markdown2.markdown_path(scctool.settings.getResFile("about.md")) html = html.replace("%VERSION%", scctool.__version__) if (not scctool.__new_version__): new_version = _("StarCraft Casting Tool is up to date.") else: new_version = _("The new version {} is available!").format( scctool.__latest_version__) html = html.replace('%NEW_VERSION%', new_version) # use self as parent here QMessageBox.about(self, _("StarCraft Casting Tool - About"), html) def closeEvent(self, event): """Close and clean up window.""" try: try: for name, window in self.mysubwindows.items(): if (window and window.isVisible()): window.close() finally: self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) self.controller.cleanUp(self._save) QMainWindow.closeEvent(self, event) # event.accept() except Exception as e: module_logger.exception("message") def createMenuBar(self): """Create the menu bar.""" try: menubar = self.menuBar() settingsMenu = menubar.addMenu(_('Settings')) apiAct = QAction(QIcon(scctool.settings.getResFile('browser.png')), _('Browser Sources'), self) apiAct.setToolTip(_('Edit Settings for all Browser Sources')) apiAct.triggered.connect(self.openBrowserSourcesDialog) settingsMenu.addAction(apiAct) apiAct = QAction(QIcon(scctool.settings.getResFile('twitch.png')), _('Twitch && Nightbot'), self) apiAct.setToolTip( _('Edit Intro-Settings and API-Settings' ' for Twitch and Nightbot')) apiAct.triggered.connect(self.openApiDialog) settingsMenu.addAction(apiAct) styleAct = QAction( QIcon(scctool.settings.getResFile('pantone.png')), _('Styles'), self) styleAct.setToolTip('') styleAct.triggered.connect(self.openStyleDialog) settingsMenu.addAction(styleAct) miscAct = QAction( QIcon(scctool.settings.getResFile('settings.png')), _('Misc'), self) miscAct.setToolTip('') miscAct.triggered.connect(self.openMiscDialog) settingsMenu.addAction(miscAct) self.createBrowserSrcMenu() ProfileMenu(self, self.controller) self.createLangMenu() infoMenu = menubar.addMenu(_('Info && Links')) myAct = QAction(QIcon(scctool.settings.getResFile('about.png')), _('About'), self) myAct.triggered.connect(self.showAbout) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('readme.ico')), _('Readme'), self) myAct.triggered.connect(self.openReadme) infoMenu.addAction(myAct) websiteAct = QAction( QIcon(scctool.settings.getResFile('youtube.png')), _('Video Tutorial'), self) websiteAct.triggered.connect(lambda: self.controller.openURL( "https://youtu.be/j5iWa4JB8bM")) infoMenu.addAction(websiteAct) myAct = QAction(QIcon(scctool.settings.getResFile('update.png')), _('Check for new version'), self) myAct.triggered.connect(lambda: self.controller.checkVersion(True)) infoMenu.addAction(myAct) myAct = QAction( QIcon(scctool.settings.getResFile('changelog.png')), _('Changelog'), self) myAct.triggered.connect(self.openChangelog) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('folder.png')), _('Open log folder'), self) myAct.triggered.connect(lambda: self.controller.open_file( scctool.settings.getAbsPath(scctool.settings.getLogDir()))) infoMenu.addAction(myAct) infoMenu.addSeparator() websiteAct = QAction( QIcon(scctool.settings.getResFile('scct.ico')), 'StarCraft Casting Tool', self) websiteAct.triggered.connect(lambda: self.controller.openURL( "https://teampheenix.github.io/StarCraft-Casting-Tool/")) infoMenu.addAction(websiteAct) discordAct = QAction( QIcon(scctool.settings.getResFile('discord.png')), 'Discord', self) discordAct.triggered.connect( lambda: self.controller.openURL("https://discord.gg/G9hFEfh")) infoMenu.addAction(discordAct) ixAct = QAction(QIcon(scctool.settings.getResFile('icon.png')), 'team pheeniX', self) ixAct.triggered.connect( lambda: self.controller.openURL("http://team-pheenix.de")) infoMenu.addAction(ixAct) alphaAct = QAction(QIcon(scctool.settings.getResFile('alpha.png')), 'AlphaTL', self) alphaAct.triggered.connect( lambda: self.controller.openURL("https://alpha.tl")) infoMenu.addAction(alphaAct) rstlAct = QAction(QIcon(scctool.settings.getResFile('rstl.png')), 'RSTL', self) rstlAct.triggered.connect( lambda: self.controller.openURL("http://hdgame.net/en/")) infoMenu.addAction(rstlAct) infoMenu.addSeparator() myAct = QAction(QIcon(scctool.settings.getResFile('patreon.png')), _('Become a Patron'), self) myAct.triggered.connect(lambda: self.controller.openURL( "https://www.patreon.com/StarCraftCastingTool")) infoMenu.addAction(myAct) myAct = QAction(QIcon(scctool.settings.getResFile('donate.ico')), _('Donate via PayPal'), self) myAct.triggered.connect(lambda: self.controller.openURL( "https://paypal.me/StarCraftCastingTool")) infoMenu.addAction(myAct) except Exception as e: module_logger.exception("message") def createLangMenu(self): menubar = self.menuBar() langMenu = menubar.addMenu(_('Language')) language = scctool.settings.config.parser.get("SCT", "language") languages = [] languages.append({ 'handle': 'de_DE', 'icon': 'de.png', 'name': 'Deutsch', 'active': True }) languages.append({ 'handle': 'en_US', 'icon': 'en.png', 'name': 'English', 'active': True }) languages.append({ 'handle': 'fr_FR', 'icon': 'fr.png', 'name': 'Français', 'active': True }) languages.append({ 'handle': 'ru_RU', 'icon': 'ru.png', 'name': 'Pусский', 'active': True }) for lang in languages: myAct = QAction(QIcon(scctool.settings.getResFile(lang['icon'])), lang['name'], self, checkable=True) myAct.setChecked(language == lang['handle']) myAct.setDisabled(not lang['active']) myAct.triggered.connect( lambda x, handle=lang['handle']: self.changeLanguage(handle)) langMenu.addAction(myAct) def createBrowserSrcMenu(self): menubar = self.menuBar() main_menu = menubar.addMenu(_('Browser Sources')) srcs = [] srcs.append({ 'name': _('Intro'), 'file': 'intro.html', 'settings': lambda: self.openBrowserSourcesDialog('intro') }) srcs.append({ 'name': _('Mapstats'), 'file': 'mapstats.html', 'settings': lambda: self.openBrowserSourcesDialog('mapstats') }) srcs.append({'name': _('Score'), 'file': 'score.html'}) srcs.append({ 'name': _('Map Icons Box'), 'settings': lambda: self.openBrowserSourcesDialog('mapicons_box'), 'sub': [{ 'name': _('Icon Set {}').format(1), 'file': 'mapicons_box_1.html' }, { 'name': _('Icon Set {}').format(2), 'file': 'mapicons_box_2.html' }, { 'name': _('Icon Set {}').format(3), 'file': 'mapicons_box_3.html' }] }) srcs.append({ 'name': _('Map Icons Landscape'), 'settings': lambda: self.openBrowserSourcesDialog("mapicons_landscape"), 'sub': [{ 'name': _('Icon Set {}').format(1), 'file': 'mapicons_landscape_1.html' }, { 'name': _('Icon Set {}').format(2), 'file': 'mapicons_landscape_2.html' }, { 'name': _('Icon Set {}').format(3), 'file': 'mapicons_landscape_3.html' }] }) srcs.append({ 'name': _('Misc'), 'sub': [{ 'name': _('Logo {}').format(1), 'file': 'logo1.html' }, { 'name': _('Logo {}').format(2), 'file': 'logo2.html' }, { 'name': _('UI Logo {}').format(1), 'file': 'ui_logo_1.html' }, { 'name': _('UI Logo {}').format(2), 'file': 'ui_logo_2.html' }, { 'name': _('Aligulac (only 1vs1)'), 'file': 'aligulac.html' }, { 'name': _('Countdown'), 'file': 'countdown.html' }, { 'name': _('League (ALphaTL && RSTL only)'), 'file': 'league.html' }, { 'name': _('Matchbanner (AlphaTL)'), 'file': 'matchbanner.html', 'settings': lambda: self.openMiscDialog('alphatl') }] }) act = QAction(QIcon(scctool.settings.getResFile('folder.png')), _('Open Folder'), self) act.triggered.connect(lambda: self.controller.open_file( scctool.settings.getAbsPath(scctool.settings.casting_html_dir))) main_menu.addAction(act) main_menu.addSeparator() for src in srcs: myMenu = QMenu(src['name'], self) sub = src.get('sub', False) if sub: for icon in sub: mySubMenu = QMenu(icon['name'], self) icon['file'] = os.path.join( scctool.settings.casting_html_dir, icon['file']) act = QAction( QIcon(scctool.settings.getResFile('html.png')), _('Open in Browser'), self) act.triggered.connect( lambda x, file=icon['file']: self.controller.openURL( scctool.settings.getAbsPath(file))) mySubMenu.addAction(act) act = QAction( QIcon(scctool.settings.getResFile('copy.png')), _('Copy URL to Clipboard'), self) act.triggered.connect( lambda x, file=icon['file']: QApplication.clipboard( ).setText(scctool.settings.getAbsPath(file))) mySubMenu.addAction(act) if icon.get('settings', None) is not None: act = QAction( QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) act.triggered.connect(icon['settings']) mySubMenu.addAction(act) myMenu.addMenu(mySubMenu) else: src['file'] = os.path.join(scctool.settings.casting_html_dir, src['file']) act = QAction(QIcon(scctool.settings.getResFile('html.png')), _('Open in Browser'), self) act.triggered.connect( lambda x, file=src['file']: self.controller.openURL( scctool.settings.getAbsPath(file))) myMenu.addAction(act) act = QAction(QIcon(scctool.settings.getResFile('copy.png')), _('Copy URL to Clipboard'), self) act.triggered.connect( lambda x, file=src['file']: QApplication.clipboard( ).setText(scctool.settings.getAbsPath(file))) myMenu.addAction(act) if src.get('settings', None) is not None: act = QAction( QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) act.triggered.connect(src['settings']) myMenu.addAction(act) main_menu.addMenu(myMenu) main_menu.addSeparator() apiAct = QAction(QIcon(scctool.settings.getResFile('browser.png')), _('Settings'), self) apiAct.setToolTip(_('Edit Settings for all Browser Sources')) apiAct.triggered.connect(self.openBrowserSourcesDialog) main_menu.addAction(apiAct) styleAct = QAction(QIcon(scctool.settings.getResFile('pantone.png')), _('Styles'), self) styleAct.setToolTip('') styleAct.triggered.connect(self.openStyleDialog) main_menu.addAction(styleAct) def openApiDialog(self): """Open subwindow with connection settings.""" self.mysubwindows['connections'] = SubwindowConnections() self.mysubwindows['connections'].createWindow(self) self.mysubwindows['connections'].show() def openStyleDialog(self): """Open subwindow with style settings.""" self.mysubwindows['styles'] = SubwindowStyles() self.mysubwindows['styles'].createWindow(self) self.mysubwindows['styles'].show() def openMiscDialog(self, tab=''): """Open subwindow with misc settings.""" self.mysubwindows['misc'] = SubwindowMisc() self.mysubwindows['misc'].createWindow(self, tab) self.mysubwindows['misc'].show() def openBrowserSourcesDialog(self, tab=''): """Open subwindow with misc settings.""" self.mysubwindows['browser'] = SubwindowBrowserSources() self.mysubwindows['browser'].createWindow(self, tab) self.mysubwindows['browser'].show() def openReadme(self): """Open subwindow with readme viewer.""" self.mysubwindows['readme'] = SubwindowMarkdown() self.mysubwindows['readme'].createWindow( self, _("Readme"), scctool.settings.getResFile('readme.ico'), scctool.settings.getResFile("../README.md")) self.mysubwindows['readme'].show() def openChangelog(self): """Open subwindow with readme viewer.""" self.mysubwindows['changelog'] = SubwindowMarkdown() self.mysubwindows['changelog'].createWindow( self, "StarCraft Casting Tool " + _("Changelog"), scctool.settings.getResFile("changelog.png"), scctool.settings.getResFile("../CHANGELOG.md")) self.mysubwindows['changelog'].show() def changeLanguage(self, language): """Change the language.""" scctool.settings.config.parser.set("SCT", "language", language) self.restart() def updateAllMapCompleters(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updateMapCompleters() def updateAllPlayerCompleters(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updatePlayerCompleters() def updateAllMapButtons(self): for idx in range(self.matchDataTabWidget.count()): self.matchDataTabWidget.widget(idx).updateMapButtons() def createMatchDataTabs(self): self.matchDataTabWidget = QTabWidget() self.matchDataTabWidget.setMovable(True) closeable = self.controller.matchControl.countMatches() > 1 self.matchDataTabWidget.setUsesScrollButtons(True) for match in self.controller.matchControl.getMatches(): MatchDataWidget(self, self.matchDataTabWidget, match, closeable) container = QWidget() buttonLayout = QHBoxLayout() buttonLayout.setContentsMargins(2, 1, 1, 2) buttonLayout.setSpacing(1) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('add.png')) button.setIcon(pixmap) button.setFixedSize(28, 28) # button.setFlat(True) button.setToolTip(_('Add Match Tab')) button.clicked.connect(self.addMatchTab) buttonLayout.addWidget(button) button = QPushButton() button.setFixedSize(28, 28) pixmap = QIcon(scctool.settings.getResFile('copy.png')) button.setIcon(pixmap) # button.setFlat(True) button.setToolTip(_('Copy Match Tab')) button.clicked.connect(self.copyMatchTab) buttonLayout.addWidget(button) container.setLayout(buttonLayout) self.matchDataTabWidget.setCornerWidget(container) tabBar = self.matchDataTabWidget.tabBar() tabBar.setExpanding(True) self.matchDataTabWidget.currentChanged.connect( self.currentMatchTabChanged) tabBar.tabMoved.connect(self.tabMoved) def addMatchTab(self): match = self.controller.matchControl.newMatchData() MatchDataWidget(self, self.matchDataTabWidget, match) count = self.matchDataTabWidget.count() self.matchDataTabWidget.setCurrentIndex(count - 1) if count > 1: for idx in range(count): self.matchDataTabWidget.widget(idx).setClosable(True) def copyMatchTab(self): matchId = self.controller.matchControl.selectedMatchId() data = self.controller.matchControl.selectedMatch().getData() match = self.controller.matchControl.newMatchData(data) self.controller.logoManager.copyMatch(match.getControlID(), matchId) MatchDataWidget(self, self.matchDataTabWidget, match) count = self.matchDataTabWidget.count() self.matchDataTabWidget.setCurrentIndex(count - 1) if count > 1: for idx in range(count): self.matchDataTabWidget.widget(idx).setClosable(True) def currentMatchTabChanged(self, idx): dataWidget = self.matchDataTabWidget.widget(idx) ident = dataWidget.matchData.getControlID() self.controller.matchControl.selectMatch(ident) with self.tlock: self.controller.updateMatchFormat() def tabMoved(self, toIdx, fromIdx): self.controller.matchControl.updateOrder(toIdx, fromIdx) def createTabs(self): """Create tabs in main window.""" try: # Initialize tab screen self.tabs = QTabWidget() self.tab1 = QWidget() self.tab2 = QWidget() # self.tabs.resize(300,200) # Add tabs self.tabs.addTab(self.tab1, _("Match Grabber for AlphaTL && RSTL")) self.tabs.addTab(self.tab2, _("Custom Match")) # Create first tab self.tab1.layout = QVBoxLayout() self.le_url = MatchComboBox(self) self.le_url.returnPressed.connect(self.refresh_click) minWidth = self.scoreWidth + 2 * self.raceWidth + \ 2 * self.mimumLineEditWidth + 4 * 6 self.le_url.setMinimumWidth(minWidth) self.pb_openBrowser = QPushButton(_("Open in Browser")) self.pb_openBrowser.clicked.connect(self.openBrowser_click) width = (self.scoreWidth + 2 * self.raceWidth + 2 * self.mimumLineEditWidth + 4 * 6) / 2 - 2 self.pb_openBrowser.setMinimumWidth(width) container = QHBoxLayout() label = QLabel() label.setFixedWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match-URL:")) label.setMinimumWidth(80) container.addWidget(label, 0) container.addWidget(self.le_url, 1) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('alpha.png')) button.setIcon(pixmap) button.clicked.connect( lambda: self.controller.openURL("https://alpha.tl/")) container.addWidget(button, 0) button = QPushButton() pixmap = QIcon(scctool.settings.getResFile('rstl.png')) button.setIcon(pixmap) button.clicked.connect( lambda: self.controller.openURL("http://hdgame.net/en/")) container.addWidget(button, 0) self.tab1.layout = QFormLayout() self.tab1.layout.addRow(container) container = QHBoxLayout() # self.pb_download = QPushButton("Download Images from URL") # container.addWidget(self.pb_download) label = QLabel() label.setFixedWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel() label.setMinimumWidth(80) container.addWidget(label, 0) self.pb_refresh = QPushButton(_("Load Data from URL")) self.pb_refresh.clicked.connect(self.refresh_click) container.addWidget(self.pb_openBrowser, 3) container.addWidget(self.pb_refresh, 3) self.tab1.layout.addRow(container) self.tab1.setLayout(self.tab1.layout) # Create second tab self.tab2.layout = QVBoxLayout() container = QHBoxLayout() label = QLabel() label.setMinimumWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match Format:")) label.setMinimumWidth(80) container.addWidget(label, 0) container.addWidget(QLabel(_("Best of")), 0) self.cb_bestof = QComboBox() for idx in range(0, scctool.settings.max_no_sets): self.cb_bestof.addItem(str(idx + 1)) self.cb_bestof.setCurrentIndex(3) string = _('"Best of 6/4": First, a Bo5/3 is played and the' ' ace map gets extended to a Bo3 if needed;' ' Best of 2: Bo3 with only two maps played.') self.cb_bestof.setToolTip(string) self.cb_bestof.setMaximumWidth(40) self.cb_bestof.currentIndexChanged.connect(self.changeBestOf) container.addWidget(self.cb_bestof, 0) container.addWidget(QLabel(_(" but at least")), 0) self.cb_minSets = QComboBox() self.cb_minSets.setToolTip( _('Minimum number of maps played (even if the match' ' is decided already)')) self.cb_minSets.setMaximumWidth(40) container.addWidget(self.cb_minSets, 0) container.addWidget(QLabel(" " + _("maps") + " "), 0) self.cb_minSets.currentIndexChanged.connect( lambda idx: self.highlightApplyCustom()) self.cb_allkill = QCheckBox(_("All-Kill Format")) self.cb_allkill.setChecked(False) self.cb_allkill.setToolTip( _('Winner stays and is automatically' ' placed into the next set')) self.cb_allkill.stateChanged.connect(self.allkill_change) container.addWidget(self.cb_allkill, 0) self.cb_solo = QCheckBox(_("1vs1")) self.cb_solo.setChecked(False) self.cb_solo.setToolTip(_('Select for solo (non-team matches)')) container.addWidget(self.cb_solo, 0) self.cb_solo.stateChanged.connect( lambda idx: self.highlightApplyCustom()) label = QLabel("") container.addWidget(label, 1) self.applycustom_is_highlighted = False self.pb_applycustom = QToolButton() action = QAction(_("Apply Format")) action.triggered.connect(self.applycustom_click) self.pb_applycustom.setDefaultAction(action) self.custom_menu = QMenu(self.pb_applycustom) for format, icon in \ self.controller.matchControl.getCustomFormats(): if icon: action = self.custom_menu.addAction( QIcon(scctool.settings.getResFile(icon)), format) else: action = self.custom_menu.addAction(format) action.triggered.connect( lambda x, format=format: self.applyCustomFormat(format)) self.pb_applycustom.setMenu(self.custom_menu) self.pb_applycustom.setPopupMode(QToolButton.MenuButtonPopup) self.pb_applycustom.setFixedWidth(150) container.addWidget(self.pb_applycustom, 0) self.defaultButtonPalette = self.pb_applycustom.palette() self.tab2.layout.addLayout(container) container = QHBoxLayout() label = QLabel() label.setMinimumWidth(self.labelWidth) container.addWidget(label, 0) label = QLabel(_("Match-URL:")) label.setMinimumWidth(80) container.addWidget(label, 0) self.le_url_custom = MonitoredLineEdit() self.le_url_custom.setAlignment(Qt.AlignCenter) self.le_url_custom.setToolTip( _('Optionally specify the Match-URL,' ' e.g., for Nightbot commands')) self.le_url_custom.setPlaceholderText( _("Specify the Match-URL of your Custom Match")) completer = QCompleter(["http://"], self.le_url_custom) completer.setCaseSensitivity(Qt.CaseInsensitive) completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) completer.setWrapAround(True) self.le_url_custom.setCompleter(completer) self.le_url_custom.setMinimumWidth(360) self.le_url_custom.textModified.connect(self.highlightApplyCustom) container.addWidget(self.le_url_custom, 11) label = QLabel("") container.addWidget(label, 1) self.pb_resetdata = QPushButton(_("Reset Match Data")) self.pb_resetdata.setFixedWidth(150) self.pb_resetdata.clicked.connect(self.resetdata_click) container.addWidget(self.pb_resetdata, 0) self.tab2.layout.addLayout(container) self.tab2.setLayout(self.tab2.layout) except Exception as e: module_logger.exception("message") def allkill_change(self): try: self.controller.matchControl.\ selectedMatch().setAllKill(self.cb_allkill.isChecked()) except Exception as e: module_logger.exception("message") def changeBestOf(self, bestof): """Change the minimum sets combo box on change of BoX.""" bestof = bestof + 1 self.cb_minSets.clear() self.highlightApplyCustom() for idx in range(0, bestof): self.cb_minSets.addItem(str(idx + 1)) if bestof == 2: self.cb_minSets.setCurrentIndex(1) else: self.cb_minSets.setCurrentIndex((bestof - 1) / 2) def createHorizontalGroupBox(self): """Create horizontal group box for tasks.""" try: self.horizontalGroupBox = QGroupBox(_("Tasks")) layout = QHBoxLayout() self.pb_twitchupdate = QPushButton(_("Update Twitch Title")) self.pb_twitchupdate.clicked.connect(self.updatetwitch_click) self.pb_nightbotupdate = QPushButton(_("Update Nightbot")) self.pb_nightbotupdate.clicked.connect(self.updatenightbot_click) self.pb_resetscore = QPushButton(_("Reset Score")) self.pb_resetscore.clicked.connect(self.resetscore_click) layout.addWidget(self.pb_twitchupdate) layout.addWidget(self.pb_nightbotupdate) layout.addWidget(self.pb_resetscore) self.horizontalGroupBox.setLayout(layout) except Exception as e: module_logger.exception("message") def createLowerTabWidget(self): """Create the tab widget at the bottom.""" try: self.lowerTabWidget = QTabWidget() self.createBackgroundTasksTab() self.countdownTab = CountdownWidget(self.controller, self) self.lowerTabWidget.addTab(self.backgroundTasksTab, _("Background Tasks")) self.lowerTabWidget.addTab(self.countdownTab, _("Countdown")) except Exception as e: module_logger.exception("message") def createBackgroundTasksTab(self): """Create group box for background tasks.""" try: self.backgroundTasksTab = QWidget() self.cb_autoUpdate = QCheckBox(_("Auto Score Update")) self.cb_autoUpdate.setChecked(False) string = _('Automatically detects the outcome' + ' of SC2 matches that are ' + 'played/observed in your SC2-client' + ' and updates the score accordingly.') self.cb_autoUpdate.setToolTip(string) self.cb_autoUpdate.stateChanged.connect(self.autoUpdate_change) self.cb_autoToggleScore = QCheckBox(_("Set Ingame Score")) self.cb_autoToggleScore.setChecked(False) string = _('Automatically sets the score of your ingame' + ' UI-interface at the begining of a game.') self.cb_autoToggleScore.setToolTip(string) self.cb_autoToggleScore.stateChanged.connect( self.autoToggleScore_change) self.cb_autoToggleProduction = QCheckBox( _("Toggle Production Tab")) self.cb_autoToggleProduction.setChecked(False) string = _('Automatically toggles the production tab of your' + ' ingame UI-interface at the begining of a game.') self.cb_autoToggleProduction.setToolTip(string) self.cb_autoToggleProduction.stateChanged.connect( self.autoToggleProduction_change) self.cb_autoTwitch = QCheckBox(_("Auto Twitch Update")) self.cb_autoTwitch.setChecked(False) self.cb_autoTwitch.stateChanged.connect(self.autoTwitch_change) self.cb_autoNightbot = QCheckBox(_("Auto Nightbot Update")) self.cb_autoNightbot.setChecked(False) self.cb_autoNightbot.stateChanged.connect(self.autoNightbot_change) if (not scctool.settings.windows): self.cb_autoToggleScore.setEnabled(False) self.cb_autoToggleScore.setAttribute(Qt.WA_AlwaysShowToolTips) self.cb_autoToggleScore.setToolTip(_('Only Windows')) self.cb_autoToggleProduction.setEnabled(False) self.cb_autoToggleProduction.setAttribute( Qt.WA_AlwaysShowToolTips) self.cb_autoToggleProduction.setToolTip(_('Only Windows')) layout = QGridLayout() layout.addWidget(self.cb_autoTwitch, 0, 0) layout.addWidget(self.cb_autoNightbot, 0, 1) layout.addWidget(self.cb_autoUpdate, 1, 0) layout.addWidget(self.cb_autoToggleScore, 1, 1) layout.addWidget(self.cb_autoToggleProduction, 1, 2) self.backgroundTasksTab.setLayout(layout) except Exception as e: module_logger.exception("message") def autoTwitch_change(self): """Handle change of auto twitch check box.""" try: if (self.cb_autoTwitch.isChecked()): self.controller.autoRequestsThread.activateTask('twitch') else: self.controller.autoRequestsThread.deactivateTask('twitch') except Exception as e: module_logger.exception("message") def autoNightbot_change(self): """Handle change of auto twitch check box.""" try: if (self.cb_autoNightbot.isChecked()): self.controller.autoRequestsThread.activateTask('nightbot') else: self.controller.autoRequestsThread.deactivateTask('nightbot') except Exception as e: module_logger.exception("message") def autoUpdate_change(self): """Handle change of auto score update check box.""" try: if (self.cb_autoUpdate.isChecked()): self.controller.runSC2ApiThread("updateScore") else: self.controller.stopSC2ApiThread("updateScore") except Exception as e: module_logger.exception("message") def autoToggleScore_change(self): """Handle change of toggle score check box.""" try: if (self.cb_autoToggleScore.isChecked()): self.controller.runSC2ApiThread("toggleScore") else: self.controller.stopSC2ApiThread("toggleScore") except Exception as e: module_logger.exception("message") def autoToggleProduction_change(self): """Handle change of toggle production tab check box.""" try: if (self.cb_autoToggleProduction.isChecked()): self.controller.runSC2ApiThread("toggleProduction") else: self.controller.stopSC2ApiThread("toggleProduction") except Exception as e: module_logger.exception("message") def applyCustomFormat(self, format): """Handle click to apply custom format.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: self.controller.matchControl.\ selectedMatch().applyCustomFormat(format) self.controller.updateMatchFormat() matchWidget = self.matchDataTabWidget.currentWidget() matchWidget.updateForms() self.resizeWindow() self.highlightApplyCustom(False) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def applycustom_click(self): """Handle click to apply custom match.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: self.statusBar().showMessage(_('Applying Custom Match...')) msg = self.controller.applyCustom( int(self.cb_bestof.currentText()), self.cb_allkill.isChecked(), self.cb_solo.isChecked(), int(self.cb_minSets.currentText()), self.le_url_custom.text().strip()) self.statusBar().showMessage(msg) self.highlightApplyCustom(False) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def resetdata_click(self): """Handle click to reset the data.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: with self.tlock: msg = self.controller.resetData() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def refresh_click(self): """Handle click to refresh/load data from an URL.""" QApplication.setOverrideCursor(Qt.WaitCursor) try: url = self.le_url.lineEdit().text() with self.tlock: self.statusBar().showMessage(_('Reading {}...').format(url)) msg = self.controller.refreshData(url) self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") finally: QApplication.restoreOverrideCursor() def openBrowser_click(self): """Handle request to open URL in browser.""" try: url = self.le_url.text() self.controller.openURL(url) except Exception as e: module_logger.exception("message") def updatenightbot_click(self): """Handle click to change nightbot command.""" try: self.statusBar().showMessage(_('Updating Nightbot Command...')) msg = self.controller.updateNightbotCommand() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") def updatetwitch_click(self): """Handle click to change twitch title.""" try: self.statusBar().showMessage(_('Updating Twitch Title...')) msg = self.controller.updateTwitchTitle() self.statusBar().showMessage(msg) except Exception as e: module_logger.exception("message") def resetscore_click(self, myteam=False): """Handle click to reset the score.""" try: self.statusBar().showMessage(_('Resetting Score...')) with self.tlock: matchDataWidget = self.matchDataTabWidget.currentWidget() for set_idx in range(self.max_no_sets): matchDataWidget.sl_score[set_idx].setValue(0) self.controller.matchControl.selectedMatch().setMapScore( set_idx, 0, overwrite=True) self.controller.autoSetNextMap() if myteam: matchDataWidget.sl_team.setValue(0) self.controller.matchControl.selectedMatch().setMyTeam(0) if not self.controller.resetWarning(): self.statusBar().showMessage('') except Exception as e: module_logger.exception("message") def highlightApplyCustom(self, highlight=True, force=False): if not force and not self.tlock.trigger(): return try: if self.applycustom_is_highlighted == highlight: return highlight except AttributeError: return False if highlight: myPalette = self.pb_applycustom.palette() myPalette.setColor(QPalette.Background, Qt.darkBlue) myPalette.setColor(QPalette.ButtonText, Qt.darkBlue) self.pb_applycustom.setPalette(myPalette) else: self.pb_applycustom.setPalette(self.defaultButtonPalette) self.applycustom_is_highlighted = highlight return highlight def logoDialog(self, team, matchDataWidget): """Open dialog for team logo.""" self.controller.logoManager.resetLogoChanged() self.mysubwindows['icons'] = SubwindowLogos() self.mysubwindows['icons'].createWindow(self, self.controller, team, matchDataWidget) self.mysubwindows['icons'].show() def resizeWindow(self): """Resize the window height to size hint.""" if (not self.isMaximized()): self.processEvents() self.resize(self.width(), self.sizeHint().height()) def processEvents(self): """Process ten PyQt5 events.""" for i in range(0, 10): self.app.processEvents() def restart(self, save=True): """Restart the main window.""" self._save = save self.close() self.app.exit(self.EXIT_CODE_REBOOT)
class SubwindowConnections(QWidget): """Show connections settings sub window.""" current_tab = -1 def createWindow(self, mainWindow, tab=''): """Create window.""" try: parent = None super().__init__(parent) # self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setWindowIcon(QIcon( scctool.settings.getResFile('twitch.png'))) self.setWindowModality(Qt.ApplicationModal) self.mainWindow = mainWindow self.passEvent = False self.controller = mainWindow.controller self.__dataChanged = False self.createButtonGroup() self.createTabs(tab) mainLayout = QVBoxLayout() mainLayout.addWidget(self.tabs) mainLayout.addLayout(self.buttonGroup) self.setLayout(mainLayout) self.resize( QSize(int(mainWindow.size().width() * 0.8), self.sizeHint().height())) relativeChange = QPoint(int(mainWindow.size().width() / 2), int(mainWindow.size().height() / 3)) -\ QPoint(int(self.size().width() / 2), int(self.size().height() / 3)) self.move(mainWindow.pos() + relativeChange) self.setWindowTitle(_("Twitch & Nightbot Connections")) except Exception: module_logger.exception("message") def createTabs(self, tab=''): """Create tabs.""" self.tabs = QTabWidget() self.createFormGroupTwitch() self.createFormGroupNightbot() # Add tabs self.tabs.addTab(self.formGroupTwitch, QIcon(scctool.settings.getResFile('twitch.png')), _("Twitch")) self.tabs.addTab(self.formGroupNightbot, QIcon(scctool.settings.getResFile('nightbot.ico')), _("Nightbot")) table = dict() table['twitch'] = 0 table['nightbot'] = 1 self.tabs.setCurrentIndex(table.get(tab, self.current_tab)) self.tabs.currentChanged.connect(self.tabChanged) @classmethod def tabChanged(cls, idx): """Save current tab index.""" cls.current_tab = idx def createFormGroupTwitch(self): """Create forms for twitch.""" self.formGroupTwitch = QWidget() layout = QFormLayout() self.twitchChannel = MonitoredLineEdit() self.twitchChannel.textModified.connect(self.changed) self.twitchChannel.setText( scctool.settings.config.parser.get("Twitch", "channel")) self.twitchChannel.setAlignment(Qt.AlignCenter) self.twitchChannel.setPlaceholderText( _("Name of the Twitch channel that should be updated")) self.twitchChannel.setToolTip( _('The connected twitch user needs to have editor' ' rights for this channel.')) layout.addRow(QLabel("Twitch-Channel:"), self.twitchChannel) container = QHBoxLayout() self.twitchToken = MonitoredLineEdit() self.twitchToken.textModified.connect(self.changed) self.twitchToken.setText( scctool.settings.config.parser.get("Twitch", "oauth")) self.twitchToken.setAlignment(Qt.AlignCenter) self.twitchToken.setPlaceholderText( _("Press 'Get' to generate a token")) self.twitchToken.setEchoMode(QLineEdit.Password) self.twitchToken.setToolTip(_("Press 'Get' to generate a new token.")) container.addWidget(self.twitchToken) self.pb_getTwitch = QPushButton(_('Get')) self.pb_getTwitch.setFixedWidth(100) self.pb_getTwitch.clicked.connect( lambda: self.controller.authThread.requestToken('twitch')) container.addWidget(self.pb_getTwitch) layout.addRow(QLabel(_("Access-Token:")), container) container = QHBoxLayout() self.twitchTemplate = MonitoredLineEdit() self.twitchTemplate.textModified.connect(self.changed) self.twitchTemplate.setText( scctool.settings.config.parser.get("Twitch", "title_template")) self.twitchTemplate.setAlignment(Qt.AlignCenter) self.twitchTemplate.setPlaceholderText("(League) – (Team1) vs (Team2)") self.twitchTemplate.setToolTip( _('Available placeholders:') + " " + ', '.join(self.controller.placeholders.available())) completer = Completer(self.controller.placeholders.available(), self.twitchTemplate) self.twitchTemplate.setCompleter(completer) container.addWidget(self.twitchTemplate) button = QPushButton(_('Test')) button.setFixedWidth(100) button.clicked.connect( lambda: self.testPlaceholder(self.twitchTemplate.text())) container.addWidget(button) label = QLabel(_("Title Template:")) label.setFixedWidth(100) layout.addRow(label, container) container = QVBoxLayout() self.cb_set_game = QCheckBox(_("Set Game to 'StarCraft II'")) self.cb_set_game.setChecked( scctool.settings.config.parser.getboolean("Twitch", "set_game")) self.cb_set_game.stateChanged.connect(self.changed) container.addWidget(self.cb_set_game) label = QLabel(_("Options:") + " ") label.setMinimumWidth(120) layout.addRow(label, container) layout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupTwitch.setLayout(layout) def createFormGroupNightbot(self): """Create forms for nightbot.""" self.formGroupNightbot = QWidget() mainLayout = QVBoxLayout() tokenBox = QGroupBox("Access-Token") container = QHBoxLayout() self.nightbotToken = MonitoredLineEdit() self.nightbotToken.textModified.connect(self.changed) self.nightbotToken.setText( scctool.settings.config.parser.get("Nightbot", "token")) self.nightbotToken.setAlignment(Qt.AlignCenter) self.nightbotToken.setEchoMode(QLineEdit.Password) self.nightbotToken.setPlaceholderText( _("Press 'Get' to generate a token")) self.nightbotToken.setToolTip(_("Press 'Get' to generate a token.")) container.addWidget(self.nightbotToken) self.pb_getNightbot = QPushButton(_('Get')) self.pb_getNightbot.clicked.connect( lambda: self.controller.authThread.requestToken('nightbot')) self.pb_getNightbot.setFixedWidth(100) # self.pb_getNightbot.setEnabled(False) container.addWidget(self.pb_getNightbot) tokenBox.setLayout(container) mainLayout.addWidget(tokenBox, 0) # scroll area widget contents - layout self.scrollLayout = QVBoxLayout() self.scrollLayout.setDirection(QBoxLayout.BottomToTop) self.scrollLayout.addStretch(0) buttonLayout = QHBoxLayout() buttonLayout.addStretch(0) self.scrollLayout.addLayout(buttonLayout) # scroll area widget contents self.scrollWidget = QWidget() self.scrollWidget.setLayout(self.scrollLayout) # scroll area self.scrollArea = QScrollArea() self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.scrollWidget) self.scrollArea.setFixedHeight(180) mainLayout.addWidget(self.scrollArea, 1) layout = QHBoxLayout() layout.addWidget(QLabel("")) addButton = QPushButton(_('Add Command')) addButton.clicked.connect(self.addCommand) layout.addWidget(addButton) mainLayout.addLayout(layout, 0) data = scctool.settings.nightbot_commands if len(data) == 0: self.addCommand() else: for cmd, msg in data.items(): self.addCommand(cmd, msg) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupNightbot.setLayout(mainLayout) def addCommand(self, cmd="", msg=""): """Add a nightbot command.""" if msg != "__DELETE__": dropbox = CommandDropBox(self.controller, cmd=cmd, msg=msg) dropbox.connect(self.changed) self.scrollLayout.insertWidget(1, dropbox) else: CommandDropBox.addDeletedCommand(cmd) def createButtonGroup(self): """Create buttons.""" try: layout = QHBoxLayout() layout.addWidget(QLabel("")) buttonCancel = QPushButton(_('Cancel')) buttonCancel.clicked.connect(self.closeWindow) layout.addWidget(buttonCancel) buttonSave = QPushButton(_('&Save && Close')) buttonSave.setToolTip(_("Shortcut: {}").format("Ctrl+S")) self.shortcut = QShortcut(QKeySequence("Ctrl+S"), self) self.shortcut.setAutoRepeat(False) self.shortcut.activated.connect(self.saveCloseWindow) buttonSave.clicked.connect(self.saveCloseWindow) layout.addWidget(buttonSave) self.buttonGroup = layout except Exception: module_logger.exception("message") def changed(self, *values): """Handle changed data.""" self.__dataChanged = True def saveData(self): """Save the data to config.""" if (self.__dataChanged): scctool.settings.config.parser.set( "Twitch", "channel", self.twitchChannel.text().strip()) scctool.settings.config.parser.set("Twitch", "oauth", self.twitchToken.text().strip()) scctool.settings.config.parser.set( "Twitch", "title_template", self.twitchTemplate.text().strip()) scctool.settings.config.parser.set( "Twitch", "set_game", str(self.cb_set_game.isChecked())) scctool.settings.config.parser.set( "Nightbot", "token", self.nightbotToken.text().strip()) self.__dataChanged = False self.controller.refreshButtonStatus() scctool.settings.nightbot_commands = CommandDropBox.getData() def saveCloseWindow(self): """Save and close window.""" self.saveData() self.closeWindow() def closeWindow(self): """Close window without save.""" self.passEvent = True self.close() def closeEvent(self, event): """Handle close event.""" try: if (not self.__dataChanged): CommandDropBox.clean() event.accept() return if (not self.passEvent): if (self.isMinimized()): self.showNormal() buttonReply = QMessageBox.question( self, _('Save data?'), _("Do you want to save the data?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if buttonReply == QMessageBox.Yes: self.saveData() CommandDropBox.clean() event.accept() except Exception: module_logger.exception("message") def testPlaceholder(self, string): """Test placeholders.""" string = self.controller.placeholders.replace(string) QMessageBox.information(self, _("Output:"), string)