class AddGlyphsDialog(QDialog): # TODO: implement Frederik's Glyph Construction Builder def __init__(self, currentGlyphs=None, parent=None): super().__init__(parent) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(self.tr("Add Glyphs…")) self.currentGlyphs = currentGlyphs self.currentGlyphNames = [glyph.name for glyph in currentGlyphs] layout = QGridLayout(self) self.markColorWidget = ColorVignette(self) self.markColorWidget.setFixedWidth(56) self.importCharDrop = QComboBox(self) self.importCharDrop.addItem(self.tr("Import glyph names…")) glyphSets = settings.readGlyphSets() for name, glyphNames in glyphSets.items(): self.importCharDrop.addItem(name, glyphNames) self.importCharDrop.currentIndexChanged[int].connect(self.importGlyphs) self.addGlyphsEdit = QPlainTextEdit(self) self.addGlyphsEdit.setFocus(True) self.addUnicodeBox = QCheckBox(self.tr("Add Unicode"), self) self.addUnicodeBox.setChecked(True) self.addAsTemplateBox = QCheckBox(self.tr("Add as template"), self) self.addAsTemplateBox.setChecked(True) self.sortFontBox = QCheckBox(self.tr("Sort font"), self) self.overrideBox = QCheckBox(self.tr("Override"), self) buttonBox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) l = 0 layout.addWidget(self.markColorWidget, l, 0) layout.addWidget(self.importCharDrop, l, 3, 1, 2) l += 1 layout.addWidget(self.addGlyphsEdit, l, 0, 1, 5) l += 1 layout.addWidget(self.addUnicodeBox, l, 0) layout.addWidget(self.addAsTemplateBox, l, 1) layout.addWidget(self.sortFontBox, l, 2) layout.addWidget(self.overrideBox, l, 3) layout.addWidget(buttonBox, l, 4) self.setLayout(layout) @classmethod def getNewGlyphNames(cls, parent, currentGlyphs=None): dialog = cls(currentGlyphs, parent) result = dialog.exec_() markColor = dialog.markColorWidget.color() if markColor is not None: markColor = markColor.getRgbF() params = dict( addUnicode=dialog.addUnicodeBox.isChecked(), asTemplate=dialog.addAsTemplateBox.isChecked(), markColor=markColor, override=dialog.overrideBox.isChecked(), sortFont=dialog.sortFontBox.isChecked(), ) newGlyphNames = [] for name in dialog.addGlyphsEdit.toPlainText().split(): if name not in dialog.currentGlyphNames: newGlyphNames.append(name) return (newGlyphNames, params, result) def importGlyphs(self, index): if index == 0: return glyphNames = self.importCharDrop.currentData() editorNames = self.addGlyphsEdit.toPlainText().split() currentNames = set(self.currentGlyphNames) ^ set(editorNames) changed = False for name in glyphNames: if name not in currentNames: changed = True editorNames.append(name) if changed: self.addGlyphsEdit.setPlainText(" ".join(editorNames)) cursor = self.addGlyphsEdit.textCursor() cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) self.addGlyphsEdit.setTextCursor(cursor) self.importCharDrop.setCurrentIndex(0) self.addGlyphsEdit.setFocus(True)
class ConfigDialog(QDialog): configChanged = pyqtSignal() def __init__(self, title, config): QDialog.__init__(self) if config is not None: self.type = config.type else: self.type = JDEROBOTCOMM self.setWindowTitle(title) commSelectionBox = QGroupBox('Select Communication Interface') commSelectionBox.setObjectName('commInterface') # add new config input fields fixedWidthFont = QFontDatabase.systemFont(QFontDatabase.FixedFont) self.commTypeCombo = QComboBox() self.commTypeCombo.setFont(fixedWidthFont) self.commTypeCombo.setMaximumWidth(220) boxLayout = QVBoxLayout() boxLayout.addWidget(self.commTypeCombo) commSelectionBox.setLayout(boxLayout) vLayout = QFormLayout() vLayout.addWidget(commSelectionBox) self.configsLayout = QVBoxLayout() self.configsBox = QGroupBox('') self.configsBox.setLayout(self.configsLayout) vLayout.addWidget(self.configsBox) self.setLayout(vLayout) self.resize(700, 500) #self.setStyleSheet('QGroupBox#commInterface { border: 1px solid black; border-radius: 4px; padding:15px;} QGroupBox::title#commInterface {background-color:transparent; padding-left:25px; padding-top:5px;} ') self.rosConfigsUI = RosConfigDialog('ROS Communication') self.rosConfigsUI.configChanged.connect(self.configChangedHandler) self.configsLayout.addWidget(self.rosConfigsUI) self.rosConfigsUI.setVisible(False) self.jderobotCommConfigsUI = JdeRobotCommConfigDialog('JdeRobot Communication') self.jderobotCommConfigsUI.configChanged.connect(self.configChangedHandler) self.configsLayout.addWidget(self.jderobotCommConfigsUI) self.jderobotCommConfigsUI.setVisible(True) self.rosConfig = None self.jdeRobotCommConfig = None self.commTypeCombo.addItem('JdeRobot Communication', 'jderobotcomm') self.commTypeCombo.addItem('ROS Node', 'ros') self.commTypeCombo.currentIndexChanged.connect(self.commTypeComboChanged) if config is not None: if config.type == ROS: self.rosConfig = config self.commTypeCombo.setCurrentIndex(1) self.loadRosConfigs() elif config.type == JDEROBOTCOMM: self.jdeRobotCommConfig = config self.commTypeCombo.setCurrentIndex(0) self.loadJdeRobotCommConfigs() else: self.loadJdeRobotCommConfigs() def commTypeComboChanged(self): if self.commTypeCombo.currentData() == 'ros': self.loadRosConfigs() elif self.commTypeCombo.currentData() == 'jderobotcomm': self.loadJdeRobotCommConfigs() def loadRosConfigs(self): self.type = ROS self.jderobotCommConfigsUI.setVisible(False) self.rosConfigsUI.setVisible(True) if self.rosConfig is None: self.rosConfig = RosConfig() self.rosConfigsUI.setConfig(self.rosConfig) self.configChanged.emit() def loadJdeRobotCommConfigs(self): self.type = JDEROBOTCOMM self.rosConfigsUI.setVisible(False) self.jderobotCommConfigsUI.setVisible(True) if self.jdeRobotCommConfig is None: self.jdeRobotCommConfig = JdeRobotConfig() self.jderobotCommConfigsUI.setConfig(self.jdeRobotCommConfig) self.configChanged.emit() def configChangedHandler(self): self.configChanged.emit() def getConfig(self): if self.type == ROS: return self.rosConfig elif self.type == JDEROBOTCOMM: return self.jdeRobotCommConfig
class GuiConfigEditGeneralTab(QWidget): def __init__(self, theParent): QWidget.__init__(self, theParent) self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme # The Form self.mainForm = QConfigLayout() self.mainForm.setHelpTextStyle(self.theTheme.helpText) self.setLayout(self.mainForm) # Look and Feel # ============= self.mainForm.addGroupLabel("Look and Feel") ## Select Theme self.selectTheme = QComboBox() self.selectTheme.setMinimumWidth(self.mainConf.pxInt(200)) self.theThemes = self.theTheme.listThemes() for themeDir, themeName in self.theThemes: self.selectTheme.addItem(themeName, themeDir) themeIdx = self.selectTheme.findData(self.mainConf.guiTheme) if themeIdx != -1: self.selectTheme.setCurrentIndex(themeIdx) self.mainForm.addRow( "Main GUI theme", self.selectTheme, "Changing this requires restarting novelWriter." ) ## Select Icon Theme self.selectIcons = QComboBox() self.selectIcons.setMinimumWidth(self.mainConf.pxInt(200)) self.theIcons = self.theTheme.theIcons.listThemes() for iconDir, iconName in self.theIcons: self.selectIcons.addItem(iconName, iconDir) iconIdx = self.selectIcons.findData(self.mainConf.guiIcons) if iconIdx != -1: self.selectIcons.setCurrentIndex(iconIdx) self.mainForm.addRow( "Main icon theme", self.selectIcons, "Changing this requires restarting novelWriter." ) ## Dark Icons self.preferDarkIcons = QSwitch() self.preferDarkIcons.setChecked(self.mainConf.guiDark) self.mainForm.addRow( "Prefer icons for dark backgrounds", self.preferDarkIcons, "This may improve the look of icons on dark themes." ) ## Font Family self.guiFont = QLineEdit() self.guiFont.setReadOnly(True) self.guiFont.setFixedWidth(self.mainConf.pxInt(162)) self.guiFont.setText(self.mainConf.guiFont) self.fontButton = QPushButton("...") self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) self.fontButton.clicked.connect(self._selectFont) self.mainForm.addRow( "Font family", self.guiFont, "Changing this requires restarting novelWriter.", theButton = self.fontButton ) ## Font Size self.guiFontSize = QSpinBox(self) self.guiFontSize.setMinimum(8) self.guiFontSize.setMaximum(60) self.guiFontSize.setSingleStep(1) self.guiFontSize.setValue(self.mainConf.guiFontSize) self.mainForm.addRow( "Font size", self.guiFontSize, "Changing this requires restarting novelWriter.", theUnit = "pt" ) # GUI Settings # ============ self.mainForm.addGroupLabel("GUI Settings") self.showFullPath = QSwitch() self.showFullPath.setChecked(self.mainConf.showFullPath) self.mainForm.addRow( "Show full path in document header", self.showFullPath, "Shows the document title and parent folder names." ) self.hideVScroll = QSwitch() self.hideVScroll.setChecked(self.mainConf.hideVScroll) self.mainForm.addRow( "Hide vertical scroll bars in main windows", self.hideVScroll, "Scrolling with mouse wheel and keys only." ) self.hideHScroll = QSwitch() self.hideHScroll.setChecked(self.mainConf.hideHScroll) self.mainForm.addRow( "Hide horizontal scroll bars in main windows", self.hideHScroll, "Scrolling with mouse wheel and keys only." ) return def saveValues(self): """Save the values set for this tab. """ validEntries = True needsRestart = False guiTheme = self.selectTheme.currentData() guiIcons = self.selectIcons.currentData() guiDark = self.preferDarkIcons.isChecked() guiFont = self.guiFont.text() guiFontSize = self.guiFontSize.value() showFullPath = self.showFullPath.isChecked() hideVScroll = self.hideVScroll.isChecked() hideHScroll = self.hideHScroll.isChecked() # Check if restart is needed needsRestart |= self.mainConf.guiTheme != guiTheme needsRestart |= self.mainConf.guiIcons != guiIcons needsRestart |= self.mainConf.guiFont != guiFont needsRestart |= self.mainConf.guiFontSize != guiFontSize self.mainConf.guiTheme = guiTheme self.mainConf.guiIcons = guiIcons self.mainConf.guiDark = guiDark self.mainConf.guiFont = guiFont self.mainConf.guiFontSize = guiFontSize self.mainConf.showFullPath = showFullPath self.mainConf.hideVScroll = hideVScroll self.mainConf.hideHScroll = hideHScroll self.mainConf.confChanged = True return validEntries, needsRestart ## # Slots ## def _selectFont(self): """Open the QFontDialog and set a font for the font style. """ currFont = QFont() currFont.setFamily(self.mainConf.guiFont) currFont.setPointSize(self.mainConf.guiFontSize) theFont, theStatus = QFontDialog.getFont(currFont, self) if theStatus: self.guiFont.setText(theFont.family()) self.guiFontSize.setValue(theFont.pointSize()) return
class GuiConfigEditEditingTab(QWidget): def __init__(self, theParent): QWidget.__init__(self, theParent) self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme # The Form self.mainForm = QConfigLayout() self.mainForm.setHelpTextStyle(self.theTheme.helpText) self.setLayout(self.mainForm) # Spell Checking # ============== self.mainForm.addGroupLabel("Syntax Highlighting") ## Syntax Highlighting self.selectSyntax = QComboBox() self.selectSyntax.setMinimumWidth(self.mainConf.pxInt(200)) self.theSyntaxes = self.theTheme.listSyntax() for syntaxFile, syntaxName in self.theSyntaxes: self.selectSyntax.addItem(syntaxName, syntaxFile) syntaxIdx = self.selectSyntax.findData(self.mainConf.guiSyntax) if syntaxIdx != -1: self.selectSyntax.setCurrentIndex(syntaxIdx) self.mainForm.addRow( "Highlight theme", self.selectSyntax, "" ) self.highlightQuotes = QSwitch() self.highlightQuotes.setChecked(self.mainConf.highlightQuotes) self.mainForm.addRow( "Highlight text wrapped in quotes", self.highlightQuotes, helpText="Applies to single, double and straight quotes." ) self.highlightEmph = QSwitch() self.highlightEmph.setChecked(self.mainConf.highlightEmph) self.mainForm.addRow( "Add highlight colour to emphasised text", self.highlightEmph, helpText="Applies to emphasis, strong and strikethrough." ) # Spell Checking # ============== self.mainForm.addGroupLabel("Spell Checking") ## Spell Check Provider and Language self.spellLangList = QComboBox(self) self.spellToolList = QComboBox(self) self.spellToolList.addItem("Internal (difflib)", NWSpellCheck.SP_INTERNAL) self.spellToolList.addItem("Spell Enchant (pyenchant)", NWSpellCheck.SP_ENCHANT) theModel = self.spellToolList.model() idEnchant = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT) theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant) self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool) toolIdx = self.spellToolList.findData(self.mainConf.spellTool) if toolIdx != -1: self.spellToolList.setCurrentIndex(toolIdx) self._doUpdateSpellTool(0) self.mainForm.addRow( "Spell check provider", self.spellToolList, "Note that the internal spell check tool is quite slow." ) self.mainForm.addRow( "Spell check language", self.spellLangList ) ## Big Document Size Limit self.bigDocLimit = QSpinBox(self) self.bigDocLimit.setMinimum(10) self.bigDocLimit.setMaximum(10000) self.bigDocLimit.setSingleStep(10) self.bigDocLimit.setValue(self.mainConf.bigDocLimit) self.mainForm.addRow( "Big document limit", self.bigDocLimit, "Disables full spell checking over the size limit.", theUnit="kB" ) # Writing Guides # ============== self.mainForm.addGroupLabel("Writing Guides") ## Show Tabs and Spaces self.showTabsNSpaces = QSwitch() self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces) self.mainForm.addRow( "Show tabs and spaces", self.showTabsNSpaces ) ## Show Line Endings self.showLineEndings = QSwitch() self.showLineEndings.setChecked(self.mainConf.showLineEndings) self.mainForm.addRow( "Show line endings", self.showLineEndings ) return def saveValues(self): """Save the values set for this tab. """ validEntries = True needsRestart = False guiSyntax = self.selectSyntax.currentData() highlightQuotes = self.highlightQuotes.isChecked() highlightEmph = self.highlightEmph.isChecked() spellTool = self.spellToolList.currentData() spellLanguage = self.spellLangList.currentData() bigDocLimit = self.bigDocLimit.value() showTabsNSpaces = self.showTabsNSpaces.isChecked() showLineEndings = self.showLineEndings.isChecked() self.mainConf.guiSyntax = guiSyntax self.mainConf.highlightQuotes = highlightQuotes self.mainConf.highlightEmph = highlightEmph self.mainConf.spellTool = spellTool self.mainConf.spellLanguage = spellLanguage self.mainConf.bigDocLimit = bigDocLimit self.mainConf.showTabsNSpaces = showTabsNSpaces self.mainConf.showLineEndings = showLineEndings self.mainConf.confChanged = True return validEntries, needsRestart ## # Internal Functions ## def _disableComboItem(self, theList, theValue): """Disable a list item in the combo box. """ theModel = theList.model() anItem = theModel.item(1) anItem.setFlags(anItem.flags() ^ Qt.ItemIsEnabled) return theModel def _doUpdateSpellTool(self, currIdx): """Update the list of dictionaries based on spell tool selected. """ spellTool = self.spellToolList.currentData() self._updateLanguageList(spellTool) return def _updateLanguageList(self, spellTool): """Updates the list of available spell checking dictionaries available for the selected spell check tool. It will try to preserve the language choice, if the language exists in the updated list. """ if spellTool == NWSpellCheck.SP_ENCHANT: theDict = NWSpellEnchant() else: theDict = NWSpellSimple() self.spellLangList.clear() for spTag, spName in theDict.listDictionaries(): self.spellLangList.addItem(spName, spTag) spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage) if spellIdx != -1: self.spellLangList.setCurrentIndex(spellIdx) return
class NewEntryDialog(QDialog): def __init__(self, parent): QDialog.__init__(self, parent) self.submitted = False self.cover_bytes = b'' self.setWindowTitle('Add new entry') self.container = QWidget() self.horizontal_layout = QHBoxLayout() self.container_left_layout = QVBoxLayout() self.container_right_layout = QVBoxLayout() self.horizontal_layout.addLayout(self.container_left_layout) self.horizontal_layout.addLayout(self.container_right_layout) label_type = QLabel(_('Entry type:')) self.combo_type = QComboBox() label_type.setBuddy(self.combo_type) for entry in EntryType: self.combo_type.addItem(entry.value, entry) label_status = QLabel(_('Entry status:')) self.combo_status = QComboBox() label_status.setBuddy(self.combo_status) for entry in EntryStatus: self.combo_status.addItem(entry.value, entry) label_progress = QLabel(_('Progress status:')) self.combo_progress = QComboBox() label_progress.setBuddy(self.combo_progress) for entry in ProgressStatus: self.combo_progress.addItem(entry.value, entry) label_org_name = QLabel(_('Entry original name:')) self.line_org_name = QLineEdit() label_org_name.setBuddy(self.line_org_name) label_eng_name = QLabel(_('Entry english name:')) self.line_eng_name = QLineEdit() label_eng_name.setBuddy(self.line_eng_name) label_synonyms = QLabel(_('Synonym names:')) self.line_synonyms = QLineEdit() label_synonyms.setBuddy(self.line_synonyms) label_description = QLabel(_('Description:')) self.description_textbox = QPlainTextEdit() label_description.setBuddy(self.description_textbox) self.nsfw = QCheckBox(_('NSFW')) only_int = QIntValidator(bottom=0) label_current_progress = QLabel(_('Current progress:')) self.current_progress_textbox = QLineEdit() label_current_progress.setBuddy(self.current_progress_textbox) self.current_progress_textbox.setValidator(only_int) label_current_progress.setBuddy(self.current_progress_textbox) label_max_progress = QLabel(_('Max progress:')) self.max_progress_textbox = QLineEdit() label_max_progress.setBuddy(self.max_progress_textbox) self.max_progress_textbox.setValidator(only_int) label_max_progress.setBuddy(self.max_progress_textbox) label_release_date = QLabel(_('Release date:')) self.release_date1 = QDateEdit() self.release_date2 = QDateEdit() label_release_date.setBuddy(self.release_date1) label_release_date.setBuddy(self.release_date2) self.cover = QLabel("Cover:") self.load_cover_button = QPushButton(_('Load cover')) self.load_cover_button.clicked.connect(self.load_image) self.accept_button = QPushButton(_('Add')) self.container_left_layout.addWidget(self.cover) self.container_left_layout.addWidget(self.load_cover_button) self.container_right_layout.addWidget(label_type) self.container_right_layout.addWidget(self.combo_type) self.container_right_layout.addWidget(label_status) self.container_right_layout.addWidget(self.combo_status) self.container_right_layout.addWidget(label_progress) self.container_right_layout.addWidget(self.combo_progress) self.container_right_layout.addWidget(label_org_name) self.container_right_layout.addWidget(self.line_org_name) self.container_right_layout.addWidget(label_eng_name) self.container_right_layout.addWidget(self.line_eng_name) self.container_right_layout.addWidget(label_synonyms) self.container_right_layout.addWidget(self.line_synonyms) self.container_right_layout.addWidget(label_description) self.container_right_layout.addWidget(self.description_textbox) self.container_right_layout.addWidget(self.nsfw) self.container_right_layout.addWidget(label_current_progress) self.container_right_layout.addWidget(self.current_progress_textbox) self.container_right_layout.addWidget(label_max_progress) self.container_right_layout.addWidget(self.max_progress_textbox) self.container_right_layout.addWidget(label_release_date) date_layout = QHBoxLayout() date_layout.addWidget(self.release_date1) date_layout.addWidget(self.release_date2) self.container_right_layout.addLayout(date_layout) self.container_right_layout.addWidget(self.accept_button) self.root_layout = QVBoxLayout(self) self.main_layout = QVBoxLayout(self) self.root_layout.addLayout(self.main_layout) self.container.setLayout(self.horizontal_layout) self.main_layout.addWidget(self.container) self.setLayout(self.root_layout) self.accept_button.clicked.connect(self.submit) def submit(self): self.submitted = True self.close() def load_image(self): fname = QFileDialog.getOpenFileName(self, 'Select entry cover') image_path = fname[0] self.cover_bytes = open(image_path, 'rb').read() qp = QPixmap() qp.loadFromData(self.cover_bytes) self.cover.setPixmap(qp) self.cover.setMaximumHeight(500) self.cover.setMaximumWidth(500) def get_values(self): entry_status = self.combo_status.currentData() entry_type = self.combo_type.currentData() entry_progress = self.combo_progress.currentData() date1 = self.release_date1.date().toPyDate() date2 = self.release_date2.date().toPyDate() try: current_progress = int(self.current_progress_textbox.text()) except: current_progress = 0 try: max_progress = int(self.max_progress_textbox.text()) except: max_progress = -1 entry = GenericEntry(self.cover_bytes, self.line_eng_name.text(), self.line_org_name.text(), self.line_synonyms.text(), date1, date2, entry_status, self.description_textbox.toPlainText(), self.nsfw.isChecked(), entry_type, current_progress, max_progress, entry_progress) return entry
class GalaxyOwnPlanetSelectorWidget(QFrame): planetSelected = pyqtSignal(int) def __init__(self, parent: QWidget): super(GalaxyOwnPlanetSelectorWidget, self).__init__(parent) self.world = XNovaWorld_instance() # setup frame self.setFrameShadow(QFrame.Raised) self.setFrameShape(QFrame.StyledPanel) # layout self._layout = QVBoxLayout() self._layout.setContentsMargins(6, 6, 6, 6) self._layout.setSpacing(0) self.setLayout(self._layout) # label self._lbl = QLabel(self.tr('Select planet:'), self) # combo self._cb = QComboBox(self) self._cb.setEditable(False) self._cb.setInsertPolicy(QComboBox.InsertAtBottom) self._cb.currentIndexChanged.connect(self.on_cb_currentChanged) # finalize layout self._layout.addWidget(self._lbl, 0, Qt.AlignHCenter) self._layout.addWidget(self._cb, 0, Qt.AlignCenter) # ... self.fill_planets() def fill_planets(self): planets = self.world.get_planets() for pl in planets: st = '{0} {1}'.format(pl.name, pl.coords.coords_str()) dt = QVariant(pl.planet_id) self._cb.addItem(st, dt) @pyqtSlot(int) def on_cb_currentChanged(self, index: int): if index == -1: return planet_id = int(self._cb.currentData(Qt.UserRole)) self.planetSelected.emit(planet_id)
class JdeRobotCommConfigDialog(QDialog): configChanged = pyqtSignal() def __init__(self, name): super(QDialog, self).__init__() self.setWindowTitle(name) self.config = None self.gridLayout = QGridLayout() # add header self.gridLayout.addWidget(QLabel('Server Type'), 0, 0) self.gridLayout.addWidget(QLabel('Name'), 0, 1) self.gridLayout.addWidget(QLabel('Topic'), 0, 2) self.gridLayout.addWidget(QLabel('Proxy Name'), 0, 3) self.gridLayout.addWidget(QLabel('IP'), 0, 4) self.gridLayout.addWidget(QLabel('Port'), 0, 5) self.gridLayout.addWidget(QLabel('Interface'), 0, 6) self.gridLayout.addWidget(QLabel(''), 0, 7) # add new config input fields fixedWidthFont = QFontDatabase.systemFont(QFontDatabase.FixedFont) self.serverTypeCombo = QComboBox() self.serverTypeCombo.setFont(fixedWidthFont) self.gridLayout.addWidget(self.serverTypeCombo, 1, 0) self.nameEdit = QLineEdit() self.nameEdit.setFont(fixedWidthFont) self.gridLayout.addWidget(self.nameEdit, 1, 1) self.topicEdit = QLineEdit() self.topicEdit.setFont(fixedWidthFont) self.topicEdit.setEnabled(False) self.gridLayout.addWidget(self.topicEdit, 1, 2) self.proxyNameEdit = QLineEdit() self.proxyNameEdit.setFont(fixedWidthFont) self.gridLayout.addWidget(self.proxyNameEdit, 1, 3) self.ipEdit = QLineEdit() self.ipEdit.setFont(fixedWidthFont) self.gridLayout.addWidget(self.ipEdit, 1, 4) self.portEdit = QLineEdit() self.portEdit.setFont(fixedWidthFont) self.gridLayout.addWidget(self.portEdit, 1, 5) self.interfaceCombo = QComboBox() self.gridLayout.addWidget(self.interfaceCombo, 1, 6) self.addButton = QPushButton('Add') self.gridLayout.addWidget(self.addButton, 1, 7) self.addButton.clicked.connect(self.addClicked) self.rowCount = 2 # add server types to the combobox self.serverTypeCombo.addItem('ICE', 'ice') self.serverTypeCombo.addItem('ROS', 'ros') self.serverTypeCombo.currentIndexChanged.connect(self.serverTypeChanged) # add interfaces to the combobox interfaces = Interfaces.getInterfaces() for interfaceName in interfaces: self.interfaceCombo.addItem(interfaceName, interfaceName) self.resize(700, 100) self.setLayout(self.gridLayout) def clearAll(self): deleteList = [] for i in range(self.rowCount): if i == 0 or i == 1: continue else: for j in range(8): item = self.gridLayout.itemAtPosition(i, j) deleteList.append(item) for item in deleteList: self.gridLayout.removeItem(item) item.widget().setParent(None) self.rowCount = 2 def setConfig(self, config): self.config = config self.clearAll() if self.config is not None: for interface in self.config.getInterfaces(): interface['id'] = self.rowCount self.addConfigRow(interface) def addConfigRow(self, configData): self.gridLayout.addWidget(QLabel(configData['serverType']), self.rowCount, 0) self.gridLayout.addWidget(QLabel(configData['name']), self.rowCount, 1) self.gridLayout.addWidget(QLabel(configData['topic']), self.rowCount, 2) self.gridLayout.addWidget(QLabel(configData['proxyName']), self.rowCount, 3) self.gridLayout.addWidget(QLabel(configData['ip']), self.rowCount, 4) self.gridLayout.addWidget(QLabel(configData['port']), self.rowCount, 5) self.gridLayout.addWidget(QLabel(configData['interface']), self.rowCount, 6) deleteButton = QPushButton('Delete') deleteButton.clicked.connect(self.deleteClicked) # we will find the item to be deleted based on the index on the config list deleteButton.setObjectName(str(self.rowCount)) self.gridLayout.addWidget(deleteButton, self.rowCount, 7) self.rowCount += 1 def serverTypeChanged(self): if self.serverTypeCombo.currentData() == 'ros': self.topicEdit.setEnabled(True) self.proxyNameEdit.setEnabled(False) self.ipEdit.setEnabled(False) self.portEdit.setEnabled(False) elif self.serverTypeCombo.currentData() == 'ice': self.topicEdit.setEnabled(False) self.proxyNameEdit.setEnabled(True) self.ipEdit.setEnabled(True) self.portEdit.setEnabled(True) self.configChanged.emit() def deleteClicked(self): if self.config is not None: id = int(self.sender().objectName()) self.config.removeInterface(id) # reset the config to redraw all configs self.setConfig(self.config) self.configChanged.emit() def addClicked(self): configData = {} configData['serverType'] = self.serverTypeCombo.currentData() configData['name'] = self.nameEdit.text() configData['topic'] = self.topicEdit.text() configData['proxyName'] = self.proxyNameEdit.text() configData['ip'] = self.ipEdit.text() configData['port'] = self.portEdit.text() configData['interface'] = self.interfaceCombo.currentData() self.nameEdit.setText('') self.topicEdit.setText('') self.proxyNameEdit.setText('') self.ipEdit.setText('') self.portEdit.setText('') if self.config is not None: configData['id'] = self.rowCount self.config.addInterface(configData) self.addConfigRow(configData) self.configChanged.emit()
class addTileDialog(QDialog): output = None ready = False def __init__(self): super().__init__() self.tilename = QComboBox() for tile in tilelist.tilelist: self.tilename.addItem(tile.__name__, tile) self.tilename.currentIndexChanged.connect(self.setTable) okbtn = QPushButton('Ok') okbtn.clicked.connect(self.onOk) clbtn = QPushButton('Cancel') clbtn.clicked.connect(self.onCancel) self.table = QTableWidget(2, 2) self.setTable(self.tilename.currentIndex()) hbox = QHBoxLayout() hbox.addWidget(clbtn) hbox.addWidget(okbtn) vbox = QVBoxLayout() vbox.addWidget(self.tilename) vbox.addWidget(self.table) vbox.addLayout(hbox) self.setLayout(vbox) self.resize(400, 300) self.table.setColumnWidth(1, 250) self.setModal(True) self.show() def setTable(self, index): try: raw = 2 + len(self.tilename.itemData(index).extraText) except TypeError: raw = 2 self.table.setRowCount(raw) self.table.setItem(0, 0, QTableWidgetItem('X position')) self.table.setCellWidget(0, 1, QSpinBox()) self.table.setItem(1, 0, QTableWidgetItem('Y position')) self.table.setCellWidget(1, 1, QSpinBox()) if self.tilename.itemData(index).extraText != None: i = 0 for line in self.tilename.itemData(index).extraText: self.table.setItem(2 + i, 0, QTableWidgetItem(line)) self.table.setCellWidget(2 + i, 1, QLineEdit('')) i += 1 def getParams(self): x = self.table.cellWidget(0, 1).value() y = self.table.cellWidget(1, 1).value() extra = None if self.tilename.currentData().extraTypes != None: i = 0 extra = [] for t in self.tilename.currentData().extraTypes: if t == 'bool': extra.append(bool(self.table.cellWidget(2 + i, 1).text())) elif t == 'int': extra.append(int(self.table.cellWidget(2 + i, 1).text())) elif t == 'float': extra.append(float(self.table.cellWidget(2 + i, 1).text())) elif t == 'str': extra.append(str(self.table.cellWidget(2 + i, 1).text())) else: extra.append(str(self.table.cellWidget(2 + i, 1).text())) i += 1 return (x, y, extra) def getTileParams(self): return self.output def onOk(self): self.output = (self.tilename.currentData(), self.getParams()) self.ready = True self.accept() def onCancel(self): self.output = (self.tilename.currentData(), self.getParams()) self.ready = True self.reject()
class TopicsTab(QWidget): configChanged = pyqtSignal() def __init__(self): super(QWidget, self).__init__() self.config = None self.count = 0 self.topicRows = {} self.nameEdit = QLineEdit() self.dataTypeComboBox = QComboBox() self.fillDataTypes() self.opTypeComboBox = QComboBox() self.opTypeComboBox.addItem('sub', 'Subscribe') self.opTypeComboBox.addItem('pub', 'Publish') self.addButton = QPushButton('Add') self.addButton.clicked.connect(self.addClicked) self.mainLayout = QVBoxLayout() rowLayout = QHBoxLayout() rowLayout.addWidget(self.nameEdit) rowLayout.addWidget(self.dataTypeComboBox) rowLayout.addWidget(self.opTypeComboBox) rowLayout.addWidget(self.addButton) rowContainer = QWidget() rowContainer.setLayout(rowLayout) rowContainer.setObjectName('titleRow') self.mainLayout.addWidget(rowContainer) self.setLayout(self.mainLayout) def fillDataTypes(self): rosTypes = Interfaces.getRosMessageTypes() for type in rosTypes: concatType = type['typeDir'] + '/' + type['type'] self.dataTypeComboBox.addItem(concatType, concatType) def addTopicRow(self, name, type, opType): rowLayout = QHBoxLayout() rowLayout.addWidget(QLabel(name)) rowLayout.addWidget(QLabel(type)) rowLayout.addWidget(QLabel(opType)) removeButton = QPushButton('Remove') removeButton.clicked.connect(self.removeTopicClicked) removeButton.setObjectName(str(self.count)) rowLayout.addWidget(removeButton) rowContainer = QWidget() rowContainer.setLayout(rowLayout) rowContainer.setObjectName('row' + str(self.count)) self.mainLayout.addWidget(rowContainer) self.topicRows[self.count] = rowContainer self.count += 1 def addClicked(self): if self.config is not None: self.config.addTopic(self.count, self.nameEdit.text(), self.dataTypeComboBox.currentData(), self.opTypeComboBox.currentData()) self.addTopicRow(self.nameEdit.text(), self.dataTypeComboBox.currentData(), self.opTypeComboBox.currentData()) self.nameEdit.setText('') self.configChanged.emit() def removeTopicClicked(self): if self.config is not None: itemToRemove = None for i in range(self.mainLayout.count()): if self.mainLayout.itemAt(i).widget().objectName() == 'row' + self.sender().objectName(): itemToRemove = self.mainLayout.itemAt(i) break if itemToRemove is not None: self.mainLayout.removeItem(itemToRemove) itemToRemove.widget().setParent(None) self.mainLayout.update() self.configChanged.emit() self.config.removeTopic(int(self.sender().objectName())) del self.topicRows[int(self.sender().objectName())] def clearAllRows(self): clearList = [] for i in range(self.mainLayout.count()): item = self.mainLayout.itemAt(i) if item.widget().objectName() != 'titleRow': clearList.append(item) for item in clearList: self.mainLayout.removeItem(item) item.widget().setParent(None) self.mainLayout.update() self.count = 0 def setConfig(self, config): self.config = config self.clearAllRows() for topic in self.config.getTopics(): topic['id'] = self.count self.addTopicRow(topic['name'], topic['type'], topic['opType'])
class InputData(QWidget): submitted = pyqtSignal(object) def __init__(self, msg, num_of_fields, default_value=None, numeric=False, placeholders=None, dropdown=False, dropdown_text=None, dropdown_options=None): super(InputData, self).__init__() self.display_msg = msg self.number_of_input_fields = num_of_fields self.numeric = numeric if default_value is not None: self.default = default_value else: self.default = [] for i in range(num_of_fields): self.default.append("") if placeholders is not None: self.placeholders = placeholders else: self.placeholders = [] for i in range(num_of_fields): self.placeholders.append("Unspecified field") if dropdown: self.dropdown = True self.dropdown_text = dropdown_text self.dropdown_options = dropdown_options else: self.dropdown = False self.textboxes = [] self.init_ui() def init_ui(self): _, _, width, height = QDesktopWidget().screenGeometry().getCoords() self.setGeometry(int(0.2 * width), int(0.2 * height), 300, 200) self.setWindowTitle("Ola senor !") self.setWindowIcon(QIcon("img/question_mark_icon.png")) v_layout = QVBoxLayout() self.explanation_text_label = QLabel(self.display_msg) v_layout.addWidget(self.explanation_text_label) if self.dropdown: v_layout.addWidget(QLabel(self.dropdown_text)) self.combobox = QComboBox() for name, data in self.dropdown_options.items(): self.combobox.addItem(name, data) v_layout.addWidget(self.combobox) for i in range(self.number_of_input_fields): textbox = QLineEdit(self.default[i]) textbox.setPlaceholderText(self.placeholders[i]) self.textboxes.append(textbox) v_layout.addWidget(textbox) # self.textbox_input_value = QLineEdit(self.default) # v_layout.addWidget(self.textbox_input_value) self.submit_btn = QPushButton("OK") self.submit_btn.clicked.connect(self.submit_data) v_layout.addWidget(self.submit_btn) self.setLayout(v_layout) self.show() def submit_data(self): send_value = [] for i, numeric_required in enumerate(self.numeric): if numeric_required: if is_numeric(self.textboxes[i].text()): send_value.append(self.textboxes[i].text()) else: show_error_message("Warning", "Input data has to be numeric") return else: send_value.append(self.textboxes[i].text()) if self.dropdown: send_value.append(self.combobox.currentData()) self.submitted.emit(send_value) self.close()
class App(QDialog): def __init__(self): super().__init__() self.title = 'Consensus Plotter' self.left = 10 self.top = 10 self.width = 320 self.height = 100 self.initUI() self.setWindowIcon(QIcon(resource_path('app_icon.ico'))) def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.createGridLayout() windowLayout = QVBoxLayout() windowLayout.addWidget(self.horizontalGroupBox) self.setLayout(windowLayout) self.show() def createGridLayout(self): self.horizontalGroupBox = QGroupBox("ROC Plot options") layout = QGridLayout() layout.setColumnStretch(1, 4) layout.setColumnStretch(2, 4) self.target_list = QComboBox() self.target_list.addItem('Select Target') self.target_list.addItem('CDK5') self.target_list.addItem('GSK3b') self.target_list.addItem('CK1') self.target_list.addItem('DYRK1a') #trg_lst = get_target self.method_list = QComboBox() self.method_list.addItem('Select Method', None) self.plot_button = QPushButton('Plot') self.fig_button = QPushButton('New Figure') self.invert_opt = QCheckBox('Invert ROC plot') self.new_window = QCheckBox('Plot on new') self.weighted = QCheckBox('weighted ') self.exp_value = QLineEdit('1.0') self.best_auc = QLineEdit('10') self.clear_plot = QPushButton('Clear Plot') self.best_roc = QPushButton('Best ROC Areas') self.populate_methods() layout.addWidget(self.target_list, 0, 0) layout.addWidget(self.method_list, 1, 0, 1, 2) layout.addWidget(self.plot_button, 1, 2) layout.addWidget(self.invert_opt, 0, 2) layout.addWidget(self.exp_value, 2, 0) layout.addWidget(self.clear_plot, 2, 2) layout.addWidget(self.new_window, 1, 3) layout.addWidget(self.weighted, 1, 4) layout.addWidget(self.best_roc, 2, 3) layout.addWidget(self.best_auc, 2, 4) #layout.addWidget(self.fig_button, 0, 2) self.plot_button.clicked.connect(self.plot_fig) self.clear_plot.clicked.connect(self.clear_fig) self.best_roc.clicked.connect(self.plot_best_roc) self.fig_button.clicked.connect(self.new_fig) self.target_list.currentIndexChanged.connect(self.selectionchange) self.horizontalGroupBox.setLayout(layout) def plot_best_roc(self): #find_best_auc(self.target_list.currentText(),self.invert_opt.isChecked(),float(self.exp_value.text()),int(self.best_auc.text())) #cumulative_best_roc() calculate_enrichment_factors() def populate_methods(self): self.method_list.clear() #self.target_list.addItem('Select Method', None) if self.target_list.currentText() != 'Select Target': mthds = get_targets_methods(self.target_list.currentText()) for mtd, idx in zip(mthds, range(len(mthds))): self.method_list.addItem(mtd, idx) def clear_fig(self): cear_plot_fig() def new_fig(self): new_fig_mpl() def plot_fig(self): #if self.target_list.currentData() != None: method = self.method_list.currentData() target = self.target_list.currentText() if method != None and 'Exponential Mean' != self.method_list.currentText() and 'Mean' != self.method_list.currentText() \ and 'Linear Reduction Mean'!= self.method_list.currentText() : if self.new_window.isChecked(): plot_remote(target, method, self.invert_opt.isChecked()) plot_remote_show() else: plot_remote_v2(target, method, self.invert_opt.isChecked()) plot_remote_show() if self.method_list.currentText() == 'Mean': get_mean_roc(target, self.invert_opt.isChecked(), self.weighted.isChecked()) plot_remote_show() if self.method_list.currentText() == 'Exponential Mean': get_mean_exp_roc(target, self.invert_opt.isChecked(), float(self.exp_value.text()), self.weighted.isChecked()) plot_remote_show() if 'Linear Reduction Mean' == self.method_list.currentText(): #get_mean_exp_roc_v2(target, self.invert_opt.isChecked(), float(self.exp_value.text())) #plot_remote_show() calculate_enrichment_factors() def selectionchange(self, i): print("Items in the list are :") self.populate_methods()
class FreedsonAdult1998Factory(BaseAlgorithmFactory): config_preset_input = QComboBox config_sedentary_input = QSpinBox config_light_input = QSpinBox config_moderate_input = QSpinBox config_vigorous_input = QSpinBox def __init__(self): super().__init__() def create(self, params: dict): # Create instance of algorithm return FreedsonAdult1998(params) def params(self): return { 'sedentary_cutoff': self.config_sedentary_input.value(), 'light_cutoff': self.config_light_input.value(), 'moderate_cutoff': self.config_moderate_input.value(), 'vigorous_cutoff': self.config_vigorous_input.value() } def name(self): return 'Freedson Adult 1998' def unique_id(self): return 1 def info(self): my_info = { 'description': """ \ It is a uniaxial accelerometer that assesses accelerations ranging from 0.05-2.0 G and is band limited with a frequency response from 0.25-2.5 Hz. The acceleration signal is filtered by an analog bandpass filter and digitized by an 8 bit A/D converter at a sampling rate of 10 samples per second. Each digitized signal is summed over a user specified time interval (epoch), and at the end of each epoch the activity count is stored internally and the accumulator is reset to zero. In the current study, a 60-s epoch was used and activity counts were expressed as the average counts per minute over the 6 min of exercise. Cut points (intensity buckets): * https://actigraph.desk.com/customer/portal/articles/2515802 Counts (accelerator sum over 60 s) * https://actigraph.desk.com/customer/portal/articles/2515580-What-are-counts- Notes: --> Only Y axis used on Actigraph devices. 8 bits = 256 = 2g epoch = 60 seconds """, 'name': self.name(), 'author': 'Dominic Létourneau', 'version': '0.1', 'reference': ("Freedson PS1, Melanson E, Sirard J., Calibration of the Computer Science and " "Applications, Inc. accelerometer., Med Sci Sports Exerc. 1998 May;30(5):777-81" ), 'unique_id': self.unique_id() } return my_info def required_sensors(self): return [SensorType.ACCELEROMETER] def build_config_widget(self, parent_widget: QWidget, default_params: dict = None): # Initialize inputs self.config_preset_input = QComboBox() # self.config_preset_input.addItem('') self.config_preset_input.addItem('Valeurs originales', [99, 1951, 5724, 9498]) self.config_preset_input.addItem('Personnalisées', [-1, -1, -1, -1]) # self.config_preset_input.addItem('Child', [99, 573, 1002, 0]) self.config_preset_input.currentIndexChanged.connect( self.config_preset_changed) base_layout = QVBoxLayout() preset_frame = QFrame() preset_frame.setStyleSheet( 'QFrame{background-color: rgba(200,200,200,50%);}' 'QLabel{background-color: rgba(0,0,0,0%);}') preset_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) frame_layout = QGridLayout() item_label = QLabel('Preset') frame_layout.addWidget(item_label, 0, 0) frame_layout.addWidget(self.config_preset_input, 0, 1) # frame_layout.addRow('Preset', self.config_preset_input) preset_frame.setLayout(frame_layout) base_layout.addWidget(preset_frame) layout = QGridLayout() layout.setAlignment(Qt.AlignTop) self.config_sedentary_input = QSpinBox() self.config_sedentary_input.setRange(0, 15000) item_label = QLabel('Cut-off Sedentary') layout.addWidget(item_label, 0, 0) layout.addWidget(self.config_sedentary_input, 0, 1) # layout.addRow("Cut-off Sedentary", self.config_sedentary_input) self.config_light_input = QSpinBox() self.config_light_input.setRange(0, 15000) item_label = QLabel('Cut-off Light') layout.addWidget(item_label, 1, 0) layout.addWidget(self.config_light_input, 1, 1) # layout.addRow("Cut-off Light", self.config_light_input) self.config_moderate_input = QSpinBox() self.config_moderate_input.setRange(0, 15000) item_label = QLabel('Cut-off Moderate') layout.addWidget(item_label, 2, 0) layout.addWidget(self.config_moderate_input, 2, 1) # layout.addRow("Cut-off Moderate", self.config_moderate_input) self.config_vigorous_input = QSpinBox() self.config_vigorous_input.setRange(0, 15000) item_label = QLabel('Cut-off Vigorous') layout.addWidget(item_label, 3, 0) layout.addWidget(self.config_vigorous_input, 3, 1) # layout.addRow("Cut-off Vigorous", self.config_vigorous_input) base_layout.addLayout(layout) base_widget = QWidget(parent_widget) base_widget.setLayout(base_layout) # Set default values if default_params is None: self.config_preset_changed() else: self.config_sedentary_input = default_params['sedentary_cutoff'] self.config_light_input = default_params['light_cutoff'] self.config_moderate_input = default_params['moderate_cutoff'] self.config_vigorous_input = default_params['vigorous_cutoff'] return base_widget def config_preset_changed(self): params = self.config_preset_input.currentData() if params is not None and len(params) == 4: if params[0] != -1: self.config_sedentary_input.setValue(params[0]) self.config_light_input.setValue(params[1]) self.config_moderate_input.setValue(params[2]) self.config_vigorous_input.setValue(params[3]) self.config_sedentary_input.setEnabled(params[0] == -1) self.config_light_input.setEnabled(params[0] == -1) self.config_moderate_input.setEnabled(params[0] == -1) self.config_vigorous_input.setEnabled(params[0] == -1) def build_display_widget(self, parent_widget: QWidget, results, recordsets): layout = QVBoxLayout() # Add Scroll area scroll = QScrollArea(parent=parent_widget) scroll.setLayout(layout) view = OpenIMUBarGraphView(scroll) view.set_title('Active minutes') layout.addWidget(view) for result in results: data = result['result'] view.set_category_axis(data.keys()) values = [] for key in data: values.append(data[key]) label = result['result_name'] view.add_set(label, values) # if len(results) == len(recordsets): # for i, _ in enumerate(results): # view.set_category_axis(results[i].keys()) # values = [] # # for key in results[i]: # values.append(results[i][key]) # # label = recordsets[i].name # view.add_set(label, values) # Update view view.update() return scroll def build_data_table(self, results): data_table = {} headers = [] data = [] data_names = [] # Results are stored in json, as a list of dict if isinstance(results, list): for result in results: if isinstance(result, dict): result_data = result['result'] result_name = result['result_name'] headers.append(result_name) if not data_names: data_names = list(result_data.keys()) data.append(list(result_data.values())) data_table = { 'headers': headers, 'data_names': data_names, 'data': data } return data_table
class ResultW(QFrame): """ Stores the result of a comparison that can be replayed at any time. Contains a QLabel, QPushButton (visualize) and QPushButton (copy to clipboard). """ template_button_pressed_signal = pyqtSignal() visualize_button_pressed_signal = pyqtSignal() def __init__(self, text, result, replays): super().__init__() self.result = result self.replays = replays self.label = QLabel(self) self.label.setText(text) self.visualize_button = QPushButton(self) self.visualize_button.setText("Visualize") self.visualize_button.clicked.connect( lambda: self.visualize_button_pressed_signal.emit()) if len(replays) == 1: self.set_layout_single() # at the moment, this only happens for replay stealing and when # visualizing multiple replays else: self.set_layout_multiple() def set_layout_single(self): self.actions_combobox = QComboBox() self.actions_combobox.addItem("More") self.actions_combobox.addItem("View Frametimes", "View Frametimes") self.actions_combobox.addItem("View Replay Data", "View Replay Data") self.actions_combobox.setInsertPolicy(QComboBox.NoInsert) self.actions_combobox.activated.connect(self.action_combobox_activated) layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.label, 0, 0, 1, 4) layout.addItem(SPACER, 0, 4, 1, 1) if isinstance(self.result, AnalysisResult): layout.addWidget(self.visualize_button, 0, 5, 1, 3) layout.addWidget(self.actions_combobox, 0, 8, 1, 1) else: template_button = self.new_template_button() layout.addWidget(self.visualize_button, 0, 5, 1, 2) layout.addWidget(template_button, 0, 7, 1, 1) layout.addWidget(self.actions_combobox, 0, 8, 1, 1) self.setLayout(layout) def set_layout_multiple(self): layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.label, 0, 0, 1, 1) layout.addItem(SPACER, 0, 1, 1, 1) if isinstance(self.result, AnalysisResult): layout.addWidget(self.visualize_button, 0, 2, 1, 2) else: template_button = self.new_template_button() layout.addWidget(self.visualize_button, 0, 2, 1, 1) layout.addWidget(template_button, 0, 3, 1, 1) self.setLayout(layout) def action_combobox_activated(self): if self.actions_combobox.currentData() == "View Frametimes": self.frametime_window = FrametimeWindow(self.result, self.replays[0]) self.frametime_window.show() if self.actions_combobox.currentData() == "View Replay Data": self.replay_data_window = ReplayDataWindow(self.replays[0]) self.replay_data_window.show() self.actions_combobox.setCurrentIndex(0) def new_template_button(self): template_button = QPushButton(self) template_button.setText("Copy Template") template_button.clicked.connect( lambda: self.template_button_pressed_signal.emit()) return template_button
class Main_Window(QMainWindow): def __init__(self): super(Main_Window, self).__init__() self.mtz_name = "" self.pdb_name = "" self.fcf_name = "" self.cif_name = "" self.saved_Data = {} self.resolution = 1.8 self.scaling_factor = 1 self.scatfact_table = 'electron' self.initUI() self.home() self.show() def initUI(self): self.setGeometry(300, 300, 600, 400) self.setFixedSize(self.size()) self.setWindowTitle('Plot_Fcalc_Fobs') self.setWindowIcon(QIcon('gear-tools.png')) self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) #Operations about MTZ file def load_mtz(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.mtz_name, _ = QFileDialog.getOpenFileName( self, "Choose an MTZ file", "", "MTZ Files (*.mtz);;All Files (*)", options=options) self.textbox1.setText(self.mtz_name) def mtz_text_editor_enter(self): tmp = self.textbox1.text() if os.path.isfile(tmp): self.mtz_name = tmp msgBox = QMessageBox() msgBox.setText("Successfully found MTZ file.") msgBox.setWindowTitle("MTZ File") msgBox.exec_() else: msgBox = QMessageBox() msgBox.setText("File does not exist, please enter another name") msgBox.setWindowTitle("File") msgBox.exec_() def read_mtz(self, f): #read an mtz file return any_reflection_file(str(f)) def show_mtz(self): try: subprocess.call(['viewhkl', str(self.mtz_name)]) except: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Critical) msgBox.setText( "An Error Has Ocurred While Trying To Show This File!") msgBox.setWindowTitle("Error") msgBox.exec_() #Operations about PDB file def load_pdb(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.pdb_name, _ = QFileDialog.getOpenFileName( self, "Choose a PDB file", "", "PDB Files (*.pdb);;All Files (*)", options=options) self.textbox2.setText(self.pdb_name) def pdb_text_editor_enter(self): tmp = self.textbox2.text() if os.path.isfile(tmp): self.pdb_name = tmp msgBox = QMessageBox() msgBox.setText("Successfully found PDB file.") msgBox.setWindowTitle("PDB File") else: msgBox = QMessageBox() msgBox.setText("File does not exist, please enter another name") msgBox.setWindowTitle("File") msgBox.exec_() def read_pdb(self, f): #read a pdb file try: if isinstance(f, str): structures = pdb.input( file_name=f, raise_sorry_if_format_error=True).xray_structure_simple() else: raise TypeError, 'read_pdb: Cannot deal is type {}'.format( type(f)) except libtbx.utils.Sorry as e: print e print "Error parsing pdb file, check if the data tag does not contain any spaces." exit() return structures def show_pdb(self): self.F_Window = File_Window(str(self.pdb_name)) #Operations about FCF file def load_fcf(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.fcf_name, _ = QFileDialog.getOpenFileName( self, "Choose an FCF file", "", "FCF Files (*.fcf);;All Files (*)", options=options) self.textbox3.setText(self.fcf_name) def fcf_text_editor_enter(self): tmp = self.textbox3.text() if os.path.isfile(tmp): self.fcf_name = tmp msgBox = QMessageBox() msgBox.setText("Successfully found FCF file.") msgBox.setWindowTitle("FCF File") else: msgBox = QMessageBox() msgBox.setText("File does not exist, please enter another name") msgBox.setWindowTitle("File") msgBox.exec_() def read_fcf(self, f): #read an fcf file return any_reflection_file(str(f)) def show_fcf(self): self.F_Window = File_Window(str(self.fcf_name)) #Operations about CIF file def load_cif(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.cif_name, _ = QFileDialog.getOpenFileName( self, "Choose a CIF file", "", "CIF Files (*.cif);;All Files (*)", options=options) self.textbox4.setText(self.cif_name) def cif_text_editor_enter(self): tmp = self.textbox4.text() if os.path.isfile(tmp): self.cif_name = tmp msgBox = QMessageBox() msgBox.setText("Successfully found CIF file.") msgBox.setWindowTitle("CIF File") else: msgBox = QMessageBox() msgBox.setText("File does not exist, please enter another name") msgBox.setWindowTitle("File") msgBox.exec_() def read_cif(self, f): #read a cif file One cif file can contain multiple structures try: if isinstance(f, str): structures = cif.reader( file_path=f, raise_if_errors=True).build_crystal_structures() else: raise TypeError, 'read_cif: Cannot deal is type {}'.format( type(f)) except libtbx.utils.Sorry as e: print e print "Error parsing cif file, check if the data tag does not contain any spaces." exit() return structures def read_cif_reflections(self, f): #read an cif reflection file return any_reflection_file(str(f)) def show_cif(self): self.F_Window = File_Window(str(self.cif_name)) #Set resolution def set_resolution(self): try: self.resolution = float(self.textbox5.text()) msgBox = QMessageBox() msgBox.setText("Successfully set the resolution to {}".format( self.resolution)) msgBox.setWindowTitle("Resolution") msgBox.exec_() except: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Critical) msgBox.setText( "An Error Has Ocurred while trying to set resolution! Please check if you input the proper number." ) msgBox.setWindowTitle("Error") msgBox.exec_() def set_wilson_scaling_factor(self): try: self.scaling_factor = float(self.textbox6.text()) msgBox = QMessageBox() msgBox.setText("Successfully set scaling factor to {}".format( self.scaling_factor)) msgBox.setWindowTitle("Scaling Factor") msgBox.exec_() except: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Critical) msgBox.setText( "An Error Has Ocurred while trying to set the wilson scaling factor! Please check if you input the proper number." ) msgBox.setWindowTitle("Error") msgBox.exec_() def save_fobs_fcalc(self): try: options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog save_name, _ = QFileDialog.getSaveFileName( self, "Save Fobs Fcalc", "", "JSON Files (*.json);;All Files (*)", options=options) with open(save_name, 'w') as fp: json.dump(self.saved_Data, fp, indent=4) except: msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Critical) msgBox.setText( "An Error Has Ocurred while trying to save fobs and fcalc!") msgBox.setWindowTitle("Error") msgBox.exec_() def f_calc_structure_factors(self, structure, **kwargs): """Takes cctbx structure and returns f_calc miller array Takes an optional options dictionary with keys: input: **kwargs: 'd_min': minimum d-spacing for structure factor calculation 'algorithm': which algorithm to use ('direct', 'fft', 'automatic') structure: <cctbx.xray.structure.structure object> output: f_calc: <cctbx.miller.array object> with calculated structure factors in the f_calc.data() function """ dmin = kwargs.get('dmin', 1.0) algorithm = kwargs.get('algorithm', "automatic") anomalous = kwargs.get('anomalous', False) table = kwargs.get('scatfact_table', 'wk1995') return_as = kwargs.get('return_as', "series") verbose = kwargs.get('verbose', False) if dmin <= 0.0: raise ValueError, "d-spacing must be greater than zero." if algorithm == "automatic": if structure.scatterers().size() <= 100: algorithm = "direct" else: algorithm = None structure.scattering_type_registry(table=table) f_calc_manager = structure.structure_factors(anomalous_flag=anomalous, d_min=dmin, algorithm=algorithm) f_calc = f_calc_manager.f_calc() if verbose: print "\nScattering table:", structure.scattering_type_registry_params.table structure.scattering_type_registry().show() print "Minimum d-spacing: %g" % f_calc.d_min() if return_as == "miller": return f_calc elif return_as == "series": fcalc = pd.Series(index=f_calc.indices(), data=np.abs(f_calc.data())) phase = pd.Series(index=f_calc.indices(), data=np.angle(f_calc.data())) return fcalc, phase elif return_as == "df": dffcal = pd.DataFrame(index=f_calc.index) dffcal['fcalc'] = np.abs(f_calc.data()) dffcal['phase'] = np.angle(f_calc.data()) return dffcal else: raise ValueError, "Unknown argument for 'return_as':{}".format( return_as) def calc_structure_factors(self, structures, dmin=1.0, table='electron', prefix='', verbose=True, **kwargs): """Wrapper around f_calc_structure_factors() Takes a structure object in which there is only one strcture dmin can be a dataframe and it will take the minimum dspacing (as specified by col 'd') or a float if combine is specified, function will return a dataframe combined with the given one, otherwise a dictionary of dataframes prefix is a prefix for the default names fcalc/phases to identify different structures """ fcalc = self.f_calc_structure_factors(structures,dmin=dmin,scatfact_table=table,\ return_as="miller",verbose=verbose,**kwargs) return fcalc def home(self): #quit button btn1 = QPushButton("Quit", self) btn1.clicked.connect(QtCore.QCoreApplication.instance().quit) btn1.resize(100, 50) btn1.move(490, 340) #plot button btn2 = QPushButton("Plot", self) btn2.clicked.connect(self.plot_Fcalc_Fobs) btn2.resize(100, 50) btn2.move(380, 340) #load mtz file button btn3 = QPushButton("Choose MTZ", self) btn3.clicked.connect(self.load_mtz) btn3.resize(100, 35) btn3.move(490, 5) #show the mtz file button btn3_1 = QPushButton("Show MTZ", self) btn3_1.clicked.connect(self.show_mtz) btn3_1.resize(100, 35) btn3_1.move(385, 5) #show the path of the mtz file self.textbox1 = QLineEdit(self) self.textbox1.resize(375, 35) self.textbox1.move(5, 5) self.textbox1.setText(self.mtz_name) self.textbox1.returnPressed.connect(self.mtz_text_editor_enter) #load pdb file button btn4 = QPushButton("Choose PDB", self) btn4.clicked.connect(self.load_pdb) btn4.resize(100, 35) btn4.move(490, 45) #show the pdb file button btn4_1 = QPushButton("Show PDB", self) btn4_1.clicked.connect(self.show_pdb) btn4_1.resize(100, 35) btn4_1.move(385, 45) #show the path of the pdb file self.textbox2 = QLineEdit(self) self.textbox2.resize(375, 35) self.textbox2.move(5, 45) self.textbox2.setText(self.pdb_name) self.textbox2.returnPressed.connect(self.pdb_text_editor_enter) #load fcf file button btn5 = QPushButton("Choose FCF", self) btn5.clicked.connect(self.load_fcf) btn5.resize(100, 35) btn5.move(490, 85) #show the fcf file button btn5_1 = QPushButton("Show FCF", self) btn5_1.clicked.connect(self.show_fcf) btn5_1.resize(100, 35) btn5_1.move(385, 85) #show the path of the fcf file self.textbox3 = QLineEdit(self) self.textbox3.resize(375, 35) self.textbox3.move(5, 85) self.textbox3.setText(self.fcf_name) self.textbox3.returnPressed.connect(self.fcf_text_editor_enter) #load cif file button btn6 = QPushButton("Choose CIF", self) btn6.clicked.connect(self.load_cif) btn6.resize(100, 35) btn6.move(490, 125) #show the cif file button btn6_1 = QPushButton("Show CIF", self) btn6_1.clicked.connect(self.show_cif) btn6_1.resize(100, 35) btn6_1.move(385, 125) #show the path of the cif file self.textbox4 = QLineEdit(self) self.textbox4.resize(375, 35) self.textbox4.move(5, 125) self.textbox4.setText(self.cif_name) self.textbox4.returnPressed.connect(self.cif_text_editor_enter) #checkbox to choose the way to calculate Fcalc #1 get Fcalc by calculating the structure factor from pdb file self.cb1 = QCheckBox('Calc Fcalc from PDB', self) self.cb1.move(10, 170) self.cb1.resize(145, 20) self.cb1.setAutoExclusive(1) #2 get Fcalc by calculating the structure factor from cif file self.cb2 = QCheckBox('Calc Fcalc from CIF', self) self.cb2.move(160, 170) self.cb2.resize(145, 20) self.cb2.setAutoExclusive(1) #3 get Fcalc from mtz file self.cb3 = QCheckBox('Get Fcalc from MTZ', self) self.cb3.move(305, 170) self.cb3.resize(145, 20) self.cb3.toggle() self.cb3.setAutoExclusive(1) #4 get Fcalc from fcf file self.cb4 = QCheckBox('Get Fcalc from FCF', self) self.cb4.move(455, 170) self.cb4.resize(145, 20) self.cb4.setAutoExclusive(1) #set a checkbox group to group the options for fobs self.cb_group_fcalc = QButtonGroup(self) self.cb_group_fcalc.addButton(self.cb1) self.cb_group_fcalc.addButton(self.cb2) self.cb_group_fcalc.addButton(self.cb3) self.cb_group_fcalc.addButton(self.cb4) self.cb_group_fcalc.setExclusive(1) #checkbox to choose the way to get Fobs #1 get Fobs from mtz file self.cb1_1 = QCheckBox('Get Fobs from MTZ', self) self.cb1_1.move(10, 200) self.cb1_1.resize(145, 20) self.cb1_1.toggle() #2 get Fobs from fcf file self.cb2_1 = QCheckBox('Get Fobs from FCF', self) self.cb2_1.move(160, 200) self.cb2_1.resize(145, 20) #3 get Fobs from CIF file self.cb3_1 = QCheckBox('Get Fobs from CIF', self) self.cb3_1.move(305, 200) self.cb3_1.resize(145, 20) #set a checkbox group to group the options for fobs self.cb_group_fobs = QButtonGroup(self) self.cb_group_fobs.addButton(self.cb1_1) self.cb_group_fobs.addButton(self.cb2_1) self.cb_group_fobs.addButton(self.cb3_1) self.cb_group_fobs.setExclusive(1) #input textbox to set resolution self.textbox5 = QLineEdit(self) self.textbox5.move(10, 230) self.textbox5.resize(60, 35) self.textbox5.setValidator( QDoubleValidator(self, bottom=0, top=500, decimals=2)) self.textbox5.setText(str(self.resolution)) btn7 = QPushButton("Set Resolution", self) btn7.move(75, 230) btn7.resize(100, 35) btn7.clicked.connect(self.set_resolution) #Overall wilson plot scaling factor self.textbox6 = QLineEdit(self) self.textbox6.move(10, 270) self.textbox6.resize(60, 36) self.textbox6.setValidator( QDoubleValidator(self, bottom=-500, top=500, decimals=4)) self.textbox6.setText(str(self.scaling_factor)) btn8 = QPushButton("Set Wilson\nFactor", self) btn8.move(75, 270) btn8.resize(100, 36) btn8.clicked.connect(self.set_wilson_scaling_factor) #Save the Fobs and Fcalc as text file btn9 = QPushButton("Save", self) btn9.move(270, 340) btn9.resize(100, 50) btn9.clicked.connect(self.save_fobs_fcalc) #Combobox to select scattering factors self.combo = QComboBox(self) self.combo.addItem('electron', 'electron') self.combo.addItem('xray-wk1995', 'wk1995') self.combo.addItem('xray-it1992', 'it1992') self.combo.move(185, 230) self.combo.resize(150, 35) def plot_Fcalc_Fobs(self): self.resolution = float(self.textbox5.text()) self.scaling_factor = float(self.textbox6.text()) #try: if self.cb1_1.checkState(): #1 get Fobs from mtz file mtz_file = self.read_mtz(self.textbox1.text()) Fobs = mtz_file.as_miller_arrays()[2] Fobs_ds = Fobs.d_spacings().data() Fobs_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fobs.indices()),\ data=np.array([Fobs.data()*self.scaling_factor,Fobs_ds]).transpose()) elif self.cb2_1.checkState(): #2 get Fobs from fcf file fcf_file = self.read_fcf(self.textbox3.text()) model = dict(fcf_file.file_content().model().items()[0][1]) if model['_shelx_refln_list_code'] is '6': Fobs = fcf_file.as_miller_arrays( )[1] #This Fobs is Fobs^2 but in order to be more convinient for me just call it Fobs Fobs_ds = Fobs.d_spacings().data() Fobs_data = np.array(Fobs.data()) Fobs_data[Fobs_data < 0] = 0 Fobs_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fobs.indices()),\ data=np.array([np.sqrt(Fobs_data)*self.scaling_factor,Fobs_ds]).transpose()) elif model['_shelx_refln_list_code'] is '4': Fobs = fcf_file.as_miller_arrays( )[1] #This Fobs is Fobs^2 but in order to be more convinient for me just call it Fobs Fobs_ds = Fobs.d_spacings().data() Fobs_data = np.array(Fobs.data()) Fobs_data[Fobs_data < 0] = 0 Fobs_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fobs.indices()),\ data=np.array([np.sqrt(Fobs_data)*self.scaling_factor,Fobs_ds]).transpose()) elif model['_shelx_refln_list_code'] is '3': Fobs = fcf_file.as_miller_arrays()[1] Fobs_ds = Fobs.d_spacings().data() Fobs_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fobs.indices()),\ data=np.array([Fobs.data()*self.scaling_factor,Fobs_ds]).transpose()) elif self.cb3_1.checkState(): cif_file = self.read_cif_reflections(str(self.textbox4.text())) Fobs = cif_file.as_miller_arrays()[0] if type(Fobs.data()[0]) is complex: Fobs = cif_file.as_miller_arrays()[1] Fobs_ds = Fobs.d_spacings().data() Fobs_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fobs.indices()),\ data=np.array([Fobs.data()*self.scaling_factor,Fobs_ds]).transpose()) #------------------------------------------------------------------------------- if self.cb1.checkState( ): #1 get Fcalc by calculating the structure factor from pdb file self.scatfact_table = str(self.combo.currentData()) pdb_structure = self.read_pdb(str(self.textbox2.text())) Fcalc = self.calc_structure_factors(pdb_structure, dmin=self.resolution, table=self.scatfact_table) Fcalc_data = Fcalc.data() Fcalc_indices = Fcalc.indices() Fcalc_ds = Fcalc.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc_indices),\ data=np.array([np.abs(Fcalc_data),Fcalc_ds]).transpose()) elif self.cb2.checkState( ): #2 get Fcalc by calculating the structure factor from cif file self.scatfact_table = str(self.combo.currentData()) cif_structures = self.read_cif(str(self.textbox4.text())) for name, cif_structure in cif_structures.items(): Fcalc = self.calc_structure_factors(cif_structure, dmin=self.resolution, table=self.scatfact_table) break #abandon any more structures in the cif file, if there is any, only read the first one Fcalc_P1 = Fcalc.expand_to_p1() Fcalc_data = Fcalc_P1.data() Fcalc_indices = Fcalc_P1.indices() Fcalc_ds = Fcalc_P1.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc_indices),\ data=np.array([np.abs(Fcalc_data),Fcalc_ds]).transpose()) elif self.cb3.checkState(): #3 get Fcalc from mtz file if not self.cb1_1.checkState(): mtz_file = self.read_mtz(self.textbox1.text()) Fcalc = mtz_file.as_miller_arrays()[3] Fcalc_ds = Fcalc.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc.indices()),\ data=np.array([np.abs(Fcalc.data()),Fcalc_ds]).transpose()) elif self.cb4.checkState(): #4 get Fcalc from fcf file if not self.cb2_1.checkState(): fcf_file = self.read_fcf(self.textbox3.text()) model = dict(fcf_file.file_content().model().items()[0][1]) if model['_shelx_refln_list_code'] is '6': Fcalc = fcf_file.as_miller_arrays()[0] Fcalc_ds = Fcalc.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc.indices()),\ data=np.array([np.abs(Fcalc.data()),Fcalc_ds]).transpose()) elif model['_shelx_refln_list_code'] is '4': Fcalc = fcf_file.as_miller_arrays()[0] Fcalc_ds = Fcalc.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc.indices()),\ data=np.array([np.sqrt(Fcalc.data()),Fcalc_ds]).transpose()) elif model['_shelx_refln_list_code'] is '3': Fcalc = fcf_file.as_miller_arrays()[0] Fcalc_ds = Fcalc.d_spacings().data() Fcalc_DF = pd.DataFrame(index=pd.MultiIndex.from_tuples(Fcalc.indices()),\ data=np.array([np.abs(Fcalc.data()),Fcalc_ds]).transpose()) #------------------------------------------------------------------------------- merged_DF = Fobs_DF.merge(Fcalc_DF, how='inner', left_index=True, right_index=True, suffixes=('_Fobs', '_Fcalc')) merged_fobs = merged_DF['0_Fobs'].values.tolist() merged_fcalc = merged_DF['0_Fcalc'].values.tolist() fig, ax = plt.subplots() x2, y2 = pd.Series(merged_fobs, name="F_obs"), pd.Series(merged_fcalc, name="F_model") self.saved_Data['Fobs'] = merged_fobs self.saved_Data['Fcalc'] = merged_fcalc index = merged_DF.index.tolist() ds = merged_DF['1_Fobs'].values.tolist() af = AnnoteFinder(merged_fobs, merged_fcalc, zip(index, ds), ax=ax) fig.canvas.mpl_connect('button_press_event', af) self.fit_window = FitWindow(ax=ax, data=zip(merged_fobs, merged_fcalc)) sns.regplot(x=x2, y=y2, x_ci=None, ci=None, marker='+', ax=ax) plt.show()
class FlowBoardEditorToolBar(QToolBar): makeNewBoard = pyqtSignal(int) # argument: board size toolChanged = pyqtSignal() def __init__(self): super(FlowBoardEditorToolBar, self).__init__() self.setSizePolicy( QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) boardbox = QBoxLayout(QBoxLayout.TopToBottom) boardbox.setSpacing(2) self._sizelist = QComboBox() for s in range(5, 16): self._sizelist.addItem("{0}x{0}".format(s), s) self._sizelist.setCurrentIndex(2) boardbox.addWidget(self._sizelist) self._sizelist.currentIndexChanged.connect(self._sizelistChanged) self._clearbutton = QPushButton("clear") boardbox.addWidget(self._clearbutton) self._clearbutton.clicked.connect(self._clearClicked) boardboxwidget = QWidget() boardboxwidget.setLayout(boardbox) self.addWidget(boardboxwidget) self._toolchooser = FlowToolChooser() self.addWidget(self._toolchooser) self._toolchooser.changed.connect(self._toolChanged) @property def selectedSize(self): return self._sizelist.currentData() @property def selectedEndpointKey(self): t = self._toolchooser.selected return t.endpointKey if isinstance(t, FlowToolEndpoint) else None def selectSize(self, size): if size != self.selectedSize: i = self._sizelist.findData(size) if i >= 0: self._sizelist.setCurrentIndex(i) @property def tools(self): return self._toolchooser @pyqtSlot(FlowBoard) def updateBoard(self, board): self._clearbutton.setEnabled(not board.isEmpty()) ek = self.selectedEndpointKey if ek is not None and board.hasCompleteEndpoints(ek): self._toolchooser.selectFirstOpenEndpoint(board) @pyqtSlot(int) def _sizelistChanged(self, _): self.makeNewBoard.emit(self.selectedSize) @pyqtSlot(bool) def _clearClicked(self, _): self.makeNewBoard.emit(self.selectedSize) @pyqtSlot() def _toolChanged(self): self.toolChanged.emit()
class AiEditorWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self._setup() edited = pyqtSignal() def _setup(self): self.tabs = QTabWidget(self) general = QWidget(self.tabs) layout = QFormLayout() general.setLayout(layout) def make_spinbox(title, low, high, tab): widget = QSpinBox(tab) widget.setRange(low, high) widget.valueChanged.connect(self.edited) tab.layout().addRow(title, widget) return widget self.title = MandatoryField(_("Title"), QLineEdit(general)) self.title.add_to_form(layout) self.title.widget.editingFinished.connect(self.edited) self.depth = make_spinbox(_("Default depth (half-steps)"), 0, 20, general) self.start_depth = make_spinbox(_("Minimum depth"), 0, 20, general) self.max_combination_depth = make_spinbox(_("Forced mode depth"), 0, 24, general) self.dynamic_depth = make_spinbox(_("Static search mode threshold"), 0, 24, general) self.deeper_if_bad = QCheckBox(general) self.deeper_if_bad.stateChanged.connect(self.edited) layout.addRow(_("Think better if situation seem bad"), self.deeper_if_bad) self.moves_bound_low = make_spinbox(_("`Few moves' mode bound"), 1, 5, general) self.moves_bound_high = make_spinbox(_("`Too many moves' mode bound"), 5, 50, general) self.use_positional_score = QCheckBox(general) layout.addRow(_("Use positional score"), self.use_positional_score) self.use_positional_score.stateChanged.connect(self.edited) self.use_timeout = QCheckBox(general) layout.addRow(_("Continue thinking while there is time"), self.use_timeout) self.use_timeout.stateChanged.connect(self.edited) self.use_timeout.stateChanged.connect(self._on_use_timeout) self.timeout = make_spinbox(_("Timeout (seconds)"), 1, 120, general) self.timeout.setEnabled(False) self.random_opening_depth = make_spinbox(_("Random opening depth"), 1, 5, general) self.random_opening_depth.setToolTip( _("Number of first moves to be considered as opening; during these moves, AI will select it's move randomly from several best options" )) self.random_opening_options = make_spinbox(_("Random opening options"), 1, 5, general) self.random_opening_options.setToolTip( _("From how many best options to select during the opening")) self.accept_draw = QComboBox(self) self.accept_draw.addItem(_("Always accept"), ALWAYS_ACCEPT) self.accept_draw.addItem(_("Always decline"), ALWAYS_DECLINE) self.accept_draw.addItem(_("Accept if AI is losing"), ACCEPT_IF_LOSING) self.accept_draw.currentIndexChanged.connect(self.edited) layout.addRow(_("Accept draws"), self.accept_draw) self.tabs.addTab(general, _("General")) evaluator = QWidget(self.tabs) layout = QFormLayout() evaluator.setLayout(layout) self.mobility_weight = make_spinbox(_("Mobility"), -500, 500, evaluator) self.backyard_weight = make_spinbox(_("Back row"), -100, 100, evaluator) self.center_weight = make_spinbox(_("Center"), -100, 100, evaluator) self.opposite_side_weight = make_spinbox(_("Opposite side"), -100, 100, evaluator) self.backed_weight = make_spinbox(_("Backed"), -100, 100, evaluator) self.asymetry_weight = make_spinbox(_("Asymetry"), -100, 100, evaluator) self.pre_king_weight = make_spinbox(_("Pre-king"), 1, 100, evaluator) self.king_coef = make_spinbox(_("King"), 1, 100, evaluator) self.attacked_man_coef = make_spinbox(_("Attacked man"), -300, 300, evaluator) self.attacked_king_coef = make_spinbox(_("Attacked king"), -300, 300, evaluator) self.tabs.addTab(evaluator, _("Board evaluation")) extra = QWidget(self.tabs) layout = QVBoxLayout() extra.setLayout(layout) self.extra = QTextEdit(general) self.extra.textChanged.connect(self.edited) layout.addWidget(self.extra) self.tabs.addTab(extra, _("Extra options")) layout = QVBoxLayout() layout.addWidget(self.tabs) hbox = QHBoxLayout() save = QPushButton(_("Save..."), self) save.setIcon(QIcon.fromTheme("document-save")) save.setToolTip(_("Save AI settings to JSON file")) save.clicked.connect(self._on_save) hbox.addWidget(save) load = QPushButton(_("Load..."), self) load.setIcon(QIcon.fromTheme("document-open")) load.setToolTip(_("Load AI settings from JSON file")) load.clicked.connect(self._on_load) hbox.addWidget(load) layout.addLayout(hbox) self.setLayout(layout) def _on_use_timeout(self): use = self.use_timeout.checkState() == Qt.Checked self.timeout.setEnabled(use) def _on_save(self): path, mask = QFileDialog.getSaveFileName(self, _("Save file"), ".", JSON_MASK) if path: ai = self.get_ai() json_data = ai.params() with open(path, 'w') as f: f.write(json.dumps(json_data)) def _on_load(self): path, mask = QFileDialog.getOpenFileName(self, _("Load file"), ".", JSON_MASK) if path: try: with open(path) as f: text = f.read() json_data = json.loads(text) ai = AI() ai.title = self.get_ai().title ai.load_json(json_data) self.set_ai(ai) except Exception as e: logging.exception(e) def set_ai(self, ai): self.title.widget.setText(ai.title) self.depth.setValue(ai.depth) if ai.start_depth is not None: self.start_depth.setValue(ai.start_depth) self.max_combination_depth.setValue(ai.max_combination_depth) self.dynamic_depth.setValue(ai.dynamic_depth) self.deeper_if_bad.setCheckState( Qt.Checked if ai.deeper_if_bad else Qt.Unchecked) self.moves_bound_low.setValue(ai.moves_bound_low) self.moves_bound_high.setValue(ai.moves_bound_high) self.use_positional_score.setCheckState( Qt.Checked if ai.use_positional_score else Qt.Unchecked) self.use_timeout.setCheckState( Qt.Checked if ai.use_timeout else Qt.Unchecked) self.timeout.setValue(1 if ai.timeout is None else ai.timeout) self.random_opening_depth.setValue(ai.random_opening_depth) self.random_opening_options.setValue(ai.random_opening_options) self.mobility_weight.setValue(ai.mobility_weight) self.backyard_weight.setValue(ai.backyard_weight) self.center_weight.setValue(ai.center_weight) self.opposite_side_weight.setValue(ai.opposite_side_weight) self.backed_weight.setValue(ai.backed_weight) self.asymetry_weight.setValue(ai.asymetry_weight) self.pre_king_weight.setValue(ai.pre_king_weight) self.king_coef.setValue(ai.king_coef) self.attacked_man_coef.setValue(-ai.attacked_man_coef) self.attacked_king_coef.setValue(-ai.attacked_king_coef) policy = ai.accept_draw policy_idx = self.accept_draw.findData(policy) self.accept_draw.setCurrentIndex(policy_idx) self.extra.setText("" if ai.extra is None else ai.extra) def get_ai(self): ai = AI() ai.title = self.title.widget.text() ai.depth = self.depth.value() ai.start_depth = self.start_depth.value() ai.max_combination_depth = self.max_combination_depth.value() ai.dynamic_depth = self.dynamic_depth.value() ai.deeper_if_bad = self.deeper_if_bad.checkState() == Qt.Checked ai.moves_bound_low = self.moves_bound_low.value() ai.moves_bound_high = self.moves_bound_high.value() ai.use_positional_score = self.use_positional_score.checkState( ) == Qt.Checked ai.use_timeout = self.use_timeout.checkState() == Qt.Checked ai.timeout = self.timeout.value() ai.random_opening_depth = self.random_opening_depth.value() ai.random_opening_options = self.random_opening_options.value() ai.mobility_weight = self.mobility_weight.value() ai.backyard_weight = self.backyard_weight.value() ai.center_weight = self.center_weight.value() ai.opposite_side_weight = self.opposite_side_weight.value() ai.backed_weight = self.backed_weight.value() ai.asymetry_weight = self.asymetry_weight.value() ai.pre_king_weight = self.pre_king_weight.value() ai.king_coef = self.king_coef.value() ai.attacked_man_coef = -self.attacked_man_coef.value() ai.attacked_king_coef = -self.attacked_king_coef.value() ai.accept_draw = self.accept_draw.currentData() ai.extra = self.extra.toPlainText() return ai
class GeneralPage(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.server_url = MandatoryField(_("Server URL"), QLineEdit(self)) self.server_url.add_to_form(layout) self.use_local_server = QCheckBox(self) layout.addRow(_("Use local server"), self.use_local_server) self.local_server_path = MandatoryField( _("Local server start command"), QLineEdit(self)) self.local_server_path.add_to_form(layout) self.use_local_server.stateChanged.connect(self._on_use_local_server) self.proxy_usage = QComboBox(self) self.proxy_usage.addItem(_("Use system settings"), PROXY_SYSTEM) self.proxy_usage.addItem(_("Do not use proxy"), PROXY_NONE) self.proxy_usage.addItem(_("Manual configuration"), PROXY_CUSTOM) layout.addRow(_("Proxy usage"), self.proxy_usage) self.proxy_usage.currentIndexChanged.connect(self._on_use_proxy) self.proxy_address = QLineEdit(self) layout.addRow(_("Proxy"), self.proxy_address) self.proxy_address.setEnabled(False) self.log_level = QComboBox(self) self.log_level.addItem(_("Debug"), logging.DEBUG) self.log_level.addItem(_("Information"), logging.INFO) self.log_level.addItem(_("Warning"), logging.WARN) self.log_level.addItem(_("Error"), logging.ERROR) layout.addRow(_("Logging level"), self.log_level) self.setLayout(layout) def _on_use_local_server(self): use = self.use_local_server.checkState() == Qt.Checked self.local_server_path.widget.setEnabled(use) self.local_server_path.is_mandatory = use def _on_use_proxy(self): usage = self.proxy_usage.currentData() self.proxy_address.setEnabled(usage == PROXY_CUSTOM) def load(self, settings): url = settings.value("server_url", DEFAULT_SERVER_URL) self.server_url.widget.setText(url) use_local_server = settings.value("use_local_server", type=bool) self.use_local_server.setCheckState( Qt.Checked if use_local_server else Qt.Unchecked) path = settings.value("local_server_path", "hcheckersd --local") self.local_server_path.widget.setText(path) self.local_server_path.widget.setEnabled(use_local_server) self.local_server_path.widget.is_mandatory = use_local_server level = settings.value("log_level", logging.INFO, type=int) level_idx = self.log_level.findData(level) self.log_level.setCurrentIndex(level_idx) proxy_usage = settings.value("proxy_usage", PROXY_SYSTEM, type=int) self.proxy_usage.setCurrentIndex(proxy_usage) proxy = settings.value("proxy_address", EXAMPLE_PROXY) self.proxy_address.setText(proxy) def save(self, settings): settings.setValue("server_url", self.server_url.widget.text()) use_local_server = self.use_local_server.checkState() == Qt.Checked settings.setValue("use_local_server", use_local_server) settings.setValue("local_server_path", self.local_server_path.widget.text()) level = self.log_level.currentData() settings.setValue("log_level", level) settings.setValue("proxy_usage", self.proxy_usage.currentData()) settings.setValue("proxy_address", self.proxy_address.text())
class ViewSettingsPage(QWidget): def __init__(self, share_dir, parent=None): QWidget.__init__(self, parent) layout = QFormLayout() self.show_notation = QCheckBox(self) layout.addRow(_("Show fields notation on the board"), self.show_notation) self.show_border = QCheckBox(self) layout.addRow(_("Show board borders with notation"), self.show_border) self.show_possible_moves = QCheckBox(self) layout.addRow(_("Show possible moves"), self.show_possible_moves) self.theme = QComboBox(self) self.themes = dict() for theme in Theme.list_themes(share_dir): self.themes[theme.id] = theme self.theme.addItem(theme.name, theme.id) layout.addRow(_("Theme"), self.theme) self.enable_sound = QCheckBox(self) layout.addRow(_("Enable sounds"), self.enable_sound) self.setLayout(layout) def get_theme(self): theme_name = self.theme.currentData() return self.themes[theme_name] def get_enable_sound(self): return self.enable_sound.checkState() == Qt.Checked def load(self, settings): show_notation = settings.value("show_notation", type=bool) self.show_notation.setCheckState( Qt.Checked if show_notation else Qt.Unchecked) show_border = settings.value("show_border", False, type=bool) self.show_border.setCheckState( Qt.Checked if show_border else Qt.Unchecked) show_possible_moves = settings.value("show_possible_moves", type=bool) self.show_possible_moves.setCheckState( Qt.Checked if show_possible_moves else Qt.Unchecked) theme = settings.value("theme") theme_idx = self.theme.findData(theme) if theme_idx < 0: theme_idx = self.theme.findText("default") if theme_idx >= 0: self.theme.setCurrentIndex(theme_idx) enable_sound = settings.value("enable_sound", type=bool) self.enable_sound.setCheckState( Qt.Checked if enable_sound else Qt.Unchecked) def save(self, settings): settings.setValue("show_notation", self.show_notation.checkState() == Qt.Checked) settings.setValue("show_border", self.show_border.checkState() == Qt.Checked) settings.setValue("show_possible_moves", self.show_possible_moves.checkState() == Qt.Checked) settings.setValue("theme", self.theme.currentData()) settings.setValue("enable_sound", self.get_enable_sound())
class Settings_Net(QWidget): def __init__(self, parent: QWidget): super(Settings_Net, self).__init__(parent) self._layout = QVBoxLayout() self.setLayout(self._layout) # construct layout min_width = 150 self.user_agents = dict() self.user_agents['chrome_win7_x64'] = ( 'Chrome 41, Windows 7 x64', 'Mozilla/5.0 (Windows NT 6.1; WOW64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/41.0.2227.0 Safari/537.36') self.user_agents['chrome_linux_64'] = ( 'Chrome 41, Linux x86_64', 'Mozilla/5.0 (X11; Linux x86_64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/41.0.2227.0 Safari/537.36') self.user_agents['chrome_android'] = ( 'Chrome 47, Android 4.3 Galaxy-S3', 'Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/47.0.2526.83 Mobile Safari/537.36') self.user_agents['firefox_win32'] = ( 'Firefox 40, Windows 7 32-bit', 'Mozilla/5.0 (Windows NT 6.1; rv:40.0) ' 'Gecko/20100101 Firefox/40.1') self.user_agents['firefox_android'] = ( 'Firefox, Android 4.3 Galaxy-S3', 'Mozilla/5.0 (Android 4.3; Mobile; rv:43.0) ' 'Gecko/43.0 Firefox/43.0') self.user_agents['edge_win10'] = ( 'Microsoft Edge, Windows 10 x64', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/42.0.2311.135 Safari/537.36 Edge/12.246') # server URL self._l_surl = QHBoxLayout() self._l_ua = QHBoxLayout() self._lbl_surl = QLabel(self.tr('Server URL:'), self) self._lbl_surl.setMinimumWidth(min_width) self._le_surl = QLineEdit(self) self._l_surl.addWidget(self._lbl_surl) self._l_surl.addWidget(self._le_surl) self._layout.addLayout(self._l_surl) # emulate browser combo box self._l_eb = QHBoxLayout() self._lbl_eb = QLabel(self.tr('Emulate browser:'), self) self._lbl_eb.setMinimumWidth(min_width) self._cb_eb = QComboBox(self) self._cb_eb.setEditable(False) self._cb_eb.setInsertPolicy(QComboBox.InsertAtBottom) ua_keylist = [i for i in self.user_agents.keys()] ua_keylist.sort() for key_id in ua_keylist: b_tuple = self.user_agents[key_id] display_string = b_tuple[0] self._cb_eb.addItem(display_string, QVariant(str(key_id))) self._cb_eb.addItem(self.tr('<Custom>'), QVariant('custom')) self._l_eb.addWidget(self._lbl_eb) self._l_eb.addWidget(self._cb_eb) self._layout.addLayout(self._l_eb) # custom user-agent string self._lbl_ua = QLabel(self.tr('User-agent string:'), self) self._lbl_ua.setMinimumWidth(min_width) self._le_ua = QLineEdit(self) self._l_ua.addWidget(self._lbl_ua) self._l_ua.addWidget(self._le_ua) self._layout.addLayout(self._l_ua) # proxy settings self._l_proxy = QHBoxLayout() self._lbl_proxy = QLabel(self.tr('Proxy type:'), self) self._lbl_proxy.setMinimumWidth(min_width) self._cb_proxy = QComboBox(self) self._cb_proxy.setEditable(False) self._cb_proxy.addItem(self.tr('No proxy'), QVariant('none')) self._cb_proxy.addItem(self.tr('HTTP proxy'), QVariant('http')) self._cb_proxy.addItem(self.tr('SOCKS5 proxy'), QVariant('socks5')) self._l_proxy.addWidget(self._lbl_proxy) self._l_proxy.addWidget(self._cb_proxy) self._layout.addLayout(self._l_proxy) self._l_proxy_s = QHBoxLayout() self._lbl_proxy_s = QLabel(self.tr('Proxy addr:port:'), self) self._lbl_proxy_s.setMinimumWidth(min_width) self._le_proxy_addr = QLineEdit(self) self._l_proxy_s.addWidget(self._lbl_proxy_s) self._l_proxy_s.addWidget(self._le_proxy_addr) self._layout.addLayout(self._l_proxy_s) # all connections self._cb_eb.currentIndexChanged.connect( self.on_cb_eb_current_index_changed) self._cb_proxy.currentIndexChanged.connect( self.on_cb_proxy_current_index_changed) # finalize self._layout.addStretch() @pyqtSlot(int) def on_cb_eb_current_index_changed(self, index: int): key_id = str(self._cb_eb.currentData(Qt.UserRole)) if key_id == 'custom': self._le_ua.setEnabled(True) return self._le_ua.setEnabled(False) if key_id in self.user_agents: b_tuple = self.user_agents[key_id] ua_str = b_tuple[1] self._le_ua.setText(ua_str) @pyqtSlot(int) def on_cb_proxy_current_index_changed(self, index: int): if index == 0: self._le_proxy_addr.setEnabled(False) else: self._le_proxy_addr.setEnabled(True) def ua_select(self, key_id: str): cnt = self._cb_eb.count() for i in range(cnt): item_key_id = str(self._cb_eb.itemData(i, Qt.UserRole)) if item_key_id == key_id: self._cb_eb.setCurrentIndex(i) break def load_from_config(self, cfg: configparser.ConfigParser): # defaults xnova_url = 'uni4.xnova.su' user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0' user_agent_id = 'custom' proxy = '' if 'net' in cfg: xnova_url = cfg['net']['xnova_url'] user_agent = cfg['net']['user_agent'] user_agent_id = cfg['net']['user_agent_id'] proxy = cfg['net']['proxy'] self._le_surl.setText(xnova_url) self._le_surl.setEnabled( False) # cannot be edited by user, for safety! # deal with user-agent self._le_ua.setText(user_agent) if user_agent_id == 'custom': self._le_ua.setEnabled(True) else: self._le_ua.setEnabled(False) self.ua_select(user_agent_id) # deal with proxy if proxy == '': self._le_proxy_addr.setText('') self._cb_proxy.setCurrentIndex(0) self._le_proxy_addr.setEnabled(False) elif proxy.startswith('http://'): self._cb_proxy.setCurrentIndex(1) proxy_addr = proxy[7:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) elif proxy.startswith('socks5://'): self._cb_proxy.setCurrentIndex(2) proxy_addr = proxy[9:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) else: raise ValueError('Invalid proxy setting: ' + proxy) def save_to_config(self, cfg: configparser.ConfigParser): # ensure there is a 'net' section if 'net' not in cfg: cfg.add_section('net') # skip server url # deal with user-agent user_agent_id = '' user_agent = '' idx = self._cb_eb.currentIndex() if idx >= 0: user_agent_id = str(self._cb_eb.itemData(idx, Qt.UserRole)) cfg['net']['user_agent_id'] = user_agent_id user_agent = self._le_ua.text().strip() if user_agent != '': cfg['net']['user_agent'] = user_agent # deal with proxy idx = self._cb_proxy.currentIndex() proxy_addr = self._le_proxy_addr.text().strip() if idx == 0: cfg['net']['proxy'] = '' elif idx == 1: cfg['net']['proxy'] = 'http://' + proxy_addr elif idx == 2: cfg['net']['proxy'] = 'socks5://' + proxy_addr logger.debug('Saved network config')
class SettingsWindow(QDialog): """ Class describing the application settings window. """ def __init__(self, parent: QWidget = None): super().__init__(parent) # window settings self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) self.setWindowTitle(self.tr("Settings window")) # language self.group_box_lang = QGroupBox(self.tr("Language")) self.form_layout_lang = QFormLayout(self.group_box_lang) self.label_app_lang = QLabel(self.tr("Application")) self.form_layout_lang.setWidget(0, QFormLayout.LabelRole, self.label_app_lang) self.combo_box_app_lang = QComboBox() self.form_layout_lang.setWidget(0, QFormLayout.FieldRole, self.combo_box_app_lang) self.label_sch_lang = QLabel(self.tr("Schedule")) self.form_layout_lang.setWidget(1, QFormLayout.LabelRole, self.label_sch_lang) self.combo_box_sch_lang = QComboBox() self.form_layout_lang.setWidget(1, QFormLayout.FieldRole, self.combo_box_sch_lang) languages = [("English", "en_US"), ("Русский", "ru_RU")] for lang_name, lang_code in languages: self.combo_box_app_lang.addItem(lang_name, lang_code) self.combo_box_sch_lang.addItem(lang_name, lang_code) if Settings.ApplicationLang == lang_code: self.combo_box_app_lang.setCurrentText(lang_name) if Settings.ScheduleLang == lang_code: self.combo_box_sch_lang.setCurrentText(lang_name) # schedule self.group_box_schedule = QGroupBox(self.tr("Schedule")) self.form_layout_schedule = QFormLayout(self.group_box_schedule) self.label_short_name = QLabel(self.tr("Short name")) self.form_layout_schedule.setWidget(0, QFormLayout.LabelRole, self.label_short_name) self.check_box_short_name = QCheckBox() self.form_layout_schedule.setWidget(0, QFormLayout.FieldRole, self.check_box_short_name) self.check_box_short_name.setChecked(Settings.ShortName) # navigate self.layout_navigate = QHBoxLayout() self.layout_navigate.addStretch(1) self.push_button_ok = QPushButton(self.tr("OK")) self.layout_navigate.addWidget(self.push_button_ok) self.push_button_apply = QPushButton(self.tr("Apply")) self.layout_navigate.addWidget(self.push_button_apply) self.push_button_cancel = QPushButton(self.tr("Cancel")) self.layout_navigate.addWidget(self.push_button_cancel) # layout setup self.layout_main = QVBoxLayout() self.layout_main.addWidget(self.group_box_lang) self.layout_main.addWidget(self.group_box_schedule) self.layout_main.addLayout(self.layout_navigate) self.setLayout(self.layout_main) # connection self.combo_box_app_lang.currentTextChanged.connect(self.application_lang_changed) self.combo_box_sch_lang.currentTextChanged.connect(self.schedule_lang_changed) self.check_box_short_name.clicked.connect(self.short_name_checked) self.push_button_ok.clicked.connect(self.close) self.push_button_apply.clicked.connect(self.close) self.push_button_cancel.clicked.connect(self.close) def changeEvent(self, event: QEvent) -> None: if event.type() == QEvent.LanguageChange: self.setWindowTitle(self.tr("Settings window")) self.group_box_lang.setTitle(self.tr("Language")) self.label_app_lang.setText(self.tr("Application")) self.label_sch_lang.setText(self.tr("Schedule")) self.push_button_ok.setText(self.tr("OK")) self.push_button_apply.setText(self.tr("Apply")) self.push_button_cancel.setText(self.tr("Cancel")) else: super().changeEvent(event) def application_lang_changed(self) -> None: """ Method to change the application language. """ Settings.ApplicationLang = self.combo_box_app_lang.currentData() translator = QTranslator() translator.load(":/translations/application_" + Settings.ApplicationLang) qApp.removeTranslator(Settings.ApplicationTranslator) qApp.installTranslator(translator) Settings.ApplicationTranslator = translator def schedule_lang_changed(self) -> None: """ Method to change the language of the schedule. """ Settings.ScheduleLang = self.combo_box_sch_lang.currentData() translator = QTranslator() translator.load(":/translations/schedule_" + Settings.ScheduleLang) qApp.removeTranslator(Settings.ScheduleTranslator) qApp.installTranslator(translator) Settings.ScheduleTranslator = translator def short_name_checked(self) -> None: """ Method to change the display mode of names. """ Settings.ShortName = int(self.check_box_short_name.isChecked())
class fullScreenEditor(QWidget): def __init__(self, index, parent=None): QWidget.__init__(self, parent) self._background = None self._index = index self._theme = findThemePath(settings.fullScreenTheme) self._themeDatas = loadThemeDatas(self._theme) self.setMouseTracking(True) self._geometries = {} # Text editor self.editor = textEditView(self, index=index, spellcheck=settings.spellcheck, highlighting=True, dict=settings.dict) self.editor.setFrameStyle(QFrame.NoFrame) self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.editor.installEventFilter(self) self.editor.setMouseTracking(True) self.editor.setVerticalScrollBar(myScrollBar()) self.scrollBar = self.editor.verticalScrollBar() self.scrollBar.setParent(self) # Top Panel self.topPanel = myPanel(parent=self) # self.topPanel.layout().addStretch(1) # Spell checking if enchant: self.btnSpellCheck = QPushButton(self) self.btnSpellCheck.setFlat(True) self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling")) self.btnSpellCheck.setCheckable(True) self.btnSpellCheck.setChecked(self.editor.spellcheck) self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck) self.topPanel.layout().addWidget(self.btnSpellCheck) self.topPanel.layout().addStretch(1) # Formatting self.textFormat = textFormat(self) self.topPanel.layout().addWidget(self.textFormat) self.topPanel.layout().addStretch(1) self.btnClose = QPushButton(self) self.btnClose.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCloseButton)) self.btnClose.clicked.connect(self.close) self.btnClose.setFlat(True) self.topPanel.layout().addWidget(self.btnClose) # Left Panel self._locked = False self.leftPanel = myPanel(vertical=True, parent=self) self.locker = locker(self) self.locker.lockChanged.connect(self.setLocked) self.leftPanel.layout().addWidget(self.locker) # Bottom Panel self.bottomPanel = myPanel(parent=self) self.bottomPanel.layout().addSpacing(24) self.lstThemes = QComboBox(self) self.lstThemes.setAttribute(Qt.WA_TranslucentBackground) paths = allPaths("resources/themes") for p in paths: lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"] for t in lst: themeIni = os.path.join(p, t) name = loadThemeDatas(themeIni)["Name"] # self.lstThemes.addItem(os.path.splitext(t)[0]) self.lstThemes.addItem(name) self.lstThemes.setItemData(self.lstThemes.count()-1, os.path.splitext(t)[0]) self.lstThemes.setCurrentIndex(self.lstThemes.findData(settings.fullScreenTheme)) # self.lstThemes.setCurrentText(settings.fullScreenTheme) self.lstThemes.currentTextChanged.connect(self.setTheme) self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height())) self.bottomPanel.layout().addWidget(QLabel(self.tr("Theme:"), self)) self.bottomPanel.layout().addWidget(self.lstThemes) self.bottomPanel.layout().addStretch(1) self.lblProgress = QLabel(self) self.lblProgress.setMaximumSize(QSize(200, 14)) self.lblProgress.setMinimumSize(QSize(100, 14)) self.lblWC = QLabel(self) self.bottomPanel.layout().addWidget(self.lblWC) self.bottomPanel.layout().addWidget(self.lblProgress) self.updateStatusBar() self.bottomPanel.layout().addSpacing(24) # Connection self._index.model().dataChanged.connect(self.dataChanged) # self.updateTheme() self.showFullScreen() # self.showMaximized() # self.show() def setLocked(self, val): self._locked = val self.btnClose.setVisible(not val) def setTheme(self, themeName): themeName = self.lstThemes.currentData() settings.fullScreenTheme = themeName self._theme = findThemePath(themeName) self._themeDatas = loadThemeDatas(self._theme) self.updateTheme() def updateTheme(self): # Reinit stored geometries for hiding widgets self._geometries = {} rect = self.geometry() self._background = generateTheme(self._themeDatas, rect) setThemeEditorDatas(self.editor, self._themeDatas, self._background, rect) # Colors if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \ self._themeDatas["Foreground/Opacity"] < 5: self._bgcolor = QColor(self._themeDatas["Text/Color"]) self._fgcolor = QColor(self._themeDatas["Background/Color"]) else: self._bgcolor = QColor(self._themeDatas["Foreground/Color"]) self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] * 255 / 100) self._fgcolor = QColor(self._themeDatas["Text/Color"]) if self._themeDatas["Text/Color"] == self._themeDatas["Foreground/Color"]: self._fgcolor = QColor(self._themeDatas["Background/Color"]) # ScrollBar r = self.editor.geometry() w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent) r.setWidth(w) r.moveRight(rect.right() - rect.left()) self.scrollBar.setGeometry(r) # self.scrollBar.setVisible(False) self.hideWidget(self.scrollBar) p = self.scrollBar.palette() b = QBrush(self._background.copy(self.scrollBar.geometry())) p.setBrush(QPalette.Base, b) self.scrollBar.setPalette(p) self.scrollBar.setColor(self._bgcolor) # Left Panel r = self.locker.geometry() r.moveTopLeft(QPoint( 0, self.geometry().height() / 2 - r.height() / 2 )) self.leftPanel.setGeometry(r) self.hideWidget(self.leftPanel) self.leftPanel.setColor(self._bgcolor) # Top / Bottom Panels r = QRect(0, 0, 0, 24) r.setWidth(rect.width()) # r.moveLeft(rect.center().x() - r.width() / 2) self.topPanel.setGeometry(r) # self.topPanel.setVisible(False) self.hideWidget(self.topPanel) r.moveBottom(rect.bottom() - rect.top()) self.bottomPanel.setGeometry(r) # self.bottomPanel.setVisible(False) self.hideWidget(self.bottomPanel) self.topPanel.setColor(self._bgcolor) self.bottomPanel.setColor(self._bgcolor) # Lst theme # p = self.lstThemes.palette() p = self.palette() p.setBrush(QPalette.Button, self._bgcolor) p.setBrush(QPalette.ButtonText, self._fgcolor) p.setBrush(QPalette.WindowText, self._fgcolor) for panel in (self.bottomPanel, self.topPanel): for i in range(panel.layout().count()): item = panel.layout().itemAt(i) if item.widget(): item.widget().setPalette(p) # self.lstThemes.setPalette(p) # self.lblWC.setPalette(p) self.update() def paintEvent(self, event): if self._background: painter = QPainter(self) painter.setClipRegion(event.region()) painter.drawPixmap(event.rect(), self._background, event.rect()) painter.end() def resizeEvent(self, event): self.updateTheme() def keyPressEvent(self, event): if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \ not self._locked: self.close() else: QWidget.keyPressEvent(self, event) def mouseMoveEvent(self, event): r = self.geometry() for w in [self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel]: # w.setVisible(w.geometry().contains(event.pos())) if self._geometries[w].contains(event.pos()): self.showWidget(w) else: self.hideWidget(w) def hideWidget(self, widget): if widget not in self._geometries: self._geometries[widget] = widget.geometry() widget.move(self.geometry().bottomRight()) def showWidget(self, widget): if widget in self._geometries: widget.move(self._geometries[widget].topLeft()) def eventFilter(self, obj, event): if obj == self.editor and event.type() == QEvent.Enter: for w in [self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel]: # w.setVisible(False) self.hideWidget(w) return QWidget.eventFilter(self, obj, event) def dataChanged(self, topLeft, bottomRight): if not self._index: return if topLeft.row() <= self._index.row() <= bottomRight.row(): self.updateStatusBar() def updateStatusBar(self): if self._index: item = self._index.internalPointer() wc = item.data(Outline.wordCount.value) goal = item.data(Outline.goal.value) pg = item.data(Outline.goalPercentage.value) if goal: rect = self.lblProgress.geometry() rect = QRect(QPoint(0, 0), rect.size()) self.px = QPixmap(rect.size()) self.px.fill(Qt.transparent) p = QPainter(self.px) drawProgress(p, rect, pg, 2) p.end() self.lblProgress.setPixmap(self.px) self.lblWC.setText(self.tr("{} words / {}").format(wc, goal)) else: self.lblProgress.hide() self.lblWC.setText(self.tr("{} words").format(wc)) self.locker.setWordCount(wc) # If there's a goal, then we update the locker target's number of word accordingly # (also if there is a word count, we deduce it. if goal and not self.locker.isLocked(): if wc and goal - wc > 0: self.locker.spnWordTarget.setValue(goal - wc) elif not wc: self.locker.spnWordTarget.setValue(goal)
class ConnMail(ElementMaster): pixmap_path = 'images/ConnMail.png' child_pos = (True, False) def __init__(self, row, column): self.row = row self.column = column recipient = None sender = None password = None server_url = None server_port = '465' subject = None input_opt_index = 0 input_opt_data = None filename = None pass_input = False message_state = False message_txt = None log_state = False # recipient, sender, password, server_url, server_port, subject # input_opt_index, input_opt_data, filename, pass_input, message_state, message_txt, log_state self.config = (recipient, sender, password, server_url, server_port, subject, input_opt_index, input_opt_data, filename, pass_input, message_state, message_txt, log_state) super().__init__(self.row, self.column, QPixmap(self.pixmap_path), True, self.config) super().edit_sig.connect(self.edit) logging.debug( 'ConnMail::__init__() called at row {}, column {}'.format( row, column)) self.addFunction(ConnMailFunction) def __setstate__(self, state): logging.debug('ConnMail::__setstate__() called') self.row, self.column, self.config = state super().__init__(self.row, self.column, QPixmap(self.pixmap_path), True, self.config) super().edit_sig.connect(self.edit) self.addFunction(ConnMailFunction) def __getstate__(self): logging.debug('ConnMail__getstate__() called') return (self.row, self.column, self.config) def openEditor(self): logging.debug('ConnMail::openEditor() called') def edit(self): logging.debug('ConnMail::edit()') self.conn_mail_layout = QVBoxLayout() self.confirm_button = QPushButton(QC.translate('', 'Ok')) self.recipient_address_txt = QLabel() self.recipient_address_txt.setText( QC.translate('', 'Recipient address:')) self.recipient_address_input = QLineEdit() self.recipient_address_input.setPlaceholderText( QC.translate('', 'Separate addresses with spaces')) self.sender_address_txt = QLabel() self.sender_address_txt.setText( QC.translate('', 'Enter sender address:')) self.sender_address_input = QLineEdit() self.sender_address_input.setPlaceholderText( QC.translate('', '*****@*****.**')) self.password_txt = QLabel() self.password_txt.setText(QC.translate('', 'Enter password:'******'', 'Enter subject:')) self.subject_input = QLineEdit() self.server_txt = QLabel() self.server_txt.setText( QC.translate('', 'Enter server URL and port number:')) self.server_input_line = QWidget() self.server_input_line_layout = QHBoxLayout(self.server_input_line) self.server_url_input = QLineEdit() self.server_url_input.setPlaceholderText( QC.translate('', 'e.g. smtp.gmail.com')) self.server_port_input = QLineEdit() self.server_port_input.setMaximumWidth(50) self.server_port_input.setValidator(QIntValidator(0, 9999)) self.server_port_input.setText('465') self.server_input_line_layout.addWidget(self.server_url_input) self.server_input_line_layout.addWidget(self.server_port_input) self.message_box_line = QWidget() self.message_box_txt = QLabel() self.message_box_txt.setText( QC.translate('', 'Activate user defined message text?')) self.message_box_checkbox = QCheckBox() self.message_box_line_layout = QHBoxLayout(self.message_box_line) self.message_box_line_layout.addWidget(self.message_box_txt) self.message_box_line_layout.addWidget(self.message_box_checkbox) self.message_box_line_layout = QHBoxLayout(self.message_box_line) self.message_txt_input = QTextEdit() self.input_option_line = QWidget() self.input_option_txt = QLabel() self.input_option_txt.setText(QC.translate('', 'Use input as:')) self.input_options = QComboBox() self.input_options.addItem(QC.translate('', 'None')) self.input_options.addItem(QC.translate('', 'Message text')) self.input_options.addItem(QC.translate('', 'Attachment (String)')) self.input_options.addItem(QC.translate('', 'Attachment (Pickle)')) self.input_option_line_layout = QHBoxLayout(self.input_option_line) self.input_option_line_layout.addWidget(self.input_option_txt) self.input_option_line_layout.addWidget(self.input_options) self.filename_input_line = QWidget() self.filename_input_line_layout = QHBoxLayout(self.filename_input_line) self.filename_input_txt = QLabel() self.filename_input_txt.setText(QC.translate('', 'Filename:')) self.filename_input = QLineEdit() self.filename_input.setPlaceholderText(QC.translate( '', 'filename.txt')) self.filename_input_line_layout.addWidget(self.filename_input_txt) self.filename_input_line_layout.addWidget(self.filename_input) self.input_params_1 = QLabel() self.input_params_1.setText( QC.translate('', 'Note: Input configuration dict has priority')) self.input_params_2 = QLabel() self.input_params_2.setText( '{\'subject\' : \'Hello\', \'message\' : \'World!\'}') self.pass_input_line = QWidget() self.pass_input_txt = QLabel() self.pass_input_txt.setText(QC.translate('', 'Pass input forward?')) self.pass_input_check = QCheckBox() self.pass_input_line_layout = QHBoxLayout(self.pass_input_line) self.pass_input_line_layout.addWidget(self.pass_input_txt) self.pass_input_line_layout.addWidget(self.pass_input_check) self.help_txt = QLabel() self.help_txt.setText( QC.translate('', 'Only encrypted connections are allowed')) # hier logging option einfügen self.log_line = QWidget() self.ask_for_logging = QLabel() self.ask_for_logging.setText(QC.translate('', 'Log output?')) self.log_checkbox = QCheckBox() self.log_line_layout = QHBoxLayout(self.log_line) self.log_line_layout.addWidget(self.ask_for_logging) self.log_line_layout.addWidget(self.log_checkbox) self.log_line_layout.addStretch(1) self.conn_mail_edit = ElementEditor(self) self.conn_mail_edit.setWindowTitle(QC.translate('', 'Send E-Mail')) #self.conn_mail_edit.setMinimumSize(240, 330) # signals and slots self.confirm_button.clicked.connect(self.conn_mail_edit.closeEvent) self.conn_mail_edit.window_closed.connect(self.edit_done) self.message_box_checkbox.stateChanged.connect(self.toggle_message_box) self.input_options.currentIndexChanged.connect(self.indexChanged) # load existing config self.loadLastConfig() self.conn_mail_layout.addWidget(self.recipient_address_txt) self.conn_mail_layout.addWidget(self.recipient_address_input) self.conn_mail_layout.addWidget(self.sender_address_txt) self.conn_mail_layout.addWidget(self.sender_address_input) self.conn_mail_layout.addWidget(self.password_txt) self.conn_mail_layout.addWidget(self.password_input) self.conn_mail_layout.addWidget(self.server_txt) self.conn_mail_layout.addWidget(self.server_input_line) self.conn_mail_layout.addWidget(self.subject_txt) self.conn_mail_layout.addWidget(self.subject_input) self.conn_mail_layout.addWidget(self.message_box_line) self.conn_mail_layout.addWidget(self.message_txt_input) self.conn_mail_layout.addWidget(self.input_option_line) self.conn_mail_layout.addWidget(self.filename_input_line) self.conn_mail_layout.addWidget(self.input_params_1) self.conn_mail_layout.addWidget(self.input_params_2) self.conn_mail_layout.addWidget(self.pass_input_line) self.conn_mail_layout.addWidget(self.help_txt) self.conn_mail_layout.addWidget(self.log_line) self.conn_mail_layout.addWidget(self.confirm_button) self.conn_mail_edit.setLayout(self.conn_mail_layout) self.conn_mail_edit.show() def toggle_message_box(self, event): logging.debug('ConnMail::toggle_message_box() called') if event == 0: self.message_txt_input.setDisabled(True) else: self.message_txt_input.setDisabled(False) def indexChanged(self, event): current_index = event logging.debug('ConnMail::indexChanged() called: {}'.format(event)) if event == 2 or event == 3: self.filename_input_line.setVisible(True) else: self.filename_input_line.setVisible(False) def loadLastConfig(self): # recipient, sender, password, server_url, server_port, subject # input_opt_index, input_opt_data, filename, pass_input, message_state, log_state recipient, sender, password, server_url, server_port, subject, \ input_opt_index, input_opt_data, filename, pass_input, message_state, \ message_txt, log_state = self.config if message_state: self.toggle_message_box(2) self.message_box_checkbox.setChecked(True) else: self.toggle_message_box(0) self.message_box_checkbox.setChecked(False) if pass_input: self.pass_input_check.setChecked(True) else: self.pass_input_check.setChecked(False) if recipient: self.recipient_address_input.setText(recipient) if sender: self.sender_address_input.setText(sender) if password: self.password_input.setText(password) if server_url: self.server_url_input.setText(server_url) if server_port: self.server_port_input.setText(server_port) if subject: self.subject_input.setText(subject) if message_txt: self.message_txt_input.setPlainText(message_txt) if filename: self.filename_input.setText(filename) if log_state: self.log_checkbox.setChecked(True) else: self.log_checkbox.setChecked(False) self.input_options.setCurrentIndex(input_opt_index) self.indexChanged(input_opt_index) def edit_done(self): logging.debug('ConnMail::edit_done() called') recipient = self.recipient_address_input.text() sender = self.sender_address_input.text() password = self.password_input.text() server_url = self.server_url_input.text() server_port = self.server_port_input.text() subject = self.subject_input.text() input_opt_index = self.input_options.currentIndex() input_opt_data = self.input_options.currentData() filename = self.filename_input.text() pass_input = self.pass_input_check.isChecked() message_state = self.message_box_checkbox.isChecked() log_state = self.log_checkbox.isChecked() if self.message_txt_input.toPlainText() == '': message_txt = None else: message_txt = self.message_txt_input.toPlainText() # recipient, sender, password, server_url, server_port, subject # input_opt_index, input_opt_data, filename, pass_input, message_state, message_txt, log_state self.config = (recipient, sender, password, server_url, server_port, subject, input_opt_index, input_opt_data, filename, pass_input, message_state, message_txt, log_state) self.addFunction(ConnMailFunction)
class SubwindowBrowserSources(QWidget): """Show connections settings sub window.""" def createWindow(self, mainWindow, tab=''): """Create window.""" try: parent = None super().__init__(parent) # self.setWindowFlags(Qt.WindowStaysOnTopHint) self.setWindowIcon( QIcon(hwctool.settings.getResFile('browser.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(mainWindow.size().width() // 2, mainWindow.size().height() // 3) -\ QPoint(self.size().width() // 2, self.size().height() // 3) self.move(mainWindow.pos() + relativeChange) self.setWindowTitle(_("Browser Sources")) self.controller.websocketThread.unregister_hotkeys(force=True) except Exception as e: module_logger.exception("message") def createTabs(self, tab): """Create tabs.""" self.tabs = QTabWidget() self.createFormGroupIntro() # Add tabs self.tabs.addTab(self.formGroupIntro, _("Intros")) table = dict() table['intro'] = 0 self.tabs.setCurrentIndex(table.get(tab, -1)) def addHotkey(self, ident, label): element = HotkeyLayout( self, ident, label, hwctool.settings.config.parser.get("Intros", ident)) self.hotkeys[ident] = element return element def connectHotkeys(self): for ident, key in self.hotkeys.items(): if ident == 'hotkey_debug': for ident2, key2 in self.hotkeys.items(): if ident == ident2: continue key.modified.connect(key2.check_dublicate) key.modified.connect(self.hotkeyChanged) def hotkeyChanged(self, key, ident): self.changed() if (ident == 'hotkey_player1' and self.cb_single_hotkey.isChecked()): self.hotkeys['hotkey_player2'].setData( self.hotkeys['hotkey_player1'].getKey()) if not key: return if ((ident == 'hotkey_player1' and key == self.hotkeys['hotkey_player2'].getKey()['name']) or (ident == 'hotkey_player2' and key == self.hotkeys['hotkey_player1'].getKey()['name'])): self.cb_single_hotkey.setChecked(True) if (ident in ['hotkey_player1', 'hotkey_player2'] and key == self.hotkeys['hotkey_debug'].getKey()['name']): self.hotkeys['hotkey_debug'].clear() def singleHotkeyChanged(self): checked = self.cb_single_hotkey.isChecked() self.hotkeys['hotkey_player2'].setDisabled(checked) if checked: self.hotkeys['hotkey_player2'].setData( self.hotkeys['hotkey_player1'].getKey()) elif (self.hotkeys['hotkey_player1'].getKey() == self.hotkeys['hotkey_player2'].getKey()): self.hotkeys['hotkey_player2'].clear() def createFormGroupIntro(self): """Create forms for websocket connection to intro.""" self.formGroupIntro = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("Style")) layout = QHBoxLayout() styleqb = StyleComboBox( hwctool.settings.casting_html_dir + "/src/css/intro", "intro") styleqb.connect2WS(self.controller, 'intro') button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( hwctool.settings.casting_html_dir + "/intro.html")) layout.addWidget(styleqb, 2) layout.addWidget(button, 1) box.setLayout(layout) mainLayout.addWidget(box) self.hotkeyBox = QGroupBox(_("Hotkeys")) layout = QVBoxLayout() try: keyboard.unhook_all() except AttributeError: pass self.cb_single_hotkey = QCheckBox( _("Use a single hotkey for both players")) self.cb_single_hotkey.stateChanged.connect(self.singleHotkeyChanged) layout.addWidget(self.cb_single_hotkey) self.hotkeys = dict() layout.addLayout(self.addHotkey("hotkey_player1", _("Player 1"))) layout.addLayout(self.addHotkey("hotkey_player2", _("Player 2"))) layout.addLayout(self.addHotkey("hotkey_debug", _("Debug"))) self.cb_single_hotkey.setChecked(self.hotkeys['hotkey_player1'].getKey( ) == self.hotkeys['hotkey_player2'].getKey()) self.connectHotkeys() label = QLabel( _("Player 1 is always the player your observer" " camera is centered on at start of a game.")) layout.addWidget(label) self.hotkeyBox.setLayout(layout) mainLayout.addWidget(self.hotkeyBox) self.introBox = QGroupBox(_("Animation")) layout = QFormLayout() self.cb_animation = QComboBox() animation = hwctool.settings.config.parser.get("Intros", "animation") currentIdx = 0 idx = 0 options = dict() options['Fly-In'] = _("Fly-In") options['Slide'] = _("Slide") options['Fanfare'] = _("Fanfare") for key, item in options.items(): self.cb_animation.addItem(item, key) if (key == animation): currentIdx = idx idx += 1 self.cb_animation.setCurrentIndex(currentIdx) self.cb_animation.currentIndexChanged.connect(self.changed) label = QLabel(_("Animation:") + " ") label.setMinimumWidth(120) layout.addRow(label, self.cb_animation) self.sb_displaytime = QDoubleSpinBox() self.sb_displaytime.setRange(0, 10) self.sb_displaytime.setDecimals(1) self.sb_displaytime.setValue( hwctool.settings.config.parser.getfloat("Intros", "display_time")) self.sb_displaytime.setSuffix(" " + _("Seconds")) self.sb_displaytime.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Display Duration:") + " "), self.sb_displaytime) self.sl_sound = QSlider(Qt.Horizontal) self.sl_sound.setMinimum(0) self.sl_sound.setMaximum(20) self.sl_sound.setValue( hwctool.settings.config.parser.getint("Intros", "sound_volume")) self.sl_sound.setTickPosition(QSlider.TicksBothSides) self.sl_sound.setTickInterval(1) self.sl_sound.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Sound Volume:") + " "), self.sl_sound) self.introBox.setLayout(layout) mainLayout.addWidget(self.introBox) self.ttsBox = QGroupBox(_("Text-to-Speech")) layout = QFormLayout() self.cb_tts_active = QCheckBox() self.cb_tts_active.setChecked( hwctool.settings.config.parser.getboolean("Intros", "tts_active")) self.cb_tts_active.stateChanged.connect(self.changed) label = QLabel(_("Activate Text-to-Speech:") + " ") label.setMinimumWidth(120) layout.addRow(label, self.cb_tts_active) self.icons = {} self.icons['MALE'] = QIcon(hwctool.settings.getResFile('male.png')) self.icons['FEMALE'] = QIcon(hwctool.settings.getResFile('female.png')) self.cb_tts_voice = QComboBox() currentIdx = 0 idx = 0 tts_voices = self.controller.tts.getVoices() tts_voice = hwctool.settings.config.parser.get("Intros", "tts_voice") for voice in tts_voices: self.cb_tts_voice.addItem(self.icons[voice['ssmlGender']], ' ' + voice['name'], voice['name']) if (voice['name'] == tts_voice): currentIdx = idx idx += 1 self.cb_tts_voice.setCurrentIndex(currentIdx) self.cb_tts_voice.currentIndexChanged.connect(self.changed) layout.addRow(QLabel(_("Voice:") + " "), self.cb_tts_voice) self.ttsBox.setStyleSheet("QComboBox { combobox-popup: 0; }") self.ttsBox.setLayout(layout) mainLayout.addWidget(self.ttsBox) self.sb_tts_pitch = QDoubleSpinBox() self.sb_tts_pitch.setRange(-20, 20) self.sb_tts_pitch.setDecimals(2) self.sb_tts_pitch.setValue( hwctool.settings.config.parser.getfloat("Intros", "tts_pitch")) self.sb_tts_pitch.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Pitch:") + " "), self.sb_tts_pitch) self.sb_tts_rate = QDoubleSpinBox() self.sb_tts_rate.setRange(0.25, 4.00) self.sb_tts_rate.setSingleStep(0.1) self.sb_tts_rate.setDecimals(2) self.sb_tts_rate.setValue( hwctool.settings.config.parser.getfloat("Intros", "tts_rate")) self.sb_tts_rate.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Rate:") + " "), self.sb_tts_rate) self.cb_tts_scope = QComboBox() self.cb_tts_scope.setMaximumWidth(400) scope = hwctool.settings.config.parser.get("Intros", "tts_scope") currentIdx = 0 idx = 0 options = self.controller.tts.getOptions() for key, item in options.items(): self.cb_tts_scope.addItem(item['desc'], key) if (key == scope): currentIdx = idx idx += 1 self.cb_tts_scope.setCurrentIndex(currentIdx) self.cb_tts_scope.currentIndexChanged.connect(self.changed) layout.addRow(QLabel(_("Line:") + " "), self.cb_tts_scope) self.sl_tts_sound = QSlider(Qt.Horizontal) self.sl_tts_sound.setMinimum(0) self.sl_tts_sound.setMaximum(20) self.sl_tts_sound.setValue( hwctool.settings.config.parser.getint("Intros", "tts_volume")) self.sl_tts_sound.setTickPosition(QSlider.TicksBothSides) self.sl_tts_sound.setTickInterval(1) self.sl_tts_sound.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Sound Volume:") + " "), self.sl_tts_sound) text = _( "Text-to-Speech provided by Google-Cloud is paid for " "by StarCraft Casting Tool Patrons. To keep this service up " "consider becoming a <a href='{patreon}'>Patron</a> yourself. " "You can test all voices at {tts}.") patreon = 'https://www.patreon.com/StarCraftCastingTool' url = 'https://cloud.google.com/text-to-speech/' tts = "<a href='{}'>cloud.google.com/text-to-speech</a>" tts = tts.format(url) label = QLabel(text.format(patreon=patreon, tts=tts)) label.setAlignment(Qt.AlignJustify) label.setOpenExternalLinks(True) label.setWordWrap(True) label.setMargin(5) layout.addRow(label) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupIntro.setLayout(mainLayout) 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 as e: 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): self.saveWebsocketdata() self.__dataChanged = False # self.controller.refreshButtonStatus() def saveWebsocketdata(self): """Save Websocket data.""" for ident, key in self.hotkeys.items(): string = hwctool.settings.config.dumpHotkey(key.getKey()) hwctool.settings.config.parser.set("Intros", ident, string) hwctool.settings.config.parser.set("Intros", "display_time", str(self.sb_displaytime.value())) hwctool.settings.config.parser.set("Intros", "sound_volume", str(self.sl_sound.value())) hwctool.settings.config.parser.set( "Intros", "animation", self.cb_animation.currentData().strip()) hwctool.settings.config.parser.set( "Intros", "tts_voice", self.cb_tts_voice.currentData().strip()) hwctool.settings.config.parser.set( "Intros", "tts_scope", self.cb_tts_scope.currentData().strip()) hwctool.settings.config.parser.set("Intros", "tts_active", str(self.cb_tts_active.isChecked())) hwctool.settings.config.parser.set("Intros", "tts_volume", str(self.sl_tts_sound.value())) hwctool.settings.config.parser.set("Intros", "tts_pitch", str(self.sb_tts_pitch.value())) hwctool.settings.config.parser.set("Intros", "tts_rate", str(self.sb_tts_rate.value())) def openHTML(self, file): """Open file in browser.""" self.controller.openURL(hwctool.settings.getAbsPath(file)) 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): self.controller.updateHotkeys() 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() self.controller.updateHotkeys() event.accept() except Exception as e: module_logger.exception("message")
class RenameWidget(FlexiFrame): """ Display combo boxes for file renaming and file extension case handling, and an example file name """ def __init__( self, preset_type: PresetPrefType, prefs: Preferences, exiftool_process: exiftool.ExifTool, parent, ) -> None: super().__init__(parent=parent) self.setBackgroundRole(QPalette.Base) self.setAutoFillBackground(True) self.exiftool_process = exiftool_process self.prefs = prefs self.preset_type = preset_type if preset_type == PresetPrefType.preset_photo_rename: self.file_type = FileType.photo self.pref_defn = DICT_IMAGE_RENAME_L0 self.generation_type = NameGenerationType.photo_name self.index_lookup = self.prefs.photo_rename_index self.pref_conv = PHOTO_RENAME_MENU_DEFAULTS_CONV self.generation_type = NameGenerationType.photo_name else: self.file_type = FileType.video self.pref_defn = DICT_VIDEO_RENAME_L0 self.generation_type = NameGenerationType.video_name self.index_lookup = self.prefs.video_rename_index self.pref_conv = VIDEO_RENAME_MENU_DEFAULTS_CONV self.generation_type = NameGenerationType.video_name self.sample_rpd_file = make_sample_rpd_file( sample_job_code=self.prefs.most_recent_job_code(missing=_("Job Code")), prefs=self.prefs, generation_type=self.generation_type, ) layout = QFormLayout() self.layout().addLayout(layout) self.getCustomPresets() self.renameCombo = PresetComboBox( prefs=self.prefs, preset_names=self.preset_names, preset_type=preset_type, parent=self, edit_mode=False, ) self.setRenameComboIndex() self.renameCombo.activated.connect(self.renameComboItemActivated) # File extensions self.extensionCombo = QComboBox() self.extensionCombo.addItem(_(ORIGINAL_CASE), ORIGINAL_CASE) self.extensionCombo.addItem(_(UPPERCASE), UPPERCASE) self.extensionCombo.addItem(_(LOWERCASE), LOWERCASE) if preset_type == PresetPrefType.preset_photo_rename: pref_value = self.prefs.photo_extension else: pref_value = self.prefs.video_extension try: index = [ORIGINAL_CASE, UPPERCASE, LOWERCASE].index(pref_value) except ValueError: if preset_type == PresetPrefType.preset_photo_rename: t = "Photo" else: t = "Video" logging.error( "%s extension case value is invalid. Resetting to lower case.", t ) index = 2 self.extensionCombo.setCurrentIndex(index) self.extensionCombo.currentIndexChanged.connect(self.extensionChanged) self.example = QLabel() self.updateExampleFilename() layout.addRow(_("Preset:"), self.renameCombo) layout.addRow(_("Extension:"), self.extensionCombo) layout.addRow(_("Example:"), self.example) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) def setRenameComboIndex(self) -> None: """ Set the value being displayed in the combobox to reflect the current renaming preference. Takes into account built-in renaming presets and custom presets. """ index = self.index_lookup(self.preset_pref_lists) if index == -1: # Set to the "Custom..." value cb_index = self.renameCombo.count() - 1 else: # Set to appropriate combobox idex, allowing for possible separator cb_index = self.renameCombo.getComboBoxIndex(index) logging.debug( "Setting %s combobox chosen value to %s", self.file_type.name, self.renameCombo.itemText(cb_index), ) self.renameCombo.setCurrentIndex(cb_index) def pref_list(self) -> List[str]: """ :return: the user's file naming preference according to whether this widget is handling photos or videos """ if self.preset_type == PresetPrefType.preset_photo_rename: return self.prefs.photo_rename else: return self.prefs.video_rename @pyqtSlot(int) def renameComboItemActivated(self, index: int) -> None: """ Respond to user activating the Rename preset combo box. :param index: index of the item activated """ user_pref_list = None preset_class = self.renameCombo.currentData() if preset_class == PresetClass.start_editor: prefDialog = PrefDialog( self.pref_defn, self.pref_list(), self.generation_type, self.prefs, self.sample_rpd_file, ) if prefDialog.exec(): user_pref_list = prefDialog.getPrefList() if not user_pref_list: user_pref_list = None # Regardless of whether the user clicked OK or cancel, refresh the rename combo # box entries self.getCustomPresets() self.renameCombo.resetEntries(self.preset_names) self.setUserPrefList(user_pref_list=user_pref_list) self.setRenameComboIndex() else: assert ( preset_class == PresetClass.custom or preset_class == PresetClass.builtin ) index = self.renameCombo.getPresetIndex(self.renameCombo.currentIndex()) user_pref_list = self.combined_pref_lists[index] self.setUserPrefList(user_pref_list=user_pref_list) self.updateExampleFilename() def getCustomPresets(self) -> None: """ Get the custom presets from the user preferences and store them in lists """ self.preset_names, self.preset_pref_lists = self.prefs.get_preset( preset_type=self.preset_type ) self.combined_pref_lists = self.pref_conv + tuple(self.preset_pref_lists) def setUserPrefList(self, user_pref_list: List[str]) -> None: """ Update the user preferences with a new preference value :param user_pref_list: the photo or video rename preference list """ if user_pref_list is not None: logging.debug("Setting new %s rename preference value", self.file_type.name) if self.preset_type == PresetPrefType.preset_photo_rename: self.prefs.photo_rename = user_pref_list else: self.prefs.video_rename = user_pref_list def updateExampleFilename( self, downloads_today: Optional[List[str]] = None, stored_sequence_no: Optional[int] = None, ) -> None: """ Update filename shown to user that serves as an example of the renaming rule in practice on sample data. :param downloads_today: if specified, update the downloads today value :param stored_sequence_no: if specified, update the stored sequence value """ if downloads_today: self.sample_rpd_file.sequences.downloads_today_tracker.downloads_today = ( downloads_today ) if stored_sequence_no is not None: self.sample_rpd_file.sequences.stored_sequence_no = stored_sequence_no if self.preset_type == PresetPrefType.preset_photo_rename: self.name_generator = gn.PhotoName(self.prefs.photo_rename) logging.debug("Updating example photo name in rename panel") else: self.name_generator = gn.VideoName(self.prefs.video_rename) logging.debug("Updating example video name in rename panel") self.example.setText(self.name_generator.generate_name(self.sample_rpd_file)) def updateSampleFile(self, sample_rpd_file: Union[Photo, Video]) -> None: self.sample_rpd_file = make_sample_rpd_file( sample_rpd_file=sample_rpd_file, sample_job_code=self.prefs.most_recent_job_code(missing=_("Job Code")), prefs=self.prefs, generation_type=self.generation_type, ) self.updateExampleFilename() @pyqtSlot(int) def extensionChanged(self, index: int) -> None: """ Respond to user changing the case of file extensions in file name generation. Save new preference value, and update example file name. """ value = self.extensionCombo.currentData() if self.preset_type == PresetPrefType.preset_photo_rename: self.prefs.photo_extension = value else: self.prefs.video_extension = value self.sample_rpd_file.generate_extension_case = value self.updateExampleFilename()
class MetToolsDownloadManager(QWidget): def __init__(self, iface) -> None: super().__init__() self.iface = iface self.options = get_options() self.msg_bar = MessageBar(iface) vbox = QVBoxLayout() self.setLayout(vbox) hbox = QHBoxLayout() vbox.addLayout(hbox) hbox.addWidget(QLabel('Dataset: ')) self.cbox_dataset = QComboBox() self.cbox_dataset.addItem('-') for index, (dataset_name, dataset_label) in enumerate(met_datasets.items()): self.cbox_dataset.addItem(dataset_name, dataset_name) self.cbox_dataset.setItemData(index + 1, dataset_label, Qt.ToolTipRole) self.cbox_dataset.currentIndexChanged.connect(self.on_dataset_changed) hbox.addWidget(self.cbox_dataset) hbox_product_name = QHBoxLayout() vbox.addLayout(hbox_product_name) hbox_product_name.addWidget(QLabel('Product: ')) self.cbox_product = QComboBox() self.cbox_product.currentIndexChanged.connect(self.on_product_changed) hbox_product_name.addWidget(self.cbox_product) hbox_start_datetime = QHBoxLayout() vbox.addLayout(hbox_start_datetime) self.dedit_start_date = QDateTimeEdit() self.dedit_start_date.setCalendarPopup(True) hbox_start_datetime.addWidget(QLabel('Start: ')) hbox_start_datetime.addWidget(self.dedit_start_date) hbox_end_datetime = QHBoxLayout() vbox.addLayout(hbox_end_datetime) self.dedit_end_date = QDateTimeEdit() self.dedit_end_date.setCalendarPopup(True) hbox_end_datetime.addWidget(QLabel('End: ')) hbox_end_datetime.addWidget(self.dedit_end_date) gbox_extent = QGroupBox('Extent') vbox.addWidget(gbox_extent) vbox_extent = QVBoxLayout() gbox_extent.setLayout(vbox_extent) hbox_extent = QHBoxLayout() vbox_extent.addLayout(hbox_extent) self.radio_global = QRadioButton('Global') self.radio_global.toggled.connect(self.on_extent_radio_button_clicked) hbox_extent.addWidget(self.radio_global) self.radio_subset = QRadioButton('Subset') self.radio_subset.toggled.connect(self.on_extent_radio_button_clicked) hbox_extent.addWidget(self.radio_subset) self.widget_extent = QWidget() vbox_extent.addWidget(self.widget_extent) grid_extent = QGridLayout() self.widget_extent.setLayout(grid_extent) self.widget_extent.hide() self.top = add_grid_lineedit(grid_extent, 0, 'North Latitude', LAT_VALIDATOR, '°', required=True) self.right = add_grid_lineedit(grid_extent, 1, 'East Longitude', LON_VALIDATOR, '°', required=True) self.left = add_grid_lineedit(grid_extent, 2, 'West Longitude', LON_VALIDATOR, '°', required=True) self.bottom = add_grid_lineedit(grid_extent, 3, 'South Latitude', LAT_VALIDATOR, '°', required=True) self.extent_from_active_layer = QPushButton('Set from Active Layer') grid_extent.addWidget(self.extent_from_active_layer, 4, 1) self.extent_from_active_layer.clicked.connect( self.on_extent_from_active_layer_button_clicked) self.radio_global.setChecked(True) self.tree = QListWidget() vbox_tree = QVBoxLayout() vbox.addLayout(vbox_tree) vbox_tree.addWidget(self.tree) self.btn_download = QPushButton('Download') self.btn_download.clicked.connect(self.on_download_button_clicked) vbox.addWidget(self.btn_download) self.progress_bar = QProgressBar() self.progress_bar.setRange(0, PROGRESS_BAR_MAX) self.progress_bar.setTextVisible(False) self.progress_bar.hide() vbox.addWidget(self.progress_bar) def on_dataset_changed(self, index: int): self.cbox_product.clear() dataset_name = self.cbox_dataset.currentData() if dataset_name is None: return auth = (self.options.rda_username, self.options.rda_password) self.products = get_met_products(dataset_name, auth) for product in self.products.keys(): self.cbox_product.addItem(product, product) def on_product_changed(self, index: int): if index == -1: return self.tree.clear() product_name = self.cbox_product.currentData() current_avail_vars = self.products[product_name] dates = [] for name in current_avail_vars.keys(): item = QListWidgetItem(current_avail_vars[name]['label']) item.setData(Qt.UserRole, name) item.setCheckState(Qt.Checked) self.tree.addItem(item) dates.append(current_avail_vars[name]['start_date']) dates.append(current_avail_vars[name]['end_date']) date_min = min(dates) date_max = max(dates) for dt_input in [self.dedit_start_date, self.dedit_end_date]: dt_input.setDateTimeRange( QDateTime(QDate(date_min.year, date_min.month, date_min.day), QTime(date_min.hour, date_min.minute)), QDateTime(QDate(date_max.year, date_max.month, date_max.day), QTime(date_max.hour, date_max.minute))) min_dt = self.dedit_start_date.minimumDateTime() max_dt = self.dedit_start_date.maximumDateTime() self.dedit_start_date.setDateTime(min_dt) self.dedit_end_date.setDateTime(max_dt) def on_download_button_clicked(self): param_names = [] for index in range(self.tree.count()): item = self.tree.item(index) if item.checkState() == Qt.Checked: param_name = item.data(Qt.UserRole) param_names.append(param_name) dataset_name = self.cbox_dataset.currentData() product_name = self.cbox_product.currentData() start_date = self.dedit_start_date.dateTime().toPyDateTime() end_date = self.dedit_end_date.dateTime().toPyDateTime() if dataset_name is None or product_name is None: raise UserError('Dataset/Product not selected') args = [ self.options.met_dir, dataset_name, product_name, start_date, end_date ] if is_met_dataset_downloaded(*args): reply = QMessageBox.question(self.iface.mainWindow( ), 'Existing dataset', ( 'You already downloaded data with the selected dataset/product/date/time combination. ' 'If you continue, this data will be removed.\n' 'Location: {}'.format(get_met_dataset_path(*args))), QMessageBox.Ok, QMessageBox.Cancel) if reply == QMessageBox.Cancel: return lat_north = self.top.value() lat_south = self.bottom.value() lon_west = self.left.value() lon_east = self.right.value() auth = (self.options.rda_username, self.options.rda_password) thread = TaskThread(lambda: download_met_dataset( self.options.met_dir, auth, dataset_name, product_name, param_names, start_date, end_date, lat_south, lat_north, lon_west, lon_east), yields_progress=True) thread.started.connect(self.on_started_download) thread.progress.connect(self.on_progress_download) thread.finished.connect(self.on_finished_download) thread.succeeded.connect(self.on_successful_download) thread.failed.connect(reraise) thread.start() def on_started_download(self) -> None: self.btn_download.hide() self.progress_bar.show() def on_progress_download(self, progress: float, status: str) -> None: bar_value = int(progress * PROGRESS_BAR_MAX) self.progress_bar.setValue(bar_value) self.progress_bar.repaint() # otherwise just updates in 1% steps if status == 'submitted': self.msg_bar.info( 'Met dataset download request submitted successfully, waiting until available for download...' ) elif status == 'ready': self.msg_bar.info( 'Met dataset download request is now ready, downloading...') logger.debug(f'Met data download: {progress*100:.1f}% - {status}') def on_finished_download(self) -> None: self.btn_download.show() self.progress_bar.hide() def on_successful_download(self) -> None: self.msg_bar.success('Meteorological dataset downloaded successfully.') Broadcast.met_datasets_updated.emit() def on_extent_radio_button_clicked(self): if self.radio_global.isChecked(): self.top.set_value(90) self.bottom.set_value(-90) self.left.set_value(-180) self.right.set_value(180) self.top.setDisabled(True) self.bottom.setDisabled(True) self.left.setDisabled(True) self.right.setDisabled(True) self.widget_extent.hide() elif self.radio_subset.isChecked(): self.widget_extent.show() self.top.setDisabled(False) self.bottom.setDisabled(False) self.left.setDisabled(False) self.right.setDisabled(False) def on_extent_from_active_layer_button_clicked(self): layer = self.iface.activeLayer() # type: Optional[QgsMapLayer] if layer is None: return layer_crs = CRS(layer.crs().toProj4()) target_crs = CRS('+proj=latlong +datum=WGS84') extent = layer.extent() # type: QgsRectangle bbox = rect_to_bbox(extent) bbox_geo = layer_crs.transform_bbox(bbox, target_crs.srs) padding = 5 # degrees lat_south = max(bbox_geo.miny - 5, -90) lat_north = min(bbox_geo.maxy + 5, 90) lon_west = max(bbox_geo.minx - 5, -180) lon_east = min(bbox_geo.maxx + 5, 180) self.bottom.set_value(lat_south) self.top.set_value(lat_north) self.left.set_value(lon_west) self.right.set_value(lon_east)
class _StylePreview(QGroupBox): preview_text_changed = pyqtSignal([]) def __init__( self, api: Api, model: AssStylesModel, selection_model: QItemSelectionModel, parent: QWidget, ) -> None: super().__init__("Preview", parent) self._api = api self._selection_model = selection_model self._renderer = AssRenderer() self._editor = QPlainTextEdit() self._editor.setPlainText(api.cfg.opt["styles"]["preview_test_text"]) self._editor.setFixedWidth(400) self._editor.setTabChangesFocus(True) self._editor.setFixedHeight(get_text_edit_row_height(self._editor, 2)) self._background_combobox = QComboBox() for i, path in enumerate(get_assets("style_preview_bk")): self._background_combobox.addItem(path.name, path.resolve()) if path.name == api.cfg.opt["styles"]["preview_background"]: self._background_combobox.setCurrentIndex(i) self._preview_box = QLabel(self) self._preview_box.setLineWidth(1) self._preview_box.setFrameShape(QFrame.StyledPanel) self._preview_box.setFrameShadow(QFrame.Sunken) self._preview_box.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) layout = QVBoxLayout(self) layout.addWidget(self._editor) layout.addWidget(self._background_combobox) layout.addWidget(self._preview_box) self.update_preview() self._editor.textChanged.connect(self._on_text_change) self._background_combobox.currentIndexChanged.connect( self._on_background_change) model.dataChanged.connect(self.update_preview) model.rowsInserted.connect(self.update_preview) model.rowsRemoved.connect(self.update_preview) selection_model.selectionChanged.connect(self.update_preview) def _on_background_change(self) -> None: self.update_preview() self._api.cfg.opt["styles"][ "preview_background"] = self._background_combobox.currentData( ).name def _on_text_change(self) -> None: self.preview_text_changed.emit() self.update_preview() self._api.cfg.opt["styles"]["preview_test_text"] = self.preview_text @property def preview_text(self) -> str: return self._editor.toPlainText() @property def _selected_style(self) -> Optional[AssStyle]: try: idx = self._selection_model.selectedIndexes()[0].row() except IndexError: return None else: return self._api.subs.styles[idx] def update_preview(self) -> None: selected_style = self._selected_style if not selected_style: self._preview_box.clear() return resolution = (self._preview_box.width(), self._preview_box.height()) if resolution[0] <= 0 or resolution[1] <= 0: self._preview_box.clear() return fake_file = AssFile() fake_style = copy(selected_style) fake_style.name = "Default" if (self._api.video.has_current_stream and self._api.video.current_stream.is_ready): fake_style.scale(resolution[1] / self._api.video.current_stream.height) fake_file.styles.append(fake_style) fake_event = AssEvent( start=0, end=1000, text=self.preview_text.replace("\n", "\\N"), style_name=fake_style.name, ) fake_file.events.append(fake_event) image = PIL.Image.new(mode="RGBA", size=resolution) background_path = self._background_combobox.currentData() if background_path and background_path.exists(): background = PIL.Image.open(background_path) for y in range(0, resolution[1], background.height): for x in range(0, resolution[0], background.width): image.paste(background, (x, y)) self._renderer.set_source(fake_file, resolution) try: aspect_ratio = self._api.video.current_stream.aspect_ratio except ResourceUnavailable: aspect_ratio = Fraction(1) subs_image = self._renderer.render( time=0, aspect_ratio=aspect_ratio, ) image = PIL.Image.composite(subs_image, image, subs_image) image = PIL.ImageQt.ImageQt(image) image = QImage(image) self._preview_box.setPixmap(QPixmap.fromImage(image))
class Bound_Split_Dialog_Preview(Muliti_Input_Dialog): def __init__(self, *args, **kwargs): Muliti_Input_Dialog.__init__(self, *args, **kwargs) self.Split = QLineEdit(self) self.Split.setText("1") self.Ref = QComboBox(self) self.Preview = QLabel(self) self.Preview.setStyleSheet("background:white") self.Preview.setAlignment(Qt.AlignCenter) self.Add_row('Input Grid Mode:', self.Split) self.Add_row('Choose Bound Refer:', self.Ref) self.Derivate_cutoff_x = QLineEdit(self) self.Derivate_cutoff_x.setText("1") self.Add_row('Derivate cutoff x:', self.Derivate_cutoff_x) self.Derivate_cutoff_y = QLineEdit(self) self.Derivate_cutoff_y.setText("1") self.Add_row('Derivate cutoff y:', self.Derivate_cutoff_y) self.Add_row(self.Preview) self.Split.textEdited.connect(self.update_preview) self.Ref.currentIndexChanged.connect(self.update_preview) self.Derivate_cutoff_x.textEdited.connect(self.update_preview) self.Derivate_cutoff_y.textEdited.connect(self.update_preview) self.Split_Width = [] def update_preview(self): data = self.Ref.currentData() img = data['img'] x, y, w, h = data['rec'] wb_img = img.copy(x + 2, y, w - 4, h) cv_img = QImage_to_CV_Img(wb_img) if self.Derivate_cutoff_x.text(): cutoff_x = float(self.Derivate_cutoff_x.text()) else: cutoff_x = 1 if self.Derivate_cutoff_x.text(): cutoff_y = float(self.Derivate_cutoff_x.text()) else: cutoff_y = 1 bounds_x, bounds_y = bound_from_cv_img(cv_img, cutoff_x, cutoff_y) bounds_x_fixed = bounds_x + x + 2 self.bounds_x = bounds_x_fixed bounds_y_fixed = bounds_y + y + 2 cv_img = QImage_to_CV_Img(img) ih = cv_img.shape[0] iw = cv_img.shape[1] for split in bounds_x_fixed: cv_img = cv2.line(cv_img, (split, 0), (split, ih), (255, 0, 0), 1) for split in bounds_y_fixed: cv_img = cv2.line(cv_img, (0, split), (iw, split), (255, 0, 0), 1) # 补充根据split_str 来分割框框 split_str = self.Split.text().split(',') split_str = [int(i) for i in split_str if len(i) > 0] ll = min(len(split_str), len(bounds_x_fixed)) current = 0 self.Split_str = split_str[0:ll] self.Split_Width = [] for i in range(ll): sp = split_str[i] start = current end = current + sp self.Split_Width.append(bounds_x_fixed[end] - bounds_x_fixed[start]) cv_img = cv2.line(cv_img, (bounds_x_fixed[start] + 10, bounds_y_fixed[0]), (bounds_x_fixed[end] - 10, bounds_y_fixed[1]), (0, 0, 255), 2) current = end qimg, _ = CV_Img_to_QImage(cv_img) self.Preview.setPixmap(qimg)
class DomainWidget(QWidget): tab_active = pyqtSignal() go_to_data_tab = pyqtSignal() def __init__(self, iface: QgisInterface) -> None: super().__init__() self.iface = iface # Import/Export ## Import from 'namelist.wps' import_from_namelist_button = QPushButton("Import from namelist") import_from_namelist_button.setObjectName( 'import_from_namelist_button') ## Export to namelist export_geogrid_namelist_button = QPushButton("Export to namelist") export_geogrid_namelist_button.setObjectName( 'export_geogrid_namelist_button') vbox_import_export = QVBoxLayout() vbox_import_export.addWidget(import_from_namelist_button) vbox_import_export.addWidget(export_geogrid_namelist_button) self.gbox_import_export = QGroupBox("Import/Export") self.gbox_import_export.setLayout(vbox_import_export) # Group: Map Type self.group_box_map_type = QGroupBox("Map Type") vbox_map_type = QVBoxLayout() hbox_map_type = QHBoxLayout() self.projection = QComboBox() self.projection.setObjectName('projection') projs = { 'undefined': '-', # do not use a default projection - let the user pick the projection. 'lat-lon': 'Latitude/Longitude', 'lambert': 'Lambert' } for proj_id, proj_label in projs.items(): self.projection.addItem(proj_label, proj_id) hbox_map_type.addWidget(QLabel('GCS/Projection:')) # TODO: when the user select the type of GCS/Projection # we should automatically change the GCS/Projection for the whole # project. This will do an on-the-fly remapping of any of the other CRS # which are different from the one supported by our tool/WRF. # The Project CRS can be accessed in QGIS under the menu `Project` > `Project Properties` > `CRS.` # -> This is only really possible for Lat/Lon as this one is a CRS, where the others are projections # that only become a full CRS with the additional parameters like truelat1. # TODO: fields should be cleared when the user changes the CRS/Projection. hbox_map_type.addWidget(self.projection) vbox_map_type.addLayout(hbox_map_type) ## Lambert only: show 'True Latitudes' field truelat_grid = QGridLayout() self.truelat1 = add_grid_lineedit(truelat_grid, 0, 'True Latitude 1', LAT_VALIDATOR, unit='°', required=True) self.truelat2 = add_grid_lineedit(truelat_grid, 1, 'True Latitude 2', LAT_VALIDATOR, unit='°', required=True) self.widget_true_lats = QWidget() self.widget_true_lats.setLayout(truelat_grid) vbox_map_type.addWidget(self.widget_true_lats) self.domain_pb_set_projection = QPushButton("Set Map CRS") self.domain_pb_set_projection.setObjectName('set_projection_button') vbox_map_type.addWidget(self.domain_pb_set_projection) self.group_box_map_type.setLayout(vbox_map_type) # Group: Horizontal Resolution self.group_box_resol = QGroupBox("Horizontal Grid Spacing") hbox_resol = QHBoxLayout() self.resolution = MyLineEdit(required=True) self.resolution.setValidator(RESOLUTION_VALIDATOR) self.resolution.textChanged.connect( lambda _: update_input_validation_style(self.resolution)) self.resolution.textChanged.emit(self.resolution.text()) hbox_resol.addWidget(self.resolution) self.resolution_label = QLabel() hbox_resol.addWidget(self.resolution_label) self.group_box_resol.setLayout(hbox_resol) # Group: Automatic Domain Generator self.group_box_auto_domain = QGroupBox("Grid Extent Calculator") vbox_auto_domain = QVBoxLayout() hbox_auto_domain = QHBoxLayout() domain_pb_set_canvas_extent = QPushButton("Set to Canvas Extent") domain_pb_set_canvas_extent.setObjectName('set_canvas_extent_button') domain_pb_set_layer_extent = QPushButton("Set to Active Layer Extent") domain_pb_set_layer_extent.setObjectName('set_layer_extent_button') vbox_auto_domain.addLayout(hbox_auto_domain) vbox_auto_domain.addWidget(domain_pb_set_canvas_extent) vbox_auto_domain.addWidget(domain_pb_set_layer_extent) self.group_box_auto_domain.setLayout(vbox_auto_domain) # Group: Manual Domain Configuration ## Subgroup: Centre Point grid_center_point = QGridLayout() self.center_lon = add_grid_lineedit(grid_center_point, 0, 'Longitude', LON_VALIDATOR, '°', required=True) self.center_lat = add_grid_lineedit(grid_center_point, 1, 'Latitude', LAT_VALIDATOR, '°', required=True) group_box_centre_point = QGroupBox("Center Point") group_box_centre_point.setLayout(grid_center_point) ## Subgroup: Advanced configuration grid_dims = QGridLayout() self.cols = add_grid_lineedit(grid_dims, 0, 'Horizontal', DIM_VALIDATOR, required=True) self.rows = add_grid_lineedit(grid_dims, 1, 'Vertical', DIM_VALIDATOR, required=True) group_box_dims = QGroupBox("Grid Extent") group_box_dims.setLayout(grid_dims) vbox_manual_domain = QVBoxLayout() vbox_manual_domain.addWidget(group_box_centre_point) vbox_manual_domain.addWidget(group_box_dims) self.group_box_manual_domain = QGroupBox("Advanced Configuration") # TODO: make this section collapsable (default state collapsed) # and change the checkbox to arrows like `setArrowType(Qt.RightArrow)` # TODO: the style should be disabled when the 'advanced configuration' box is disabled (default). self.group_box_manual_domain.setCheckable(True) self.group_box_manual_domain.setChecked(False) self.group_box_manual_domain.setLayout(vbox_manual_domain) for field in [ self.resolution, self.center_lat, self.center_lon, self.rows, self.cols, self.truelat1, self.truelat2 ]: # editingFinished is only emitted on user input, not via programmatic changes. # This is important as we want to avoid re-drawing the bbox many times when several # fields get changed while using the automatic domain generator. field.editingFinished.connect(self.on_change_any_field) # Group Box: Parent Domain self.group_box_parent_domain = QGroupBox("Enable Parenting") self.group_box_parent_domain.setObjectName('group_box_parent_domain') self.group_box_parent_domain.setCheckable(True) self.group_box_parent_domain.setChecked(False) # TODO: As it is for the single domain case the generation of the domain should be automatic. # For now leave placeholder values of '3' for Child to Parent Ratio and '2' for padding. # use `calc_parent_lonlat_from_child` in `routines.py` to calculate the coordinate of the domain given the child domain coordinate, # grid_ratio and padding. hbox_parent_num = QHBoxLayout() hbox_parent_num.addWidget(QLabel('Number of Parent Domains:')) self.parent_spin = QSpinBox() self.parent_spin.setObjectName('parent_spin') self.parent_spin.setRange(1, MAX_PARENTS) hbox_parent_num.addWidget(self.parent_spin) self.group_box_parent_domain.setLayout(hbox_parent_num) self.parent_domains = [] # type: list self.parent_vbox = QVBoxLayout() self.parent_vbox.setSizeConstraint(QLayout.SetMinimumSize) go_to_data_tab_btn = QPushButton('Continue to Datasets') go_to_data_tab_btn.clicked.connect(self.go_to_data_tab) # Tabs dom_mgr_layout = QVBoxLayout() dom_mgr_layout.addWidget(self.gbox_import_export) dom_mgr_layout.addWidget(self.group_box_map_type) dom_mgr_layout.addWidget(self.group_box_resol) dom_mgr_layout.addWidget(self.group_box_auto_domain) dom_mgr_layout.addWidget(self.group_box_manual_domain) dom_mgr_layout.addWidget(self.group_box_parent_domain) dom_mgr_layout.addLayout(self.parent_vbox) dom_mgr_layout.addWidget(go_to_data_tab_btn) self.setLayout(dom_mgr_layout) QMetaObject.connectSlotsByName(self) # trigger event for initial layout self.projection.currentIndexChanged.emit( self.projection.currentIndex()) @property def project(self) -> Project: return self._project @project.setter def project(self, val: Project) -> None: ''' Sets the currently active project. See tab_simulation. ''' self._project = val self.populate_ui_from_project() def populate_ui_from_project(self) -> None: project = self.project try: domains = project.data['domains'] except KeyError: return main_domain = domains[0] idx = self.projection.findData(main_domain['map_proj']) self.projection.setCurrentIndex(idx) try: truelat1 = main_domain['truelat1'] self.truelat1.set_value(truelat1) truelat2 = main_domain['truelat2'] self.truelat2.set_value(truelat2) except KeyError: pass self.resolution.set_value(main_domain['cell_size'][0]) lon, lat = main_domain['center_lonlat'] self.center_lat.set_value(lat) self.center_lon.set_value(lon) cols, rows = main_domain['domain_size'] self.rows.set_value(rows) self.cols.set_value(cols) if len(domains) > 1: self.group_box_parent_domain.setChecked(True) self.parent_spin.setValue(len(domains) - 1) # We call the signal handler explicitly as we need the widgets ready immediately # and otherwise this is delayed until the signals are processed (queueing etc.). self.on_parent_spin_valueChanged(len(domains) - 1) for idx, parent_domain in enumerate(domains[1:]): fields, _ = self.parent_domains[idx] fields = fields['inputs'] field_to_key = { 'ratio': 'parent_cell_size_ratio', 'top': 'padding_top', 'left': 'padding_left', 'right': 'padding_right', 'bottom': 'padding_bottom' } for field_name, key in field_to_key.items(): field = fields[field_name] val = parent_domain[key] field.set_value(val) self.draw_bbox_and_grids(zoom_out=True) @pyqtSlot() def on_import_from_namelist_button_clicked(self) -> None: file_path, _ = QFileDialog.getOpenFileName(caption='Open WPS namelist') if not file_path: return nml = read_namelist(file_path, schema_name='wps') project = convert_wps_nml_to_project(nml, self.project) Broadcast.open_project_from_object.emit(project) @pyqtSlot() def on_export_geogrid_namelist_button_clicked(self): if not self.update_project(): raise UserError('Domain configuration invalid, check fields') file_path, _ = QFileDialog.getSaveFileName(caption='Save WPS namelist as', \ directory='namelist.wps') if not file_path: return wps_namelist = convert_project_to_wps_namelist(self.project) write_namelist(wps_namelist, file_path) @pyqtSlot() def on_set_projection_button_clicked(self): crs = self.create_domain_crs() qgsProject = QgsProject.instance() # type: QgsProject qgsProject.setCrs(get_qgis_crs(crs.proj4)) def create_domain_crs(self) -> CRS: proj = self.get_proj_kwargs() if proj is None: raise UserError('Incomplete projection definition') origin_valid = all( map(lambda w: w.is_valid(), [self.center_lat, self.center_lon])) if origin_valid: origin = LonLat(self.center_lon.value(), self.center_lat.value()) else: origin = LonLat(0, 0) if proj['map_proj'] == 'lambert': crs = CRS.create_lambert(proj['truelat1'], proj['truelat2'], origin) elif proj['map_proj'] == 'lat-lon': crs = CRS.create_lonlat() else: assert False, 'unknown proj: ' + proj['map_proj'] return crs @pyqtSlot() def on_set_canvas_extent_button_clicked(self): if not self.resolution.is_valid(): return canvas = self.iface.mapCanvas() # type: QgsMapCanvas settings = canvas.mapSettings() # type: QgsMapSettings map_crs = settings.destinationCrs( ) # type: QgsCoordinateReferenceSystem extent = canvas.extent() # type: QgsRectangle self.set_domain_to_extent(map_crs, extent) @pyqtSlot() def on_set_layer_extent_button_clicked(self): if not self.resolution.is_valid(): return layer = self.iface.activeLayer() # type: QgsMapLayer if layer is None: print('foo') raise UserError('No layer selected, use the "Layers" panel') layer_crs = layer.crs() # type: QgsCoordinateReferenceSystem extent = layer.extent() # type: QgsRectangle self.set_domain_to_extent(layer_crs, extent) def set_domain_to_extent(self, crs: QgsCoordinateReferenceSystem, extent: QgsRectangle) -> None: resolution = self.resolution.value() bbox = rect_to_bbox(extent) extent_crs = CRS(crs.toProj4()) domain_crs = self.create_domain_crs() domain_srs = domain_crs.srs domain_bbox = domain_crs.transform_bbox(bbox, domain_srs) # TODO disallow creation of bounding box outside projection range (e.g. for lat-lon 360-180) xmin, xmax, ymin, ymax = domain_bbox.minx, domain_bbox.maxx, domain_bbox.miny, domain_bbox.maxy center_x = xmin + (xmax - xmin) / 2 center_y = ymin + (ymax - ymin) / 2 center_lonlat = domain_crs.to_lonlat(Coordinate2D(center_x, center_y)) self.center_lat.set_value(center_lonlat.lat) self.center_lon.set_value(center_lonlat.lon) self.resolution.set_value(resolution) cols = ceil((xmax - xmin) / resolution) rows = ceil((ymax - ymin) / resolution) self.cols.set_value(cols) self.rows.set_value(rows) self.on_change_any_field(zoom_out=True) @pyqtSlot() def on_group_box_parent_domain_clicked(self): if self.group_box_parent_domain.isChecked(): self.add_parent_domain() else: self.parent_spin.setValue(1) while self.parent_domains: self.remove_last_parent_domain() def add_parent_domain(self): idx = len(self.parent_domains) + 1 fields, group_box_parent = create_parent_group_box('Parent ' + str(idx), '?', self.proj_res_unit, required=True) self.parent_vbox.addWidget(group_box_parent) # "If you add a child widget to an already visible widget you must # explicitly show the child to make it visible." # (http://doc.qt.io/qt-5/qwidget.html#QWidget) group_box_parent.show() self.parent_domains.append((fields, group_box_parent)) # After adding/removing widgets, we need to tell Qt to recompute the sizes. # This always has to be done on the widget where the child widgets have been changed, # here self.subtab_parenting (which contains self.parent_vbox). self.adjustSize() for field in fields['inputs'].values(): field.editingFinished.connect(self.on_change_any_field) def remove_last_parent_domain(self): _, group_box_parent = self.parent_domains.pop() group_box_parent.deleteLater() self.parent_vbox.removeWidget(group_box_parent) self.on_change_any_field() @pyqtSlot(int) def on_parent_spin_valueChanged(self, value: int) -> None: count = len(self.parent_domains) for _ in range(value, count): self.remove_last_parent_domain() for _ in range(count, value): self.add_parent_domain() @pyqtSlot(int) def on_projection_currentIndexChanged(self, index: int) -> None: proj_id = self.projection.currentData() is_lambert = proj_id == 'lambert' is_undefined = proj_id == 'undefined' is_lat_lon = proj_id == 'lat-lon' self.domain_pb_set_projection.setDisabled(is_undefined) self.group_box_resol.setDisabled(is_undefined) self.group_box_auto_domain.setDisabled(is_undefined) self.group_box_manual_domain.setDisabled(is_undefined) self.group_box_parent_domain.setDisabled(is_undefined) self.widget_true_lats.setVisible(is_lambert) if is_undefined: self.proj_res_unit = '' elif is_lat_lon: self.proj_res_unit = '°' elif is_lambert: self.proj_res_unit = 'm' self.resolution_label.setText(self.proj_res_unit) # If the projection is changed the parent domains are removed self.group_box_parent_domain.setChecked(False) for _ in self.parent_domains: self.remove_last_parent_domain() self.adjustSize() def get_proj_kwargs(self) -> dict: proj_id = self.projection.currentData() kwargs = {'map_proj': proj_id} if proj_id == 'lambert': valid = all( map(lambda w: w.is_valid(), [self.truelat1, self.truelat2])) if not valid: return None kwargs = { 'map_proj': proj_id, 'truelat1': self.truelat1.value(), 'truelat2': self.truelat2.value(), } return kwargs def update_project(self) -> bool: proj_kwargs = self.get_proj_kwargs() if proj_kwargs is None: return False valid = all( map(lambda w: w.is_valid(), [ self.center_lat, self.center_lon, self.resolution, self.cols, self.rows ])) if not valid: return False center_lonlat = LonLat(lon=self.center_lon.value(), lat=self.center_lat.value()) resolution = self.resolution.value() domain_size = (self.cols.value(), self.rows.value()) parent_domains = [] for parent_domain in self.parent_domains: fields, _ = parent_domain inputs = fields['inputs'] valid = all(map(lambda w: w.is_valid(), inputs.values())) if not valid: return False ratio, top, left, right, bottom = [ inputs[name].value() for name in ['ratio', 'top', 'left', 'right', 'bottom'] ] parent_domains.append({ 'parent_cell_size_ratio': ratio, 'padding_left': left, 'padding_right': right, 'padding_bottom': bottom, 'padding_top': top }) self.project.set_domains(cell_size=(resolution, resolution), domain_size=domain_size, center_lonlat=center_lonlat, parent_domains=parent_domains, **proj_kwargs) return True def on_change_any_field(self, zoom_out=False): if not self.update_project(): return domains = self.project.data['domains'] # update main domain size as it may have been adjusted main_domain_size = domains[0]['domain_size'] self.cols.set_value(main_domain_size[0]) self.rows.set_value(main_domain_size[1]) for (fields, _), domain in zip(self.parent_domains, domains[1:]): # update the parent resolutions res_label = fields['other']['resolution'] res_label.setText( HORIZONTAL_RESOLUTION_LABEL.format( resolution=domain['cell_size'][0], unit=self.proj_res_unit)) # update any padding as it may have been adjusted for name in ['left', 'right', 'top', 'bottom']: field = fields['inputs'][name] field.set_value(domain['padding_' + name]) self.draw_bbox_and_grids(zoom_out) def draw_bbox_and_grids(self, zoom_out: bool) -> None: project = self.project update_domain_grid_layers(project) update_domain_outline_layers(self.iface.mapCanvas(), project, zoom_out=zoom_out)
class GuiBuildNovel(QDialog): FMT_PDF = 1 # Print to PDF FMT_ODT = 2 # Open Document file FMT_FODT = 3 # Flat Open Document file FMT_HTM = 4 # HTML5 FMT_NWD = 5 # nW Markdown FMT_MD = 6 # Standard Markdown FMT_GH = 7 # GitHub Markdown FMT_JSON_H = 8 # HTML5 wrapped in JSON FMT_JSON_M = 9 # nW Markdown wrapped in JSON def __init__(self, mainGui): QDialog.__init__(self, mainGui) logger.debug("Initialising GuiBuildNovel ...") self.setObjectName("GuiBuildNovel") self.mainConf = novelwriter.CONFIG self.mainGui = mainGui self.mainTheme = mainGui.mainTheme self.theProject = mainGui.theProject self.htmlText = [] # List of html documents self.htmlStyle = [] # List of html styles self.htmlSize = 0 # Size of the html document self.buildTime = 0 # The timestamp of the last build self.setWindowTitle(self.tr("Build Novel Project")) self.setMinimumWidth(self.mainConf.pxInt(700)) self.setMinimumHeight(self.mainConf.pxInt(600)) pOptions = self.theProject.options self.resize( self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth", 900)), self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800)) ) self.docView = GuiBuildNovelDocView(self, self.theProject) hS = self.mainTheme.fontPixelSize wS = 2*hS # Title Formats # ============= self.titleGroup = QGroupBox(self.tr("Title Formats for Novel Files"), self) self.titleForm = QGridLayout(self) self.titleGroup.setLayout(self.titleForm) fmtHelp = "<br>".join([ "<b>%s</b>" % self.tr("Formatting Codes:"), self.tr("{0} for the title as set in the document").format(r"%title%"), self.tr("{0} for chapter number (1, 2, 3)").format(r"%ch%"), self.tr("{0} for chapter number as a word (one, two)").format(r"%chw%"), self.tr("{0} for chapter number in upper case Roman").format(r"%chI%"), self.tr("{0} for chapter number in lower case Roman").format(r"%chi%"), self.tr("{0} for scene number within chapter").format(r"%sc%"), self.tr("{0} for scene number within novel").format(r"%sca%"), ]) fmtScHelp = "<br><br>%s" % self.tr( "Leave blank to skip this heading, or set to a static text, like " "for instance '{0}', to make a separator. The separator will " "be centred automatically and only appear between sections of " "the same type." ).format("* * *") xFmt = self.mainConf.pxInt(100) self.fmtTitle = QLineEdit() self.fmtTitle.setMaxLength(200) self.fmtTitle.setMinimumWidth(xFmt) self.fmtTitle.setToolTip(fmtHelp) self.fmtTitle.setText( self._reFmtCodes(self.theProject.titleFormat["title"]) ) self.fmtChapter = QLineEdit() self.fmtChapter.setMaxLength(200) self.fmtChapter.setMinimumWidth(xFmt) self.fmtChapter.setToolTip(fmtHelp) self.fmtChapter.setText( self._reFmtCodes(self.theProject.titleFormat["chapter"]) ) self.fmtUnnumbered = QLineEdit() self.fmtUnnumbered.setMaxLength(200) self.fmtUnnumbered.setMinimumWidth(xFmt) self.fmtUnnumbered.setToolTip(fmtHelp) self.fmtUnnumbered.setText( self._reFmtCodes(self.theProject.titleFormat["unnumbered"]) ) self.fmtScene = QLineEdit() self.fmtScene.setMaxLength(200) self.fmtScene.setMinimumWidth(xFmt) self.fmtScene.setToolTip(fmtHelp + fmtScHelp) self.fmtScene.setText( self._reFmtCodes(self.theProject.titleFormat["scene"]) ) self.fmtSection = QLineEdit() self.fmtSection.setMaxLength(200) self.fmtSection.setMinimumWidth(xFmt) self.fmtSection.setToolTip(fmtHelp + fmtScHelp) self.fmtSection.setText( self._reFmtCodes(self.theProject.titleFormat["section"]) ) self.buildLang = QComboBox() self.buildLang.setMinimumWidth(xFmt) theLangs = self.mainConf.listLanguages(self.mainConf.LANG_PROJ) self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None") for langID, langName in theLangs: self.buildLang.addItem(langName, langID) langIdx = self.buildLang.findData(self.theProject.projLang) if langIdx != -1: self.buildLang.setCurrentIndex(langIdx) self.hideScene = QSwitch(width=wS, height=hS) self.hideScene.setChecked( pOptions.getBool("GuiBuildNovel", "hideScene", False) ) self.hideSection = QSwitch(width=wS, height=hS) self.hideSection.setChecked( pOptions.getBool("GuiBuildNovel", "hideSection", True) ) # Wrapper boxes due to QGridView and QLineEdit expand bug self.boxTitle = QHBoxLayout() self.boxTitle.addWidget(self.fmtTitle) self.boxChapter = QHBoxLayout() self.boxChapter.addWidget(self.fmtChapter) self.boxUnnumb = QHBoxLayout() self.boxUnnumb.addWidget(self.fmtUnnumbered) self.boxScene = QHBoxLayout() self.boxScene.addWidget(self.fmtScene) self.boxSection = QHBoxLayout() self.boxSection.addWidget(self.fmtSection) titleLabel = QLabel(self.tr("Title")) chapterLabel = QLabel(self.tr("Chapter")) unnumbLabel = QLabel(self.tr("Unnumbered")) sceneLabel = QLabel(self.tr("Scene")) sectionLabel = QLabel(self.tr("Section")) langLabel = QLabel(self.tr("Language")) hSceneLabel = QLabel(self.tr("Hide scene")) hSectionLabel = QLabel(self.tr("Hide section")) self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(chapterLabel, 1, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxChapter, 1, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(unnumbLabel, 2, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxUnnumb, 2, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(sceneLabel, 3, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(sectionLabel, 4, 0, 1, 1, Qt.AlignLeft) self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(langLabel, 5, 0, 1, 1, Qt.AlignLeft) self.titleForm.addWidget(self.buildLang, 5, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(hSceneLabel, 6, 0, 1, 1, Qt.AlignLeft) self.titleForm.addWidget(self.hideScene, 6, 1, 1, 1, Qt.AlignRight) self.titleForm.addWidget(hSectionLabel, 7, 0, 1, 1, Qt.AlignLeft) self.titleForm.addWidget(self.hideSection, 7, 1, 1, 1, Qt.AlignRight) self.titleForm.setColumnStretch(0, 0) self.titleForm.setColumnStretch(1, 1) # Font Options # ============ self.fontGroup = QGroupBox(self.tr("Font Options"), self) self.fontForm = QGridLayout(self) self.fontGroup.setLayout(self.fontForm) # Font Family self.textFont = QLineEdit() self.textFont.setReadOnly(True) self.textFont.setMinimumWidth(xFmt) self.textFont.setText( pOptions.getString("GuiBuildNovel", "textFont", self.mainConf.textFont) ) self.fontButton = QPushButton("...") self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("..."))) self.fontButton.clicked.connect(self._selectFont) self.textSize = QSpinBox(self) self.textSize.setFixedWidth(6*self.mainTheme.textNWidth) self.textSize.setMinimum(6) self.textSize.setMaximum(72) self.textSize.setSingleStep(1) self.textSize.setValue( pOptions.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize) ) self.lineHeight = QDoubleSpinBox(self) self.lineHeight.setFixedWidth(6*self.mainTheme.textNWidth) self.lineHeight.setMinimum(0.8) self.lineHeight.setMaximum(3.0) self.lineHeight.setSingleStep(0.05) self.lineHeight.setDecimals(2) self.lineHeight.setValue( pOptions.getFloat("GuiBuildNovel", "lineHeight", 1.15) ) # Wrapper box due to QGridView and QLineEdit expand bug self.boxFont = QHBoxLayout() self.boxFont.addWidget(self.textFont) fontFamilyLabel = QLabel(self.tr("Font family")) fontSizeLabel = QLabel(self.tr("Font size")) lineHeightLabel = QLabel(self.tr("Line height")) justifyLabel = QLabel(self.tr("Justify text")) stylingLabel = QLabel(self.tr("Disable styling")) self.fontForm.addWidget(fontFamilyLabel, 0, 0, 1, 1, Qt.AlignLeft) self.fontForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight) self.fontForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) self.fontForm.addWidget(fontSizeLabel, 1, 0, 1, 1, Qt.AlignLeft) self.fontForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) self.fontForm.addWidget(lineHeightLabel, 2, 0, 1, 1, Qt.AlignLeft) self.fontForm.addWidget(self.lineHeight, 2, 1, 1, 2, Qt.AlignRight) self.fontForm.setColumnStretch(0, 0) self.fontForm.setColumnStretch(1, 1) self.fontForm.setColumnStretch(2, 0) # Styling Options # =============== self.styleGroup = QGroupBox(self.tr("Styling Options"), self) self.styleForm = QGridLayout(self) self.styleGroup.setLayout(self.styleForm) self.justifyText = QSwitch(width=wS, height=hS) self.justifyText.setChecked( pOptions.getBool("GuiBuildNovel", "justifyText", False) ) self.noStyling = QSwitch(width=wS, height=hS) self.noStyling.setChecked( pOptions.getBool("GuiBuildNovel", "noStyling", False) ) self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft) self.styleForm.addWidget(self.justifyText, 1, 1, 1, 2, Qt.AlignRight) self.styleForm.addWidget(stylingLabel, 2, 0, 1, 1, Qt.AlignLeft) self.styleForm.addWidget(self.noStyling, 2, 1, 1, 2, Qt.AlignRight) self.styleForm.setColumnStretch(0, 0) self.styleForm.setColumnStretch(1, 1) # Include Options # =============== self.textGroup = QGroupBox(self.tr("Include Options"), self) self.textForm = QGridLayout(self) self.textGroup.setLayout(self.textForm) self.includeSynopsis = QSwitch(width=wS, height=hS) self.includeSynopsis.setChecked( pOptions.getBool("GuiBuildNovel", "incSynopsis", False) ) self.includeComments = QSwitch(width=wS, height=hS) self.includeComments.setChecked( pOptions.getBool("GuiBuildNovel", "incComments", False) ) self.includeKeywords = QSwitch(width=wS, height=hS) self.includeKeywords.setChecked( pOptions.getBool("GuiBuildNovel", "incKeywords", False) ) self.includeBody = QSwitch(width=wS, height=hS) self.includeBody.setChecked( pOptions.getBool("GuiBuildNovel", "incBodyText", True) ) synopsisLabel = QLabel(self.tr("Include synopsis")) commentsLabel = QLabel(self.tr("Include comments")) keywordsLabel = QLabel(self.tr("Include keywords")) bodyLabel = QLabel(self.tr("Include body text")) self.textForm.addWidget(synopsisLabel, 0, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(commentsLabel, 1, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(keywordsLabel, 2, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight) self.textForm.addWidget(bodyLabel, 3, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.includeBody, 3, 1, 1, 1, Qt.AlignRight) self.textForm.setColumnStretch(0, 1) self.textForm.setColumnStretch(1, 0) # File Filter Options # =================== self.fileGroup = QGroupBox(self.tr("File Filter Options"), self) self.fileForm = QGridLayout(self) self.fileGroup.setLayout(self.fileForm) self.novelFiles = QSwitch(width=wS, height=hS) self.novelFiles.setChecked( pOptions.getBool("GuiBuildNovel", "addNovel", True) ) self.noteFiles = QSwitch(width=wS, height=hS) self.noteFiles.setChecked( pOptions.getBool("GuiBuildNovel", "addNotes", False) ) self.ignoreFlag = QSwitch(width=wS, height=hS) self.ignoreFlag.setChecked( pOptions.getBool("GuiBuildNovel", "ignoreFlag", False) ) novelLabel = QLabel(self.tr("Include novel files")) notesLabel = QLabel(self.tr("Include note files")) exportLabel = QLabel(self.tr("Ignore export flag")) self.fileForm.addWidget(novelLabel, 0, 0, 1, 1, Qt.AlignLeft) self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight) self.fileForm.addWidget(notesLabel, 1, 0, 1, 1, Qt.AlignLeft) self.fileForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight) self.fileForm.addWidget(exportLabel, 2, 0, 1, 1, Qt.AlignLeft) self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight) self.fileForm.setColumnStretch(0, 1) self.fileForm.setColumnStretch(1, 0) # Export Options # ============== self.exportGroup = QGroupBox(self.tr("Export Options"), self) self.exportForm = QGridLayout(self) self.exportGroup.setLayout(self.exportForm) self.replaceTabs = QSwitch(width=wS, height=hS) self.replaceTabs.setChecked( pOptions.getBool("GuiBuildNovel", "replaceTabs", False) ) self.replaceUCode = QSwitch(width=wS, height=hS) self.replaceUCode.setChecked( pOptions.getBool("GuiBuildNovel", "replaceUCode", False) ) tabsLabel = QLabel(self.tr("Replace tabs with spaces")) uCodeLabel = QLabel(self.tr("Replace Unicode in HTML")) self.exportForm.addWidget(tabsLabel, 0, 0, 1, 1, Qt.AlignLeft) self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight) self.exportForm.addWidget(uCodeLabel, 1, 0, 1, 1, Qt.AlignLeft) self.exportForm.addWidget(self.replaceUCode, 1, 1, 1, 1, Qt.AlignRight) self.exportForm.setColumnStretch(0, 1) self.exportForm.setColumnStretch(1, 0) # Build Button # ============ self.buildProgress = QProgressBar() self.buildNovel = QPushButton(self.tr("Build Preview")) self.buildNovel.clicked.connect(self._buildPreview) # Action Buttons # ============== self.buttonBox = QHBoxLayout() # Printing self.printMenu = QMenu(self) self.btnPrint = QPushButton(self.tr("Print")) self.btnPrint.setMenu(self.printMenu) self.printSend = QAction(self.tr("Print Preview"), self) self.printSend.triggered.connect(self._printDocument) self.printMenu.addAction(self.printSend) self.printFile = QAction(self.tr("Print to PDF"), self) self.printFile.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) self.printMenu.addAction(self.printFile) # Saving to File self.saveMenu = QMenu(self) self.btnSave = QPushButton(self.tr("Save As")) self.btnSave.setMenu(self.saveMenu) self.saveODT = QAction(self.tr("Open Document (.odt)"), self) self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT)) self.saveMenu.addAction(self.saveODT) self.saveFODT = QAction(self.tr("Flat Open Document (.fodt)"), self) self.saveFODT.triggered.connect(lambda: self._saveDocument(self.FMT_FODT)) self.saveMenu.addAction(self.saveFODT) self.saveHTM = QAction(self.tr("novelWriter HTML (.htm)"), self) self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM)) self.saveMenu.addAction(self.saveHTM) self.saveNWD = QAction(self.tr("novelWriter Markdown (.nwd)"), self) self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD)) self.saveMenu.addAction(self.saveNWD) self.saveMD = QAction(self.tr("Standard Markdown (.md)"), self) self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) self.saveMenu.addAction(self.saveMD) self.saveGH = QAction(self.tr("GitHub Markdown (.md)"), self) self.saveGH.triggered.connect(lambda: self._saveDocument(self.FMT_GH)) self.saveMenu.addAction(self.saveGH) self.saveJsonH = QAction(self.tr("JSON + novelWriter HTML (.json)"), self) self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H)) self.saveMenu.addAction(self.saveJsonH) self.saveJsonM = QAction(self.tr("JSON + novelWriter Markdown (.json)"), self) self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M)) self.saveMenu.addAction(self.saveJsonM) self.btnClose = QPushButton(self.tr("Close")) self.btnClose.clicked.connect(self._doClose) self.buttonBox.addWidget(self.btnSave) self.buttonBox.addWidget(self.btnPrint) self.buttonBox.addWidget(self.btnClose) self.buttonBox.setSpacing(self.mainConf.pxInt(4)) # Assemble GUI # ============ # Splitter Position boxWidth = self.mainConf.pxInt(350) boxWidth = pOptions.getInt("GuiBuildNovel", "boxWidth", boxWidth) docWidth = max(self.width() - boxWidth, 100) docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth) # The Tool Box self.toolsBox = QVBoxLayout() self.toolsBox.addWidget(self.titleGroup) self.toolsBox.addWidget(self.fontGroup) self.toolsBox.addWidget(self.styleGroup) self.toolsBox.addWidget(self.textGroup) self.toolsBox.addWidget(self.fileGroup) self.toolsBox.addWidget(self.exportGroup) self.toolsBox.addStretch(1) # Tool Box Wrapper Widget self.toolsWidget = QWidget() self.toolsWidget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.toolsWidget.setLayout(self.toolsBox) # Tool Box Scroll Area self.toolsArea = QScrollArea() self.toolsArea.setMinimumWidth(self.mainConf.pxInt(250)) self.toolsArea.setWidgetResizable(True) self.toolsArea.setWidget(self.toolsWidget) if self.mainConf.hideVScroll: self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) else: self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) if self.mainConf.hideHScroll: self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) else: self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Tools and Buttons Layout tSp = self.mainConf.pxInt(8) self.innerBox = QVBoxLayout() self.innerBox.addWidget(self.toolsArea) self.innerBox.addSpacing(tSp) self.innerBox.addWidget(self.buildProgress) self.innerBox.addWidget(self.buildNovel) self.innerBox.addSpacing(tSp) self.innerBox.addLayout(self.buttonBox) # Tools and Buttons Wrapper Widget self.innerWidget = QWidget() self.innerWidget.setLayout(self.innerBox) # Main Dialog Splitter self.mainSplit = QSplitter(Qt.Horizontal) self.mainSplit.addWidget(self.innerWidget) self.mainSplit.addWidget(self.docView) self.mainSplit.setSizes([boxWidth, docWidth]) self.idxSettings = self.mainSplit.indexOf(self.innerWidget) self.idxDocument = self.mainSplit.indexOf(self.docView) self.mainSplit.setCollapsible(self.idxSettings, False) self.mainSplit.setCollapsible(self.idxDocument, False) # Outer Layout self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.mainSplit) self.setLayout(self.outerBox) self.buildNovel.setFocus() logger.debug("GuiBuildNovel initialisation complete") return def viewCachedDoc(self): """Load the previously generated document from cache. """ if self._loadCache(): textFont = self.textFont.text() textSize = self.textSize.value() justifyText = self.justifyText.isChecked() self.docView.setTextFont(textFont, textSize) self.docView.setJustify(justifyText) if self.noStyling.isChecked(): self.docView.clearStyleSheet() else: self.docView.setStyleSheet(self.htmlStyle) htmlSize = sum([len(x) for x in self.htmlText]) if htmlSize < nwConst.MAX_BUILDSIZE: qApp.processEvents() self.docView.setContent(self.htmlText, self.buildTime) else: self.docView.setText( self.tr("Failed to generate preview. The result is too big.") ) else: self.htmlText = [] self.htmlStyle = [] self.buildTime = 0 return False return True ## # Slots and Related ## def _buildPreview(self): """Build a preview of the project in the document viewer. """ # Get Settings justifyText = self.justifyText.isChecked() noStyling = self.noStyling.isChecked() textFont = self.textFont.text() textSize = self.textSize.value() replaceTabs = self.replaceTabs.isChecked() self.htmlText = [] self.htmlStyle = [] self.htmlSize = 0 # Build Preview # ============= makeHtml = ToHtml(self.theProject) self._doBuild(makeHtml, isPreview=True) if replaceTabs: makeHtml.replaceTabs() self.htmlText = makeHtml.fullHTML self.htmlStyle = makeHtml.getStyleSheet() self.htmlSize = makeHtml.getFullResultSize() self.buildTime = int(time()) # Load Preview # ============ self.docView.setTextFont(textFont, textSize) self.docView.setJustify(justifyText) if noStyling: self.docView.clearStyleSheet() else: self.docView.setStyleSheet(self.htmlStyle) if self.htmlSize < nwConst.MAX_BUILDSIZE: self.docView.setContent(self.htmlText, self.buildTime) else: self.docView.setText( "Failed to generate preview. The result is too big." ) self._saveCache() return def _doBuild(self, bldObj, isPreview=False, doConvert=True): """Rund the build with a specific build object. """ tStart = int(time()) # Get Settings fmtTitle = self.fmtTitle.text() fmtChapter = self.fmtChapter.text() fmtUnnumbered = self.fmtUnnumbered.text() fmtScene = self.fmtScene.text() fmtSection = self.fmtSection.text() buildLang = self.buildLang.currentData() hideScene = self.hideScene.isChecked() hideSection = self.hideSection.isChecked() textFont = self.textFont.text() textSize = self.textSize.value() lineHeight = self.lineHeight.value() justifyText = self.justifyText.isChecked() noStyling = self.noStyling.isChecked() incSynopsis = self.includeSynopsis.isChecked() incComments = self.includeComments.isChecked() incKeywords = self.includeKeywords.isChecked() novelFiles = self.novelFiles.isChecked() noteFiles = self.noteFiles.isChecked() ignoreFlag = self.ignoreFlag.isChecked() includeBody = self.includeBody.isChecked() replaceUCode = self.replaceUCode.isChecked() # The language lookup dict is reloaded if needed self.theProject.setProjectLang(buildLang) # Get font information fontInfo = QFontInfo(QFont(textFont, textSize)) textFixed = fontInfo.fixedPitch() isHtml = isinstance(bldObj, ToHtml) isOdt = isinstance(bldObj, ToOdt) bldObj.setTitleFormat(fmtTitle) bldObj.setChapterFormat(fmtChapter) bldObj.setUnNumberedFormat(fmtUnnumbered) bldObj.setSceneFormat(fmtScene, hideScene) bldObj.setSectionFormat(fmtSection, hideSection) bldObj.setFont(textFont, textSize, textFixed) bldObj.setJustify(justifyText) bldObj.setLineHeight(lineHeight) bldObj.setSynopsis(incSynopsis) bldObj.setComments(incComments) bldObj.setKeywords(incKeywords) bldObj.setBodyText(includeBody) if isHtml: bldObj.setStyles(not noStyling) bldObj.setReplaceUnicode(replaceUCode) if isOdt: bldObj.setColourHeaders(not noStyling) bldObj.setLanguage(buildLang) bldObj.initDocument() # Make sure the project and document is up to date self.mainGui.saveDocument() self.buildProgress.setMaximum(len(self.theProject.tree)) self.buildProgress.setValue(0) for nItt, tItem in enumerate(self.theProject.tree): noteRoot = noteFiles noteRoot &= tItem.itemType == nwItemType.ROOT noteRoot &= tItem.itemClass != nwItemClass.NOVEL noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE try: if noteRoot: # Add headers for root folders of notes bldObj.addRootHeading(tItem.itemHandle) if doConvert: bldObj.doConvert() elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag): bldObj.setText(tItem.itemHandle) bldObj.doPreProcessing() bldObj.tokenizeText() bldObj.doHeaders() if doConvert: bldObj.doConvert() bldObj.doPostProcessing() except Exception: logger.error("Failed to build document '%s'", tItem.itemHandle) logException() if isPreview: self.docView.setText(( "Failed to generate preview. " "Document with title '%s' could not be parsed." ) % tItem.itemName) return False # Update progress bar, also for skipped items self.buildProgress.setValue(nItt+1) if isOdt: bldObj.closeDocument() tEnd = int(time()) logger.debug("Built project in %.3f ms", 1000*(tEnd - tStart)) if bldObj.errData: self.mainGui.makeAlert([ self.tr("There were problems when building the project:") ] + bldObj.errData, nwAlert.ERROR) return def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag): """This function checks whether a file should be included in the export or not. For standard note and novel files, this is controlled by the options selected by the user. For other files classified as non-exportable, a few checks must be made, and the following are not: * Items that are not actual files. * Items that have been orphaned which are tagged as NO_LAYOUT and NO_CLASS. * Items that appear in the TRASH folder or have parent set to None (orphaned files). """ if theItem is None: return False if not (theItem.isExported or ignoreFlag): return False isNone = theItem.itemType != nwItemType.FILE isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT isNone |= theItem.isInactive() isNone |= theItem.itemParent is None isNote = theItem.itemLayout == nwItemLayout.NOTE isNovel = not isNone and not isNote if isNone: return False if isNote and not noteFiles: return False if isNovel and not novelFiles: return False return True def _saveDocument(self, theFmt): """Save the document to various formats. """ replaceTabs = self.replaceTabs.isChecked() fileExt = "" textFmt = "" # Settings # ======== if theFmt == self.FMT_ODT: fileExt = "odt" textFmt = self.tr("Open Document") elif theFmt == self.FMT_FODT: fileExt = "fodt" textFmt = self.tr("Flat Open Document") elif theFmt == self.FMT_HTM: fileExt = "htm" textFmt = self.tr("Plain HTML") elif theFmt == self.FMT_NWD: fileExt = "nwd" textFmt = self.tr("novelWriter Markdown") elif theFmt == self.FMT_MD: fileExt = "md" textFmt = self.tr("Standard Markdown") elif theFmt == self.FMT_GH: fileExt = "md" textFmt = self.tr("GitHub Markdown") elif theFmt == self.FMT_JSON_H: fileExt = "json" textFmt = self.tr("JSON + novelWriter HTML") elif theFmt == self.FMT_JSON_M: fileExt = "json" textFmt = self.tr("JSON + novelWriter Markdown") elif theFmt == self.FMT_PDF: fileExt = "pdf" textFmt = self.tr("PDF") else: return False # Generate File Name # ================== cleanName = makeFileNameSafe(self.theProject.projName) fileName = "%s.%s" % (cleanName, fileExt) saveDir = self.mainConf.lastPath if not os.path.isdir(saveDir): saveDir = os.path.expanduser("~") savePath = os.path.join(saveDir, fileName) savePath, _ = QFileDialog.getSaveFileName( self, self.tr("Save Document As"), savePath ) if not savePath: return False self.mainConf.setLastPath(savePath) # Build and Write # =============== errMsg = "" wSuccess = False if theFmt == self.FMT_ODT: makeOdt = ToOdt(self.theProject, isFlat=False) self._doBuild(makeOdt) try: makeOdt.saveOpenDocText(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_FODT: makeOdt = ToOdt(self.theProject, isFlat=True) self._doBuild(makeOdt) try: makeOdt.saveFlatXML(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_HTM: makeHtml = ToHtml(self.theProject) self._doBuild(makeHtml) if replaceTabs: makeHtml.replaceTabs() try: makeHtml.saveHTML5(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_NWD: makeNwd = ToMarkdown(self.theProject) makeNwd.setKeepMarkdown(True) self._doBuild(makeNwd, doConvert=False) if replaceTabs: makeNwd.replaceTabs(spaceChar=" ") try: makeNwd.saveRawMarkdown(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt in (self.FMT_MD, self.FMT_GH): makeMd = ToMarkdown(self.theProject) if theFmt == self.FMT_GH: makeMd.setGitHubMarkdown() else: makeMd.setStandardMarkdown() self._doBuild(makeMd) if replaceTabs: makeMd.replaceTabs(nSpaces=4, spaceChar=" ") try: makeMd.saveMarkdown(savePath) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M: jsonData = { "meta": { "workingTitle": self.theProject.projName, "novelTitle": self.theProject.bookTitle, "authors": self.theProject.bookAuthors, "buildTime": self.buildTime, } } if theFmt == self.FMT_JSON_H: makeHtml = ToHtml(self.theProject) self._doBuild(makeHtml) if replaceTabs: makeHtml.replaceTabs() theBody = [] for htmlPage in makeHtml.fullHTML: theBody.append(htmlPage.rstrip("\n").split("\n")) jsonData["text"] = { "css": self.htmlStyle, "html": theBody, } elif theFmt == self.FMT_JSON_M: makeMd = ToHtml(self.theProject) makeMd.setKeepMarkdown(True) self._doBuild(makeMd, doConvert=False) if replaceTabs: makeMd.replaceTabs(spaceChar=" ") theBody = [] for nwdPage in makeMd.theMarkdown: theBody.append(nwdPage.split("\n")) jsonData["text"] = { "nwd": theBody, } try: with open(savePath, mode="w", encoding="utf-8") as outFile: outFile.write(json.dumps(jsonData, indent=2)) wSuccess = True except Exception as exc: errMsg = formatException(exc) elif theFmt == self.FMT_PDF: try: thePrinter = QPrinter() thePrinter.setOutputFormat(QPrinter.PdfFormat) thePrinter.setOrientation(QPrinter.Portrait) thePrinter.setDuplex(QPrinter.DuplexLongSide) thePrinter.setFontEmbeddingEnabled(True) thePrinter.setColorMode(QPrinter.Color) thePrinter.setOutputFileName(savePath) self.docView.document().print(thePrinter) wSuccess = True except Exception as exc: errMsg = formatException(exc) else: # If the if statements above and here match, it should not # be possible to reach this else statement. return False # pragma: no cover # Report to User # ============== if wSuccess: self.mainGui.makeAlert([ self.tr("{0} file successfully written to:").format(textFmt), savePath ], nwAlert.INFO) else: self.mainGui.makeAlert(self.tr( "Failed to write {0} file. {1}" ).format(textFmt, errMsg), nwAlert.ERROR) return wSuccess def _printDocument(self): """Open the print preview dialog. """ thePreview = QPrintPreviewDialog(self) thePreview.paintRequested.connect(self._doPrintPreview) thePreview.exec_() return def _doPrintPreview(self, thePrinter): """Connect the print preview painter to the document viewer. """ qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) thePrinter.setOrientation(QPrinter.Portrait) self.docView.document().print(thePrinter) qApp.restoreOverrideCursor() return def _selectFont(self): """Open the QFontDialog and set a font for the font style. """ currFont = QFont() currFont.setFamily(self.textFont.text()) currFont.setPointSize(self.textSize.value()) theFont, theStatus = QFontDialog.getFont(currFont, self) if theStatus: self.textFont.setText(theFont.family()) self.textSize.setValue(theFont.pointSize()) self.raise_() # Move the dialog to front (fixes a bug on macOS) return def _loadCache(self): """Save the current data to cache. """ buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) dataCount = 0 if os.path.isfile(buildCache): logger.debug("Loading build cache") try: with open(buildCache, mode="r", encoding="utf-8") as inFile: theJson = inFile.read() theData = json.loads(theJson) except Exception: logger.error("Failed to load build cache") logException() return False if "buildTime" in theData.keys(): self.buildTime = theData["buildTime"] if "htmlStyle" in theData.keys(): self.htmlStyle = theData["htmlStyle"] dataCount += 1 if "htmlText" in theData.keys(): self.htmlText = theData["htmlText"] dataCount += 1 return dataCount == 2 def _saveCache(self): """Save the current data to cache. """ buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) logger.debug("Saving build cache") try: with open(buildCache, mode="w+", encoding="utf-8") as outFile: outFile.write(json.dumps({ "buildTime": self.buildTime, "htmlStyle": self.htmlStyle, "htmlText": self.htmlText, }, indent=2)) except Exception: logger.error("Failed to save build cache") logException() return False return True def _doClose(self): """Close button was clicked. """ self.close() return ## # Events ## def closeEvent(self, theEvent): """Capture the user closing the window so we can save settings. """ self._saveSettings() self.docView.clear() theEvent.accept() return ## # Internal Functions ## def _saveSettings(self): """Save the various user settings. """ logger.debug("Saving GuiBuildNovel settings") # Formatting self.theProject.setTitleFormat({ "title": self.fmtTitle.text().strip(), "chapter": self.fmtChapter.text().strip(), "unnumbered": self.fmtUnnumbered.text().strip(), "scene": self.fmtScene.text().strip(), "section": self.fmtSection.text().strip(), }) buildLang = self.buildLang.currentData() hideScene = self.hideScene.isChecked() hideSection = self.hideSection.isChecked() winWidth = self.mainConf.rpxInt(self.width()) winHeight = self.mainConf.rpxInt(self.height()) justifyText = self.justifyText.isChecked() noStyling = self.noStyling.isChecked() textFont = self.textFont.text() textSize = self.textSize.value() lineHeight = self.lineHeight.value() novelFiles = self.novelFiles.isChecked() noteFiles = self.noteFiles.isChecked() ignoreFlag = self.ignoreFlag.isChecked() incSynopsis = self.includeSynopsis.isChecked() incComments = self.includeComments.isChecked() incKeywords = self.includeKeywords.isChecked() incBodyText = self.includeBody.isChecked() replaceTabs = self.replaceTabs.isChecked() replaceUCode = self.replaceUCode.isChecked() mainSplit = self.mainSplit.sizes() boxWidth = self.mainConf.rpxInt(mainSplit[0]) docWidth = self.mainConf.rpxInt(mainSplit[1]) self.theProject.setProjectLang(buildLang) # GUI Settings pOptions = self.theProject.options pOptions.setValue("GuiBuildNovel", "hideScene", hideScene) pOptions.setValue("GuiBuildNovel", "hideSection", hideSection) pOptions.setValue("GuiBuildNovel", "winWidth", winWidth) pOptions.setValue("GuiBuildNovel", "winHeight", winHeight) pOptions.setValue("GuiBuildNovel", "boxWidth", boxWidth) pOptions.setValue("GuiBuildNovel", "docWidth", docWidth) pOptions.setValue("GuiBuildNovel", "justifyText", justifyText) pOptions.setValue("GuiBuildNovel", "noStyling", noStyling) pOptions.setValue("GuiBuildNovel", "textFont", textFont) pOptions.setValue("GuiBuildNovel", "textSize", textSize) pOptions.setValue("GuiBuildNovel", "lineHeight", lineHeight) pOptions.setValue("GuiBuildNovel", "addNovel", novelFiles) pOptions.setValue("GuiBuildNovel", "addNotes", noteFiles) pOptions.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag) pOptions.setValue("GuiBuildNovel", "incSynopsis", incSynopsis) pOptions.setValue("GuiBuildNovel", "incComments", incComments) pOptions.setValue("GuiBuildNovel", "incKeywords", incKeywords) pOptions.setValue("GuiBuildNovel", "incBodyText", incBodyText) pOptions.setValue("GuiBuildNovel", "replaceTabs", replaceTabs) pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode) pOptions.saveSettings() return def _reFmtCodes(self, theFormat): """Translates old formatting codes to new ones. """ theFormat = theFormat.replace(r"%chnum%", r"%ch%") theFormat = theFormat.replace(r"%scnum%", r"%sc%") theFormat = theFormat.replace(r"%scabsnum%", r"%sca%") theFormat = theFormat.replace(r"%chnumword%", r"%chw%") return theFormat
class fullScreenEditor(QWidget): def __init__(self, index, parent=None): QWidget.__init__(self, parent) self._background = None self._index = index self._theme = findThemePath(settings.fullScreenTheme) self._themeDatas = loadThemeDatas(self._theme) self.setMouseTracking(True) self._geometries = {} # Text editor self.editor = MDEditView(self, index=index, spellcheck=settings.spellcheck, highlighting=True, dict=settings.dict) self.editor.setFrameStyle(QFrame.NoFrame) self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.editor.installEventFilter(self) self.editor.setMouseTracking(True) self.editor.setVerticalScrollBar(myScrollBar()) self.scrollBar = self.editor.verticalScrollBar() self.scrollBar.setParent(self) # Top Panel self.topPanel = myPanel(parent=self) # self.topPanel.layout().addStretch(1) # Spell checking if enchant: self.btnSpellCheck = QPushButton(self) self.btnSpellCheck.setFlat(True) self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling")) self.btnSpellCheck.setCheckable(True) self.btnSpellCheck.setChecked(self.editor.spellcheck) self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck) else: self.btnSpellCheck = None # Navigation Buttons self.btnPrevious = QPushButton(self) self.btnPrevious.setFlat(True) self.btnPrevious.setIcon(QIcon.fromTheme("arrow-left")) self.btnPrevious.clicked.connect(self.switchPreviousItem) self.btnNext = QPushButton(self) self.btnNext.setFlat(True) self.btnNext.setIcon(QIcon.fromTheme("arrow-right")) self.btnNext.clicked.connect(self.switchNextItem) self.btnNew = QPushButton(self) self.btnNew.setFlat(True) self.btnNew.setIcon(QIcon.fromTheme("document-new")) self.btnNew.clicked.connect(self.createNewText) # Path and New Text Buttons self.wPath = myPath(self) # Close self.btnClose = QPushButton(self) self.btnClose.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCloseButton)) self.btnClose.clicked.connect(self.close) self.btnClose.setFlat(True) # Top panel Layout if self.btnSpellCheck: self.topPanel.layout().addWidget(self.btnSpellCheck) self.topPanel.layout().addSpacing(15) self.topPanel.layout().addWidget(self.btnPrevious) self.topPanel.layout().addWidget(self.btnNext) self.topPanel.layout().addWidget(self.btnNew) self.topPanel.layout().addStretch(1) self.topPanel.layout().addWidget(self.wPath) self.topPanel.layout().addStretch(1) self.topPanel.layout().addWidget(self.btnClose) self.updateTopBar() # Left Panel self._locked = False self.leftPanel = myPanel(vertical=True, parent=self) self.locker = locker(self) self.locker.lockChanged.connect(self.setLocked) self.leftPanel.layout().addWidget(self.locker) # Bottom Panel self.bottomPanel = myPanel(parent=self) self.bottomPanel.layout().addSpacing(24) self.lstThemes = QComboBox(self) self.lstThemes.setAttribute(Qt.WA_TranslucentBackground) paths = allPaths("resources/themes") for p in paths: lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"] for t in lst: themeIni = os.path.join(p, t) name = loadThemeDatas(themeIni)["Name"] # self.lstThemes.addItem(os.path.splitext(t)[0]) self.lstThemes.addItem(name) self.lstThemes.setItemData(self.lstThemes.count()-1, os.path.splitext(t)[0]) self.lstThemes.setCurrentIndex(self.lstThemes.findData(settings.fullScreenTheme)) # self.lstThemes.setCurrentText(settings.fullScreenTheme) self.lstThemes.currentTextChanged.connect(self.setTheme) self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height())) themeLabel = QLabel(self.tr("Theme:"), self) self.bottomPanel.layout().addWidget(themeLabel) self.bottomPanel.layout().addWidget(self.lstThemes) self.bottomPanel.layout().addStretch(1) self.lblProgress = QLabel(self) self.lblProgress.setMaximumSize(QSize(200, 14)) self.lblProgress.setMinimumSize(QSize(100, 14)) self.lblWC = QLabel(self) self.lblClock = myClockLabel(self) self.bottomPanel.layout().addWidget(self.lblWC) self.bottomPanel.layout().addWidget(self.lblProgress) self.bottomPanel.layout().addSpacing(15) self.bottomPanel.layout().addWidget(self.lblClock) self.updateStatusBar() self.bottomPanel.layout().addSpacing(24) # Add Widget Settings if self.btnSpellCheck: self.topPanel.addWidgetSetting(self.tr("Spellcheck"), 'top-spellcheck', (self.btnSpellCheck, )) self.topPanel.addWidgetSetting(self.tr("Navigation"), 'top-navigation', (self.btnPrevious, self.btnNext)) self.topPanel.addWidgetSetting(self.tr("New Text"), 'top-new-doc', (self.btnNew, )) self.topPanel.addWidgetSetting(self.tr("Title"), 'top-title', (self.wPath, )) self.topPanel.addSetting(self.tr("Title: Show Full Path"), 'title-show-full-path', True) self.topPanel.setSettingCallback('title-show-full-path', lambda var, val: self.updateTopBar()) self.bottomPanel.addWidgetSetting(self.tr("Theme selector"), 'bottom-theme', (self.lstThemes, themeLabel)) self.bottomPanel.addWidgetSetting(self.tr("Word count"), 'bottom-wc', (self.lblWC, )) self.bottomPanel.addWidgetSetting(self.tr("Progress"), 'bottom-progress', (self.lblProgress, )) self.bottomPanel.addSetting(self.tr("Progress: Auto Show/Hide"), 'progress-auto-show', True) self.bottomPanel.addWidgetSetting(self.tr("Clock"), 'bottom-clock', (self.lblClock, )) self.bottomPanel.addSetting(self.tr("Clock: Show Seconds"), 'clock-show-seconds', True) self.bottomPanel.setAutoHideVariable('autohide-bottom') self.topPanel.setAutoHideVariable('autohide-top') self.leftPanel.setAutoHideVariable('autohide-left') # Connection self._index.model().dataChanged.connect(self.dataChanged) # self.updateTheme() self.showFullScreen() # self.showMaximized() # self.show() def __del__(self): # print("Leaving fullScreenEditor via Destructor event", flush=True) self.showNormal() self.close() def setLocked(self, val): self._locked = val self.btnClose.setVisible(not val) def setTheme(self, themeName): themeName = self.lstThemes.currentData() settings.fullScreenTheme = themeName self._theme = findThemePath(themeName) self._themeDatas = loadThemeDatas(self._theme) self.updateTheme() def updateTheme(self): # Reinit stored geometries for hiding widgets self._geometries = {} rect = self.geometry() self._background = generateTheme(self._themeDatas, rect) setThemeEditorDatas(self.editor, self._themeDatas, self._background, rect) # Colors if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \ self._themeDatas["Foreground/Opacity"] < 5: self._fgcolor = QColor(self._themeDatas["Text/Color"]) self._bgcolor = QColor(self._themeDatas["Background/Color"]) else: self._bgcolor = QColor(self._themeDatas["Foreground/Color"]) self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] * 255 / 100) self._fgcolor = QColor(self._themeDatas["Text/Color"]) if self._themeDatas["Text/Color"] == self._themeDatas["Foreground/Color"]: self._fgcolor = QColor(self._themeDatas["Background/Color"]) # ScrollBar r = self.editor.geometry() w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent) r.setWidth(w) r.moveRight(rect.right() - rect.left()) self.scrollBar.setGeometry(r) # self.scrollBar.setVisible(False) self.hideWidget(self.scrollBar) p = self.scrollBar.palette() b = QBrush(self._background.copy(self.scrollBar.geometry())) p.setBrush(QPalette.Base, b) self.scrollBar.setPalette(p) self.scrollBar.setColor(self._bgcolor) # Left Panel r = self.locker.geometry() r.moveTopLeft(QPoint( 0, self.geometry().height() / 2 - r.height() / 2 )) self.leftPanel.setGeometry(r) self.hideWidget(self.leftPanel) self.leftPanel.setColor(self._bgcolor) # Top / Bottom Panels r = QRect(0, 0, 0, 24) r.setWidth(rect.width()) # r.moveLeft(rect.center().x() - r.width() / 2) self.topPanel.setGeometry(r) # self.topPanel.setVisible(False) self.hideWidget(self.topPanel) r.moveBottom(rect.bottom() - rect.top()) self.bottomPanel.setGeometry(r) # self.bottomPanel.setVisible(False) self.hideWidget(self.bottomPanel) self.topPanel.setColor(self._bgcolor) self.bottomPanel.setColor(self._bgcolor) # Lst theme # p = self.lstThemes.palette() p = self.palette() p.setBrush(QPalette.Button, self._bgcolor) p.setBrush(QPalette.ButtonText, self._fgcolor) p.setBrush(QPalette.WindowText, self._fgcolor) for panel in (self.bottomPanel, self.topPanel, self.leftPanel): for i in range(panel.layout().count()): item = panel.layout().itemAt(i) if item.widget(): item.widget().setPalette(p) # self.lstThemes.setPalette(p) # self.lblWC.setPalette(p) self.update() self.editor.centerCursor() def paintEvent(self, event): if self._background: painter = QPainter(self) painter.setClipRegion(event.region()) painter.drawPixmap(event.rect(), self._background, event.rect()) painter.end() def resizeEvent(self, event): self.updateTheme() def keyPressEvent(self, event): if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \ not self._locked: # print("Leaving fullScreenEditor via keyPressEvent", flush=True) self.showNormal() self.close() elif (event.modifiers() & Qt.AltModifier) and \ event.key() in [Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Left, Qt.Key_Right]: if event.key() in [Qt.Key_PageUp, Qt.Key_Left]: success = self.switchPreviousItem() if event.key() in [Qt.Key_PageDown, Qt.Key_Right]: success = self.switchNextItem() if not success: QWidget.keyPressEvent(self, event) else: QWidget.keyPressEvent(self, event) def mouseMoveEvent(self, event): r = self.geometry() for w in [self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel]: # w.setVisible(w.geometry().contains(event.pos())) if self._geometries[w].contains(event.pos()): self.showWidget(w) else: self.hideWidget(w) def hideWidget(self, widget): if widget not in self._geometries: self._geometries[widget] = widget.geometry() if hasattr(widget, "_autoHide") and not widget._autoHide: return # Hides widget in the bottom right corner widget.move(self.geometry().bottomRight() + QPoint(1, 1)) def showWidget(self, widget): if widget in self._geometries: widget.move(self._geometries[widget].topLeft()) def eventFilter(self, obj, event): if obj == self.editor and event.type() == QEvent.Enter: for w in [self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel]: # w.setVisible(False) self.hideWidget(w) return QWidget.eventFilter(self, obj, event) def dataChanged(self, topLeft, bottomRight): # This is called sometimes after self has been destroyed. Don't know why. if not self or not self._index: return if topLeft.row() <= self._index.row() <= bottomRight.row(): self.updateStatusBar() def updateTopBar(self): item = self._index.internalPointer() previousItem = self.previousTextItem(item) nextItem = self.nextTextItem(item) self.btnPrevious.setEnabled(previousItem is not None) self.btnNext.setEnabled(nextItem is not None) self.wPath.setItem(item) def updateStatusBar(self): if self._index: item = self._index.internalPointer() wc = item.data(Outline.wordCount) goal = item.data(Outline.goal) pg = item.data(Outline.goalPercentage) if goal: if settings.fullscreenSettings.get("progress-auto-show", True): self.lblProgress.show() self.lblWC.setText(self.tr("{} words / {}").format(wc, goal)) else: if settings.fullscreenSettings.get("progress-auto-show", True): self.lblProgress.hide() self.lblWC.setText(self.tr("{} words").format(wc)) pg = 0 rect = self.lblProgress.geometry() rect = QRect(QPoint(0, 0), rect.size()) self.px = QPixmap(rect.size()) self.px.fill(Qt.transparent) p = QPainter(self.px) drawProgress(p, rect, pg, 2) p.end() self.lblProgress.setPixmap(self.px) self.locker.setWordCount(wc) # If there's a goal, then we update the locker target's number of word accordingly # (also if there is a word count, we deduce it. if goal and not self.locker.isLocked(): if wc and goal - wc > 0: self.locker.spnWordTarget.setValue(goal - wc) elif not wc: self.locker.spnWordTarget.setValue(goal) def setCurrentModelIndex(self, index): self._index = index self.editor.setCurrentModelIndex(index) self.updateTopBar() self.updateStatusBar() def switchPreviousItem(self): item = self._index.internalPointer() previousItem = self.previousTextItem(item) if previousItem: self.setCurrentModelIndex(previousItem.index()) return True return False def switchNextItem(self): item = self._index.internalPointer() nextItem = self.nextTextItem(item) if nextItem: self.setCurrentModelIndex(nextItem.index()) return True return False def switchToItem(self, item): item = self.firstTextItem(item) if item: self.setCurrentModelIndex(item.index()) def createNewText(self): item = self._index.internalPointer() newItem = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=settings.defaultTextType) self._index.model().insertItem(newItem, item.row() + 1, item.parent().index()) self.setCurrentModelIndex(newItem.index()) def previousModelItem(self, item): parent = item.parent() if not parent: # Root has no sibling return None row = parent.childItems.index(item) if row > 0: return parent.child(row - 1) return self.previousModelItem(parent) def nextModelItem(self, item): parent = item.parent() if not parent: # Root has no sibling return None row = parent.childItems.index(item) if row + 1 < parent.childCount(): return parent.child(row + 1) return self.nextModelItem(parent) def previousTextItem(self, item): previous = self.previousModelItem(item) while previous: last = self.lastTextItem(previous) if last: return last previous = self.previousModelItem(previous) return None def nextTextItem(self, item): if item.isFolder() and item.childCount() > 0: next = item.child(0) else: next = self.nextModelItem(item) while next: first = self.firstTextItem(next) if first: return first next = self.nextModelItem(next) return None def firstTextItem(self, item): if item.isText(): return item for child in item.children(): first = self.firstTextItem(child) if first: return first return None def lastTextItem(self, item): if item.isText(): return item for child in reversed(item.children()): last = self.lastTextItem(child) if last: return last return None
class OWCSVFileImport(widget.OWWidget): name = "CSV导入(CSV File Import)" description = "从csv格式文件导入数据表。" icon = "icons/CSVFile.svg" priority = 11 category = "Data" keywords = ["file", "load", "read", "open", "csv"] outputs = [ widget.OutputSignal( name="数据(Data)", type=Orange.data.Table, doc="Loaded data set.", replaces=['Data']), widget.OutputSignal( name="数据帧格式(Data Frame)", type=pd.DataFrame, doc="", replaces=['Data Frame'] ) ] class Error(widget.OWWidget.Error): error = widget.Msg( "Unexpected error" ) encoding_error = widget.Msg( "Encoding error\n" "The file might be encoded in an unsupported encoding or it " "might be binary" ) #: Paths and options of files accessed in a 'session' _session_items = settings.Setting( [], schema_only=True) # type: List[Tuple[str, dict]] #: Saved dialog state (last directory and selected filter) dialog_state = settings.Setting({ "directory": "", "filter": "" }) # type: Dict[str, str] MaxHistorySize = 50 want_main_area = False buttons_area_orientation = None def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self.__committimer = QTimer(self, singleShot=True) self.__committimer.timeout.connect(self.commit) self.__executor = qconcurrent.ThreadExecutor() self.__watcher = None # type: Optional[qconcurrent.FutureWatcher] self.controlArea.layout().setSpacing(-1) # reset spacing grid = QGridLayout() grid.addWidget(QLabel("文件(File):", self), 0, 0, 1, 1) self.import_items_model = QStandardItemModel(self) self.recent_combo = QComboBox( self, objectName="recent-combo", toolTip="Recent files.", sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, ) self.recent_combo.setModel(self.import_items_model) self.recent_combo.activated.connect(self.activate_recent) self.recent_combo.setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self.browse_button = QPushButton( "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon), toolTip="Browse filesystem", autoDefault=False, ) self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.browse_button.clicked.connect(self.browse) grid.addWidget(self.recent_combo, 0, 1, 1, 1) grid.addWidget(self.browse_button, 0, 2, 1, 1) self.controlArea.layout().addLayout(grid) ########### # Info text ########### box = gui.widgetBox(self.controlArea, "信息", addSpace=False) self.summary_text = QTextBrowser( verticalScrollBarPolicy=Qt.ScrollBarAsNeeded, readOnly=True, ) self.summary_text.viewport().setBackgroundRole(QPalette.NoRole) self.summary_text.setFrameStyle(QTextBrowser.NoFrame) self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4) self.summary_text.viewport().setAutoFillBackground(False) box.layout().addWidget(self.summary_text) button_box = QDialogButtonBox( orientation=Qt.Horizontal, standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry ) self.load_button = b = button_box.button(QDialogButtonBox.Retry) b.setText("载入") b.clicked.connect(self.__committimer.start) b.setEnabled(False) b.setDefault(True) self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel) b.clicked.connect(self.cancel) b.setEnabled(False) b.setAutoDefault(False) self.import_options_button = QPushButton( "导入选项", enabled=False, autoDefault=False, clicked=self._activate_import_dialog ) def update_buttons(cbindex): self.import_options_button.setEnabled(cbindex != -1) self.load_button.setEnabled(cbindex != -1) self.recent_combo.currentIndexChanged.connect(update_buttons) button_box.addButton( self.import_options_button, QDialogButtonBox.ActionRole ) button_box.setStyleSheet( "button-layout: {:d};".format(QDialogButtonBox.MacLayout) ) self.controlArea.layout().addWidget(button_box) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) self._restoreState() item = self.current_item() if item is not None: self.set_selected_file(item.path(), item.options()) @Slot(int) def activate_recent(self, index): """ Activate an item from the recent list. """ if 0 <= index < self.import_items_model.rowCount(): item = self.import_items_model.item(index) assert item is not None path = item.data(ImportItem.PathRole) opts = item.data(ImportItem.OptionsRole) if not isinstance(opts, Options): opts = None self.set_selected_file(path, opts) else: self.recent_combo.setCurrentIndex(-1) @Slot() def browse(self): """ Open a file dialog and select a user specified file. """ formats = [ "Text - comma separated (*.csv, *)", "Text - tab separated (*.tsv, *)", "Text - all files (*)" ] dlg = QFileDialog( self, windowTitle="打开数据文件", acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile ) dlg.setNameFilters(formats) state = self.dialog_state lastdir = state.get("directory", "") lastfilter = state.get("filter", "") if lastdir and os.path.isdir(lastdir): dlg.setDirectory(lastdir) if lastfilter: dlg.selectNameFilter(lastfilter) status = dlg.exec_() dlg.deleteLater() if status == QFileDialog.Accepted: self.dialog_state["directory"] = dlg.directory().absolutePath() self.dialog_state["filter"] = dlg.selectedNameFilter() selected_filter = dlg.selectedNameFilter() path = dlg.selectedFiles()[0] # pre-flight check; try to determine the nature of the file mtype = _mime_type_for_path(path) if not mtype.inherits("text/plain"): mb = QMessageBox( parent=self, windowTitle="", icon=QMessageBox.Question, text="The '{basename}' may be a binary file.\n" "Are you sure you want to continue?".format( basename=os.path.basename(path)), standardButtons=QMessageBox.Cancel | QMessageBox.Yes ) mb.setWindowModality(Qt.WindowModal) if mb.exec() == QMessageBox.Cancel: return # initialize dialect based on selected extension if selected_filter in formats[:-1]: filter_idx = formats.index(selected_filter) if filter_idx == 0: dialect = csv.excel() elif filter_idx == 1: dialect = csv.excel_tab() else: dialect = csv.excel_tab() header = True else: try: dialect, header = sniff_csv_with_path(path) except Exception: # pylint: disable=broad-except dialect, header = csv.excel(), True options = None # Search for path in history. # If found use the stored params to initialize the import dialog items = self.itemsFromSettings() idx = index_where(items, lambda t: samepath(t[0], path)) if idx is not None: _, options_ = items[idx] if options_ is not None: options = options_ if options is None: if not header: rowspec = [] else: rowspec = [(range(0, 1), RowSpec.Header)] options = Options( encoding="utf-8", dialect=dialect, rowspec=rowspec) dlg = CSVImportDialog( self, windowTitle="Import Options", sizeGripEnabled=True) dlg.setWindowModality(Qt.WindowModal) dlg.setPath(path) dlg.setOptions(options) status = dlg.exec_() dlg.deleteLater() if status == QDialog.Accepted: self.set_selected_file(path, dlg.options()) def current_item(self): # type: () -> Optional[ImportItem] """ Return the current selected item (file) or None if there is no current item. """ idx = self.recent_combo.currentIndex() if idx == -1: return None item = self.recent_combo.model().item(idx) # type: QStandardItem if isinstance(item, ImportItem): return item else: return None def _activate_import_dialog(self): """Activate the Import Options dialog for the current item.""" item = self.current_item() assert item is not None dlg = CSVImportDialog( self, windowTitle="导入选项", sizeGripEnabled=True, ) dlg.setWindowModality(Qt.WindowModal) dlg.setAttribute(Qt.WA_DeleteOnClose) settings = QSettings() qualname = qname(type(self)) settings.beginGroup(qualname) size = settings.value("size", QSize(), type=QSize) # type: QSize if size.isValid(): dlg.resize(size) path = item.data(ImportItem.PathRole) options = item.data(ImportItem.OptionsRole) dlg.setPath(path) # Set path before options so column types can if isinstance(options, Options): dlg.setOptions(options) def update(): newoptions = dlg.options() item.setData(newoptions, ImportItem.OptionsRole) # update the stored item self._add_recent(path, newoptions) if newoptions != options: self._invalidate() dlg.accepted.connect(update) def store_size(): settings.setValue("size", dlg.size()) dlg.finished.connect(store_size) dlg.show() def set_selected_file(self, filename, options=None): """ Set the current selected filename path. """ self._add_recent(filename, options) self._invalidate() #: Saved options for a filename SCHEMA = { "path": str, # Local filesystem path "options": str, # json encoded 'Options' } @classmethod def _local_settings(cls): # type: () -> QSettings """Return a QSettings instance with local persistent settings.""" filename = "{}.ini".format(qname(cls)) fname = os.path.join(settings.widget_settings_dir(), filename) return QSettings(fname, QSettings.IniFormat) def _add_recent(self, filename, options=None): # type: (str, Optional[Options]) -> None """ Add filename to the list of recent files. """ model = self.import_items_model index = index_where( (model.index(i, 0).data(ImportItem.PathRole) for i in range(model.rowCount())), lambda path: isinstance(path, str) and samepath(path, filename) ) if index is not None: item, *_ = model.takeRow(index) else: item = ImportItem.fromPath(filename) model.insertRow(0, item) if options is not None: item.setOptions(options) self.recent_combo.setCurrentIndex(0) # store items to local persistent settings s = self._local_settings() arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA) item = {"path": filename} if options is not None: item["options"] = json.dumps(options.as_dict()) arr = [item for item in arr if item.get("path") != filename] arr.append(item) QSettings_writeArray(s, "recent", arr) # update workflow session items items = self._session_items[:] idx = index_where(items, lambda t: samepath(t[0], filename)) if idx is not None: del items[idx] items.insert(0, (filename, options.as_dict())) self._session_items = items[:OWCSVFileImport.MaxHistorySize] def _invalidate(self): # Invalidate the current output and schedule a new commit call. # (NOTE: The widget enters a blocking state) self.__committimer.start() if self.__watcher is not None: self.__cancel_task() self.setBlocking(True) def commit(self): """ Commit the current state and submit the load task for execution. Note ---- Any existing pending task is canceled. """ self.__committimer.stop() if self.__watcher is not None: self.__cancel_task() self.error() item = self.current_item() if item is None: return path = item.data(ImportItem.PathRole) opts = item.data(ImportItem.OptionsRole) if not isinstance(opts, Options): return task = state = TaskState() state.future = ... state.watcher = qconcurrent.FutureWatcher() state.progressChanged.connect( self.__set_read_progress, Qt.DirectConnection) def progress_(i, j): task.emitProgressChangedOrCancel(i, j) task.future = self.__executor.submit( clear_stack_on_cancel(load_csv), path, opts, progress_, ) task.watcher.setFuture(task.future) w = task.watcher w.done.connect(self.__handle_result) w.progress = state self.__watcher = w self.__set_running_state() @Slot('qint64', 'qint64') def __set_read_progress(self, read, count): if count > 0: self.progressBarSet(100 * read / count) def __cancel_task(self): # Cancel and dispose of the current task assert self.__watcher is not None w = self.__watcher w.future().cancel() w.progress.cancel = True w.done.disconnect(self.__handle_result) w.progress.progressChanged.disconnect(self.__set_read_progress) w.progress.deleteLater() # wait until completion futures.wait([w.future()]) self.__watcher = None def cancel(self): """ Cancel current pending or executing task. """ if self.__watcher is not None: self.__cancel_task() self.__clear_running_state() self.setStatusMessage("Cancelled") self.summary_text.setText( "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>" ) def __set_running_state(self): self.progressBarInit() self.setBlocking(True) self.setStatusMessage("Running") self.cancel_button.setEnabled(True) self.load_button.setText("Restart") path = self.current_item().path() self.Error.clear() self.summary_text.setText( "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path)) ) def __clear_running_state(self, ): self.progressBarFinished() self.setStatusMessage("") self.setBlocking(False) self.cancel_button.setEnabled(False) self.load_button.setText("重新加载") def __set_error_state(self, err): self.Error.clear() if isinstance(err, UnicodeDecodeError): self.Error.encoding_error(exc_info=err) else: self.Error.error(exc_info=err) path = self.current_item().path() basename = os.path.basename(path) if isinstance(err, UnicodeDecodeError): text = ( "<div><i>{basename}</i> was not loaded due to a text encoding " "error. The file might be saved in an unknown or invalid " "encoding, or it might be a binary file.</div>" ).format( basename=escape(basename) ) else: text = ( "<div><i>{basename}</i> was not loaded due to an error:" "<p style='white-space: pre;'>{err}</p>" ).format( basename=escape(basename), err="".join(traceback.format_exception_only(type(err), err)) ) self.summary_text.setText(text) def __clear_error_state(self): self.Error.error.clear() self.summary_text.setText("") def onDeleteWidget(self): """Reimplemented.""" if self.__watcher is not None: self.__cancel_task() self.__executor.shutdown() super().onDeleteWidget() @Slot(object) def __handle_result(self, f): # type: (qconcurrent.Future[pd.DataFrame]) -> None assert f.done() assert f is self.__watcher.future() self.__watcher = None self.__clear_running_state() try: df = f.result() assert isinstance(df, pd.DataFrame) except pandas.errors.EmptyDataError: df = pd.DataFrame({}) except Exception as e: # pylint: disable=broad-except self.__set_error_state(e) df = None else: self.__clear_error_state() if df is not None: table = pandas_to_table(df) else: table = None self.send("数据帧格式(Data Frame)", df) self.send('数据(Data)', table) self._update_status_messages(table) def _update_status_messages(self, data): if data is None: return def pluralize(seq): return "" if len(seq) != 1 else "" summary = ("{n_instances} 行{plural_1}, " "{n_features} 个特征{plural_2}, " "{n_meta} 个meta{plural_3}").format( n_instances=len(data), plural_1=pluralize(data), n_features=len(data.domain.attributes), plural_2=pluralize(data.domain.attributes), n_meta=len(data.domain.metas), plural_3=pluralize(data.domain.metas)) self.summary_text.setText(summary) def itemsFromSettings(self): # type: () -> List[Tuple[str, Options]] """ Return items from local history. """ s = self._local_settings() items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA) items = [] # type: List[Tuple[str, Options]] for item in items_: path = item.get("path", "") if not path: continue opts_json = item.get("options", "") try: opts = Options.from_dict(json.loads(opts_json)) except (csv.Error, LookupError, TypeError, json.JSONDecodeError): _log.error("Could not reconstruct options for '%s'", path, exc_info=True) else: items.append((path, opts)) return items[::-1] def _restoreState(self): # Restore the state. Merge session (workflow) items with the # local history. model = self.import_items_model # local history items = self.itemsFromSettings() # stored session items sitems = [] for p, m in self._session_items: try: item_ = (p, Options.from_dict(m)) except (csv.Error, LookupError): # Is it better to fail then to lose a item slot? _log.error("Failed to restore '%s'", p, exc_info=True) else: sitems.append(item_) items = sitems + items items = unique(items, key=lambda t: pathnormalize(t[0])) curr = self.recent_combo.currentIndex() if curr != -1: currentpath = self.recent_combo.currentData(ImportItem.PathRole) else: currentpath = None for path, options in items: item = ImportItem.fromPath(path) item.setOptions(options) model.appendRow(item) if currentpath is not None: idx = self.recent_combo.findData(currentpath, ImportItem.PathRole) if idx != -1: self.recent_combo.setCurrentIndex(idx)
class ExportWindow(QDialog): """ Class describing a dialog for exports of schedules. """ def __init__(self, schedule: Schedule, parent: QWidget = None): super().__init__(parent) self._schedule_ref = schedule self._date_start_cache = None # window settings self.setWindowFlag(Qt.WindowContextHelpButtonHint, False) self.setWindowTitle(self.tr("Export window")) self.setMinimumWidth(800) # title, add_date, work mode self.layout_title_date_mode = QHBoxLayout() self.label_title = QLabel(self.tr("Title")) self.layout_title_date_mode.addWidget(self.label_title) self.line_edit_title = QLineEdit() self.layout_title_date_mode.addWidget(self.line_edit_title) self.check_box_add_date = QCheckBox(self.tr("Add date")) self.layout_title_date_mode.addWidget(self.check_box_add_date) self.combo_box_work_mode = QComboBox() self.layout_title_date_mode.addWidget(self.combo_box_work_mode) self.line_edit_title.setPlaceholderText( self.tr("My Group. A subgroup - green color, " "B subgroup - yellow color. ")) self.check_box_add_date.setChecked(True) self.combo_box_work_mode.addItem(self.tr("Weekly")) self.combo_box_work_mode.addItem(self.tr("Full")) # list widget with files self.layout_list_widget = QVBoxLayout() self.check_box_use_current = QCheckBox(self.tr("Use current schedule")) self.layout_list_widget.addWidget(self.check_box_use_current) self.check_box_use_current.setChecked(True) self.list_widget = QListWidget() self.layout_list_widget.addWidget(self.list_widget) self.list_widget.setSortingEnabled(True) self.list_widget.setEnabled(False) self.layout_open_folder = QHBoxLayout() self.layout_list_widget.addLayout(self.layout_open_folder) self.label_find = QLabel(self.tr("Schedules: ") + "0") self.layout_open_folder.addWidget(self.label_find) self.layout_open_folder.addStretch(1) self.push_button_open_folder = QToolButton() self.layout_open_folder.addWidget(self.push_button_open_folder) self.push_button_open_folder.setEnabled(False) self.push_button_open_folder.setText(self.tr("Open folder")) self.push_button_open_folder.setPopupMode(QToolButton.MenuButtonPopup) self.action_open_files = QAction(self.tr("Open files")) self.push_button_open_folder.addAction(self.action_open_files) # font edit self.group_box_font = QGroupBox(self.tr("Font settings")) self.form_layout_font = QFormLayout(self.group_box_font) self.label_font = QLabel(self.tr("Font")) self.form_layout_font.setWidget(0, QFormLayout.LabelRole, self.label_font) self.combo_box_font = QComboBox() self.form_layout_font.setWidget(0, QFormLayout.FieldRole, self.combo_box_font) self.label_encoding = QLabel(self.tr("Encoding")) self.form_layout_font.setWidget(1, QFormLayout.LabelRole, self.label_encoding) self.combo_box_encoding = QComboBox() self.form_layout_font.setWidget(1, QFormLayout.FieldRole, self.combo_box_encoding) for font_name, font_path in util.get_fonts(): self.combo_box_font.addItem(font_name, font_path) self.combo_box_font.setCurrentText(qApp.font().family()) self.combo_box_font.setEditable(True) self.combo_box_encoding.addItem("UTF-8") self.combo_box_encoding.addItem("Latin-1") self.combo_box_encoding.addItem("Windows-1252") # date edit self.group_box_date = QGroupBox(self.tr("Date settings")) self.form_layout_date = QFormLayout(self.group_box_date) self.label_date_start = QLabel(self.tr("Start")) self.form_layout_date.setWidget(0, QFormLayout.LabelRole, self.label_date_start) self.date_edit_start = QDateEdit() self.form_layout_date.setWidget(0, QFormLayout.FieldRole, self.date_edit_start) self.label_date_end = QLabel(self.tr("End")) self.form_layout_date.setWidget(1, QFormLayout.LabelRole, self.label_date_end) self.date_edit_end = QDateEdit() self.form_layout_date.setWidget(1, QFormLayout.FieldRole, self.date_edit_end) self.date_edit_start.setCalendarPopup(True) self.date_edit_end.setCalendarPopup(True) if QDate.currentDate().day() < (QDate.currentDate().dayOfYear() / 2): date = QDate(QDate.currentDate().year(), 2, 1) else: date = QDate(QDate.currentDate().year(), 9, 1) self._date_start_cache = date.addDays(8 - date.dayOfWeek()) self.date_edit_start.setDate(self._date_start_cache) self.date_edit_end.setMinimumDate(self._date_start_cache.addDays(7)) self.date_edit_end.setDate(self._date_start_cache.addDays(16 * 7)) # subgroup edit self.group_box_subgroup = QGroupBox(self.tr("Subgroup settings")) self.form_layout_subgroup = QFormLayout(self.group_box_subgroup) self.label_color_a = QLabel(self.tr("Color A")) self.form_layout_subgroup.setWidget(0, QFormLayout.LabelRole, self.label_color_a) self.combo_box_color_a = QComboBox() self.form_layout_subgroup.setWidget(0, QFormLayout.FieldRole, self.combo_box_color_a) self.label_color_b = QLabel(self.tr("Color B")) self.form_layout_subgroup.setWidget(1, QFormLayout.LabelRole, self.label_color_b) self.combo_box_color_b = QComboBox() self.form_layout_subgroup.setWidget(1, QFormLayout.FieldRole, self.combo_box_color_b) self.label_pattern_a_b = QLabel(self.tr("Pattern A and B")) self.form_layout_subgroup.setWidget(2, QFormLayout.LabelRole, self.label_pattern_a_b) self.combo_box_pattern_a_b = QComboBox() self.form_layout_subgroup.setWidget(2, QFormLayout.FieldRole, self.combo_box_pattern_a_b) self.add_standard_colors(self.combo_box_color_a) self.add_standard_colors(self.combo_box_color_b) self.combo_box_color_a.setCurrentIndex(9) # lime self.combo_box_color_b.setCurrentIndex(15) # yellow self.combo_box_pattern_a_b.addItem(self.tr("Chess order")) self.combo_box_pattern_a_b.setEnabled(False) # navigate buttons self.layout_navigate = QHBoxLayout() self.layout_navigate.addStretch(1) self.push_button_export = QPushButton(self.tr("Export")) self.layout_navigate.addWidget(self.push_button_export) self.push_button_cancel = QPushButton(self.tr("Cancel")) self.layout_navigate.addWidget(self.push_button_cancel) # layout setup self.layout_right_setting = QVBoxLayout() self.layout_right_setting.addWidget(self.group_box_font) self.layout_right_setting.addWidget(self.group_box_date) self.layout_right_setting.addWidget(self.group_box_subgroup) self.layout_right_setting.addStretch(1) self.layout_center = QHBoxLayout() self.layout_center.addLayout(self.layout_list_widget) self.layout_center.addLayout(self.layout_right_setting) self.layout_main = QVBoxLayout() self.layout_main.addLayout(self.layout_title_date_mode) self.layout_main.addLayout(self.layout_center) self.layout_main.addLayout(self.layout_navigate) self.setLayout(self.layout_main) # connection self.check_box_use_current.clicked.connect( self.check_box_use_current_clicked) self.push_button_open_folder.clicked.connect(self.open_folder_clicked) self.action_open_files.triggered.connect(self.open_files_clicked) self.date_edit_start.dateChanged.connect(self.date_edit_start_changed) self.combo_box_color_a.activated.connect( self.combo_box_color_a_clicked) self.combo_box_color_b.activated.connect( self.combo_box_color_b_clicked) self.push_button_export.clicked.connect(self.export_to_pdf) self.push_button_cancel.clicked.connect(self.close) def add_standard_colors(self, combo_box: QComboBox) -> None: """ Adds colors to the color selection menu. :param combo_box: Color selection menu """ color_items = [(self.tr("Custom color"), QColor()), (self.tr("Aqua"), QColor(0, 255, 255)), (self.tr("Grey"), QColor(128, 128, 128)), (self.tr("Navy"), QColor(0, 0, 192)), (self.tr("Silver"), QColor(192, 192, 192)), (self.tr("Black"), QColor(0, 0, 0)), (self.tr("Green"), QColor(0, 128, 0)), (self.tr("Olive"), QColor(192, 192, 0)), (self.tr("Blue"), QColor(0, 0, 255)), (self.tr("Lime"), QColor(0, 255, 0)), (self.tr("Purple"), QColor(128, 0, 128)), (self.tr("White"), QColor(255, 255, 255)), (self.tr("Fuchsia"), QColor(255, 0, 255)), (self.tr("Maroon"), QColor(128, 0, 0)), (self.tr("Red"), QColor(255, 0, 0)), (self.tr("Yellow"), QColor(255, 255, 0))] for name, data in color_items: combo_box.addItem(util.create_color_icon(data), name, data) def export_to_pdf(self) -> None: # select path paths = [] if self.check_box_use_current.isChecked(): path = QFileDialog.getSaveFileName(self, self.tr("Export to pdf"), ".", "PDF file (*.pdf)")[0] if path == "": return if not path.endswith(".pdf"): path += ".pdf" paths.append(path) else: for index in range(self.list_widget.count()): path = self.list_widget.item(index).data(Qt.UserRole) paths.append(path) # progress dialog progress = QProgressDialog(self.tr("Export to pdf"), self.tr("Abort exports"), 0, 100, self) progress.setWindowModality(Qt.WindowModal) progress.setMinimumDuration(2000) try: for index, path in enumerate(paths): if self.check_box_use_current.isChecked(): title_text = self.line_edit_title.text() schedule = self._schedule_ref else: title_text = QFileInfo(path).baseName() schedule = Schedule() schedule.load(path) path = path[0:-5] + ".pdf" print(path) mode = self.combo_box_work_mode.currentIndex() if mode == 0: export_weeks_to_pdf( schedule, title_text, self.check_box_add_date.isChecked(), path, self.combo_box_font.currentText(), self.combo_box_font.currentData(Qt.UserRole), self.combo_box_encoding.currentText(), self.date_edit_start.date().toPyDate(), self.date_edit_end.date().toPyDate(), self.combo_box_color_a.currentData(Qt.UserRole), self.combo_box_color_b.currentData(Qt.UserRole), progress if self.check_box_use_current.isChecked() else None) else: export_full_to_pdf( schedule, title_text, path, self.combo_box_font.currentText(), self.combo_box_font.currentData(Qt.UserRole), self.combo_box_encoding.currentText(), progress if self.check_box_use_current.isChecked() else None) progress.setValue(int(index * 100 / len(paths))) # finish dialog progress.setValue(100) finish_msg_box = QMessageBox(QMessageBox.Information, self.tr("Export to pdf"), self.tr("Gone!")) open_folder_button = finish_msg_box.addButton( self.tr("Open folder"), QMessageBox.ActionRole) finish_msg_box.addButton(QMessageBox.Ok) finish_msg_box.exec_() if finish_msg_box.clickedButton() == open_folder_button: QDesktopServices.openUrl( QUrl( QFileInfo(paths[0] if len(paths) != 0 else "."). absolutePath())) except UnicodeEncodeError as ex: QMessageBox.critical(self, self.tr("Encoding error"), str(ex)) except Exception as ex: QMessageBox.critical(self, self.tr("Unknown error"), str(ex)) progress.setValue(100) def check_box_use_current_clicked(self, checked: bool): if checked: self.list_widget.setEnabled(False) self.push_button_open_folder.setEnabled(False) self.line_edit_title.setEnabled(False) else: self.list_widget.setEnabled(True) self.push_button_open_folder.setEnabled(True) self.line_edit_title.setEnabled(True) def open_folder_clicked(self): path = QFileDialog.getExistingDirectory(self, self.tr("Select folder")) provider = QFileIconProvider() self.list_widget.clear() for dir_path, dir_names, file_names in os.walk(path): for file_name in file_names: if file_name.endswith(".json"): item = QListWidgetItem() item.setText(file_name[0:-5]) item.setData(Qt.UserRole, dir_path + os.sep + file_name) item.setIcon( provider.icon(QFileInfo(dir_path + os.sep + file_name))) self.list_widget.addItem(item) self.label_find.setText( self.tr("Schedules: ") + str(self.list_widget.count())) def open_files_clicked(self): files = QFileDialog.getOpenFileNames( self, self.tr("Select files"), "", "JSON file (*.json) ;; All files (*.*)")[0] provider = QFileIconProvider() self.list_widget.clear() for file_path in files: file = QFileInfo(file_path) item = QListWidgetItem() item.setText(file.baseName()) item.setData(Qt.UserRole, file_path) item.setIcon(provider.icon(file)) self.list_widget.addItem(item) self.label_find.setText( self.tr("Schedules: ") + str(self.list_widget.count())) def combo_box_color_a_clicked(self) -> None: """ Slot for color selection of A subgroup. """ if self.combo_box_color_a.currentIndex() == 0: self.custom_color_selected(self.combo_box_color_a) def combo_box_color_b_clicked(self) -> None: """ Slot for color selection of B subgroup. """ if self.combo_box_color_b.currentIndex() == 0: self.custom_color_selected(self.combo_box_color_b) def custom_color_selected(self, combo_box: QComboBox) -> None: """ Slot to select the color for the desired menu. :param combo_box: Menu """ color = QColorDialog.getColor(combo_box.currentData(), self) if color.isValid(): combo_box.setItemIcon(0, util.create_color_icon(color)) combo_box.setItemData(0, color) def date_edit_start_changed(self, date: QDate): """ Slot for changing the end of a range of dates. :param date: Start of the date range """ end_date = self.date_edit_end.date().addDays( self._date_start_cache.daysTo(date)) self.date_edit_end.setMinimumDate(date.addDays(7)) self.date_edit_end.setDate(end_date) self._date_start_cache = QDate(date)
class GuiItemEditor(QDialog): def __init__(self, theParent, theProject, tHandle): QDialog.__init__(self, theParent) logger.debug("Initialising GuiItemEditor ...") self.setObjectName("GuiItemEditor") self.mainConf = nw.CONFIG self.theProject = theProject self.theParent = theParent ## # Build GUI ## self.theItem = self.theProject.projTree[tHandle] if self.theItem is None: self._doClose() self.setWindowTitle(self.tr("Item Settings")) mVd = self.mainConf.pxInt(220) mSp = self.mainConf.pxInt(16) vSp = self.mainConf.pxInt(4) # Item Label self.editName = QLineEdit() self.editName.setMinimumWidth(mVd) self.editName.setMaxLength(200) # Item Status self.editStatus = QComboBox() self.editStatus.setMinimumWidth(mVd) if self.theItem.itemClass in nwLists.CLS_NOVEL: for sLabel, _, _ in self.theProject.statusItems: self.editStatus.addItem( self.theParent.statusIcons[sLabel], sLabel, sLabel ) else: for sLabel, _, _ in self.theProject.importItems: self.editStatus.addItem( self.theParent.importIcons[sLabel], sLabel, sLabel ) # Item Layout self.editLayout = QComboBox() self.editLayout.setMinimumWidth(mVd) validLayouts = [] if self.theItem.itemType == nwItemType.FILE: if self.theItem.itemClass in nwLists.CLS_NOVEL: validLayouts.append(nwItemLayout.TITLE) validLayouts.append(nwItemLayout.BOOK) validLayouts.append(nwItemLayout.PAGE) validLayouts.append(nwItemLayout.PARTITION) validLayouts.append(nwItemLayout.UNNUMBERED) validLayouts.append(nwItemLayout.CHAPTER) validLayouts.append(nwItemLayout.SCENE) validLayouts.append(nwItemLayout.NOTE) else: validLayouts.append(nwItemLayout.NO_LAYOUT) self.editLayout.setEnabled(False) for itemLayout in nwItemLayout: if itemLayout in validLayouts: self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout) # Export Switch self.textExport = QLabel(self.tr("Include when building project")) self.editExport = QSwitch() if self.theItem.itemType == nwItemType.FILE: self.editExport.setEnabled(True) self.editExport.setChecked(self.theItem.isExported) else: self.editExport.setEnabled(False) self.editExport.setChecked(False) # Buttons self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox.accepted.connect(self._doSave) self.buttonBox.rejected.connect(self._doClose) # Set Current Values self.editName.setText(self.theItem.itemName) self.editName.selectAll() statusIdx = self.editStatus.findData(self.theItem.itemStatus) if statusIdx != -1: self.editStatus.setCurrentIndex(statusIdx) layoutIdx = self.editLayout.findData(self.theItem.itemLayout) if layoutIdx != -1: self.editLayout.setCurrentIndex(layoutIdx) ## # Assemble ## nameLabel = QLabel(self.tr("Label")) statusLabel = QLabel(self.tr("Status")) layoutLabel = QLabel(self.tr("Layout")) self.mainForm = QGridLayout() self.mainForm.setVerticalSpacing(vSp) self.mainForm.setHorizontalSpacing(mSp) self.mainForm.addWidget(nameLabel, 0, 0, 1, 1) self.mainForm.addWidget(self.editName, 0, 1, 1, 2) self.mainForm.addWidget(statusLabel, 1, 0, 1, 1) self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2) self.mainForm.addWidget(layoutLabel, 2, 0, 1, 1) self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2) self.mainForm.addWidget(self.textExport, 3, 0, 1, 2) self.mainForm.addWidget(self.editExport, 3, 2, 1, 1) self.mainForm.setColumnStretch(0, 0) self.mainForm.setColumnStretch(1, 1) self.mainForm.setColumnStretch(2, 0) self.outerBox = QVBoxLayout() self.outerBox.setSpacing(mSp) self.outerBox.addLayout(self.mainForm) self.outerBox.addStretch(1) self.outerBox.addWidget(self.buttonBox) self.setLayout(self.outerBox) self.rejected.connect(self._doClose) logger.debug("GuiItemEditor initialisation complete") return ## # Slots ## @pyqtSlot() def _doSave(self): """Save the setting to the item. """ logger.verbose("ItemEditor save button clicked") itemName = self.editName.text() itemStatus = self.editStatus.currentData() itemLayout = self.editLayout.currentData() isExported = self.editExport.isChecked() self.theItem.setName(itemName) self.theItem.setStatus(itemStatus) self.theItem.setLayout(itemLayout) self.theItem.setExported(isExported) self.theProject.setProjectChanged(True) self.accept() self.close() return @pyqtSlot() def _doClose(self): """Close the dialog without saving the settings. """ logger.verbose("ItemEditor cancel button clicked") self.close() return
class ViewWidget(QWidget): tab_active = pyqtSignal() def __init__(self, iface, dock_widget: QDockWidget) -> None: super().__init__() self.iface = iface self.dock_widget = dock_widget self.vbox = QVBoxLayout() self.create_variable_selector() self.create_time_selector() self.create_extra_dim_selector() self.create_interp_input() self.create_dataset_selector() self.setLayout(self.vbox) self.datasets = {} # type: Dict[str, Dataset] self.selected_dataset = None # type: Optional[str] self.selected_variable = {} # type: Dict[str,str] self.selected_time = {} # type: Dict[str,int] self.selected_extra_dim = {} # type: Dict[Tuple[str,str],int] self.pause_replace_layer = False def create_variable_selector(self) -> None: self.variable_selector = QListWidget() self.variable_selector.currentItemChanged.connect( self.on_variable_selected) hbox = QHBoxLayout() hbox.addWidget(self.variable_selector) self.vbox.addLayout(hbox) def create_time_selector(self) -> None: self.time_label = QLabel('Time: N/A') self.time_selector = QSlider(Qt.Horizontal) self.time_selector.setSingleStep(1) self.time_selector.setPageStep(1) self.time_selector.setMinimum(0) self.time_selector.valueChanged.connect(self.on_time_selected) self.vbox.addWidget(self.time_label) self.vbox.addWidget(self.time_selector) def create_extra_dim_selector(self) -> None: self.extra_dim_label = QLabel('N/A:') self.extra_dim_selector = QComboBox() self.extra_dim_selector.currentIndexChanged.connect( self.on_extra_dim_selected) hbox = QHBoxLayout() hbox.addWidget(self.extra_dim_label) hbox.addWidget(self.extra_dim_selector) hbox.setContentsMargins(0, 0, 0, 0) self.extra_dim_container = QWidget() self.extra_dim_container.setLayout(hbox) self.extra_dim_container.setHidden(True) self.vbox.addWidget(self.extra_dim_container) def create_interp_input(self) -> None: grid = QGridLayout() self.interp_vert_selector = add_grid_combobox(grid, 0, 'Vertical Variable') self.interp_input = add_grid_lineedit(grid, 1, 'Desired Level', QDoubleValidator( 0.0, 10000.0, 50), required=True) self.interp_input.returnPressed.connect(self.on_interp_btn_clicked) btn = QPushButton('Interpolate') btn.clicked.connect(self.on_interp_btn_clicked) grid.addWidget(btn, 2, 1) self.interp_container = QGroupBox('Interpolate Vertical Level') self.interp_container.setCheckable(True) self.interp_container.setChecked(False) self.interp_container.toggled.connect(self.on_interp_toggled) self.interp_container.setLayout(grid) self.interp_container.setHidden(True) self.vbox.addWidget(self.interp_container) def create_dataset_selector(self) -> None: dataset_label = QLabel('Dataset:') self.dataset_selector = QComboBox() self.dataset_selector.currentIndexChanged.connect( self.on_dataset_selected) hbox = QHBoxLayout() hbox.addWidget(dataset_label) hbox.addWidget(self.dataset_selector) self.vbox.addLayout(hbox) def add_dataset(self, path: str) -> None: variables = gis4wrf.core.get_supported_wrf_nc_variables(path) times = gis4wrf.core.get_wrf_nc_time_steps(path) extra_dims = gis4wrf.core.get_wrf_nc_extra_dims(path) dataset_name = os.path.basename(path) is_new_dataset = dataset_name not in self.datasets self.datasets[dataset_name] = Dataset(dataset_name, path, variables, times, extra_dims) if is_new_dataset: self.dataset_selector.addItem(dataset_name, dataset_name) self.select_dataset(dataset_name) def select_dataset(self, dataset_name: str) -> None: index = self.dataset_selector.findData(dataset_name) self.dataset_selector.setCurrentIndex(index) def init_variable_selector(self) -> None: dataset = self.dataset selected = self.selected_variable.get(dataset.name) self.variable_selector.clear() for var_name, variable in sorted(dataset.variables.items(), key=lambda v: v[1].label): item = QListWidgetItem(variable.label) item.setData(Qt.UserRole, var_name) self.variable_selector.addItem(item) if var_name == selected: item.setSelected(True) if selected is None: self.extra_dim_container.hide() def init_time_selector(self) -> None: dataset = self.dataset self.time_selector.setMaximum(len(dataset.times) - 1) selected_time = self.selected_time.get(dataset.name, 0) self.select_time(selected_time) # force label update in case the index didn't change during dataset change self.on_time_selected(selected_time) def select_time(self, index: int) -> None: self.time_selector.setValue(index) def init_extra_dim_selector(self) -> None: dataset = self.dataset variable = self.variable extra_dim_name = variable.extra_dim_name if extra_dim_name is None: self.extra_dim_container.hide() return # prevent double layer replace, already happens in on_variable_selected() self.pause_replace_layer = True extra_dim = dataset.extra_dims[extra_dim_name] selected_extra_dim = self.selected_extra_dim.get( (dataset.name, extra_dim_name), 0) self.extra_dim_label.setText(extra_dim.label + ':') self.extra_dim_selector.clear() for step in extra_dim.steps: self.extra_dim_selector.addItem(step) self.extra_dim_selector.setCurrentIndex(selected_extra_dim) self.extra_dim_container.show() self.pause_replace_layer = False def init_interp_input(self, dataset_init: bool) -> None: if dataset_init: self.interp_vert_selector.clear() has_vert = False for variable in sorted(self.dataset.variables.values(), key=lambda v: v.label): if variable.extra_dim_name != 'bottom_top': continue has_vert = True self.interp_vert_selector.addItem(variable.label, variable.name) if not has_vert: self.extra_dim_container.setEnabled(True) self.interp_container.hide() else: variable = self.variable extra_dim_name = variable.extra_dim_name if extra_dim_name != 'bottom_top': self.interp_container.hide() return self.interp_container.show() def on_dataset_selected(self, index: int) -> None: self.init_variable_selector() self.init_time_selector() self.init_interp_input(True) previous_dataset = self.selected_dataset if previous_dataset is not None: gis4wrf.plugin.geo.remove_group(previous_dataset) self.selected_dataset = self.dataset_name def on_variable_selected(self, current: Optional[QListWidgetItem], previous: Optional[QListWidgetItem]) -> None: if current is None: return dataset = self.dataset var_name = current.data(Qt.UserRole) assert var_name == self.var_name self.selected_variable[dataset.name] = var_name self.init_extra_dim_selector() self.init_interp_input(False) self.replace_variable_layer() self.select_time_band_in_variable_layers() def on_time_selected(self, index: int) -> None: dataset = self.dataset self.selected_time[dataset.name] = index self.time_label.setText('Time: ' + dataset.times[index]) self.select_time_band_in_variable_layers() def on_extra_dim_selected(self, index: int) -> None: if index == -1: # happens when clearing the dropdown entries return variable = self.variable extra_dim_name = variable.extra_dim_name self.selected_extra_dim[(self.dataset_name, extra_dim_name)] = index self.replace_variable_layer() self.select_time_band_in_variable_layers() def on_interp_toggled(self, enabled: True) -> None: self.extra_dim_container.setEnabled(not enabled) self.replace_variable_layer() def on_interp_btn_clicked(self) -> None: self.replace_variable_layer() self.select_time_band_in_variable_layers() def replace_variable_layer(self) -> None: if self.pause_replace_layer: return if self.interp_enabled and self.interp_level is None: return dataset = self.dataset variable = self.variable extra_dim_index = self.extra_dim_index interp_level = self.interp_level interp_vert_name = self.interp_vert_name if interp_level is not None: extra_dim_index = None uri, dispose = gis4wrf.core.convert_wrf_nc_var_to_gdal_dataset( dataset.path, variable.name, extra_dim_index, interp_level, interp_vert_name) layer = gis4wrf.plugin.geo.load_layers( [(uri, variable.label, variable.name)], group_name=dataset.name, visible=True)[0] dispose_after_delete(layer, dispose) def select_time_band_in_variable_layers(self) -> None: dataset = self.dataset time_idx = self.time_index layers = gis4wrf.plugin.geo.get_raster_layers_in_group(dataset.name) for layer in layers: var_name = layer.shortName() if var_name in dataset.variables: gis4wrf.plugin.geo.switch_band(layer, time_idx) @property def dataset_name(self) -> str: return self.dataset_selector.currentData() @property def var_name(self) -> str: return self.variable_selector.currentItem().data(Qt.UserRole) @property def time_index(self) -> int: return self.time_selector.value() @property def extra_dim_index(self) -> Optional[int]: if self.variable.extra_dim_name is None: return None index = self.extra_dim_selector.currentIndex() assert index != -1 return index @property def interp_enabled(self): return self.interp_container.isVisible( ) and self.interp_container.isChecked() @property def interp_vert_name(self): if not self.interp_enabled: return None return self.interp_vert_selector.currentData() @property def interp_level(self) -> Optional[float]: if not self.interp_enabled: return None if not self.interp_input.is_valid(): return None return self.interp_input.value() @property def dataset(self) -> Dataset: return self.datasets[self.dataset_name] @property def variable(self) -> WRFNetCDFVariable: return self.dataset.variables[self.var_name]
class RegisterViewWidget(WidgetBase): def __init__(self, parent: QWidget): super(RegisterViewWidget, self).__init__(parent) self._registers = [] self._running_task: asyncio.Task = None self._visibility_selector = QComboBox(self) self._visibility_selector.addItem("Show all registers", lambda _: True) self._visibility_selector.addItem("Only configuration parameters", lambda r: r.mutable and r.persistent) # noinspection PyUnresolvedReferences self._visibility_selector.currentIndexChanged.connect( lambda _: self._on_visibility_changed()) self._reset_selected_button = make_button( self, "Reset selected", icon_name="clear-symbol", tool_tip=f"Reset the currently selected registers to their default " f"values. The restored values will be committed " f"immediately. This function is available only if a " f"default value is defined. [{RESET_SELECTED_SHORTCUT}]", on_clicked=self._do_reset_selected, ) self._reset_all_button = make_button( self, "Reset all", icon_name="skull-crossbones", tool_tip=f"Reset the all registers to their default " f"values. The restored values will be committed " f"immediately.", on_clicked=self._do_reset_all, ) self._read_selected_button = make_button( self, "Read selected", icon_name="process", tool_tip=f"Read the currently selected registers only " f"[{READ_SELECTED_SHORTCUT}]", on_clicked=self._do_read_selected, ) self._read_all_button = make_button( self, "Read all", icon_name="process-plus", tool_tip="Read all registers from the device", on_clicked=self._do_read_all, ) self._export_button = make_button( self, "Export", icon_name="export", tool_tip="Export configuration parameters", on_clicked=self._do_export, ) self._import_button = make_button( self, "Import", icon_name="import", tool_tip="Import configuration parameters", on_clicked=self._do_import, ) self._expand_all_button = make_button( self, "", icon_name="expand-arrow", tool_tip="Expand all namespaces", on_clicked=lambda: self._tree.expandAll(), ) self._collapse_all_button = make_button( self, "", icon_name="collapse-arrow", tool_tip="Collapse all namespaces", on_clicked=lambda: self._tree.collapseAll(), ) self._status_display = QLabel(self) self._status_display.setWordWrap(True) self._reset_selected_button.setEnabled(False) self._reset_all_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(False) self._export_button.setEnabled(False) self._import_button.setEnabled(False) self._tree = QTreeView(self) self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel) self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel) self._tree.setAnimated(True) self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection) self._tree.setAlternatingRowColors(True) self._tree.setContextMenuPolicy(Qt.ActionsContextMenu) # Not sure about this one. This hardcoded value may look bad on some platforms. self._tree.setIndentation(20) def add_action( callback: typing.Callable[[], None], icon_name: str, name: str, shortcut: typing.Optional[str] = None, ): action = QAction(get_icon(icon_name), name, self) # noinspection PyUnresolvedReferences action.triggered.connect(callback) if shortcut: action.setShortcut(shortcut) action.setAutoRepeat(False) try: action.setShortcutVisibleInContextMenu(True) except AttributeError: pass # This feature is not available in PyQt before 5.10 self._tree.addAction(action) add_action(self._do_read_all, "process-plus", "Read all registers") add_action( self._do_read_selected, "process", "Read selected registers", READ_SELECTED_SHORTCUT, ) add_action( self._do_reset_selected, "clear-symbol", "Reset selected to default", RESET_SELECTED_SHORTCUT, ) self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.VALUE), EditorDelegate(self._tree, self._display_status), ) # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom # decoration position in order to be able to use center alignment. Left or right positions do not work here. self._tree.setItemDelegateForColumn( int(Model.ColumnIndices.FLAGS), StyleOptionModifyingDelegate( self._tree, decoration_position=QStyleOptionViewItem.Top, # Important decoration_alignment=Qt.AlignCenter, ), ) header: QHeaderView = self._tree.header() header.setSectionResizeMode(QHeaderView.ResizeToContents) header.setStretchLastSection( False) # Horizontal scroll bar doesn't work if this is enabled buttons_layout = QGridLayout() buttons_layout.addWidget(self._read_selected_button, 0, 0) buttons_layout.addWidget(self._reset_selected_button, 0, 2) buttons_layout.addWidget(self._read_all_button, 1, 0) buttons_layout.addWidget(self._reset_all_button, 1, 2) buttons_layout.addWidget(self._import_button, 2, 0) buttons_layout.addWidget(self._export_button, 2, 2) for col in range(3): buttons_layout.setColumnStretch(col, 1) layout = lay_out_vertically( (self._tree, 1), buttons_layout, lay_out_horizontally( self._visibility_selector, (None, 1), self._expand_all_button, self._collapse_all_button, ), self._status_display, ) self.setLayout(layout) def reset(self): self.setup([]) def setup(self, registers: typing.Iterable[Register]): self._registers = list(registers) self._on_visibility_changed() def _replace_model( self, register_visibility_predicate: typing.Callable[[Register], bool]): # Cancel all operations that might be pending on the old model self._cancel_task() old_model = self._tree.model() # Configure the new model filtered_registers = list( filter(register_visibility_predicate, self._registers)) # It is important to set the Tree widget as the parent in order to let the widget take ownership new_model = Model(self._tree, filtered_registers) _logger.info("New model %r", new_model) self._tree.setModel(new_model) # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured self._tree.selectionModel().selectionChanged.connect( lambda *_: self._on_selection_changed()) # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it. # We could call deleteLater() on it, but it seems dangerous, because if that something ever decided # to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on # our hands. The horror! if old_model is not None: import gc model_referrers = gc.get_referrers(old_model) if len(model_referrers) > 1: _logger.warning( "Extra references to the old model %r: %r", old_model, model_referrers, ) # Update the widget - all root items are expanded by default for row in itertools.count(): index = self._tree.model().index(row, 0) if not index.isValid(): break self._tree.expand(index) self._reset_selected_button.setEnabled(False) self._read_selected_button.setEnabled(False) self._read_all_button.setEnabled(len(filtered_registers) > 0) self._reset_all_button.setEnabled(len(filtered_registers) > 0) self._export_button.setEnabled(len(filtered_registers) > 0) self._import_button.setEnabled(len(filtered_registers) > 0) self._display_status(f"{len(filtered_registers)} registers loaded") def _on_visibility_changed(self): self._replace_model(self._visibility_selector.currentData()) def _on_selection_changed(self): selected = self._get_selected_registers() self._reset_selected_button.setEnabled( any(map(lambda r: r.has_default_value, selected))) self._read_selected_button.setEnabled(len(selected) > 0) def _do_read_selected(self): selected = self._get_selected_registers() if selected: self._read_specific(selected) else: self._display_status("No registers are selected, nothing to read") def _do_reset_selected(self): rv = {} for r in self._get_selected_registers(): if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_reset_all(self): rv = {} for r in self._registers: if r.has_default_value: rv[r] = r.default_value self._write_specific(rv) def _do_read_all(self): self._read_specific(self._tree.model().registers) def _do_import(self): import_registers(parent=self, registers=self._registers) def _do_export(self): export_registers(parent=self, registers=self._registers) def _read_specific(self, registers: typing.List[Register]): total_registers_read = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_read total_registers_read = total_registers self._display_status( f"Reading {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info("Reading registers: %r", [r.name for r in registers]) mod: Model = self._tree.model() await mod.read(registers=registers, progress_callback=progress_callback) except asyncio.CancelledError: self._display_status(f"Read has been cancelled") raise except Exception as ex: _logger.exception("Register read failed") show_error("Read failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not read registers: {ex!r}") else: self._display_status( f"{total_registers_read} registers have been read") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _write_specific(self, register_value_mapping: typing.Dict[Register, typing.Any]): total_registers_assigned = None def progress_callback(register: Register, current_register_index: int, total_registers: int): nonlocal total_registers_assigned total_registers_assigned = total_registers self._display_status( f"Writing {register.name!r} " f"({current_register_index + 1} of {total_registers})") async def executor(): try: _logger.info( "Writing registers: %r", [r.name for r in register_value_mapping.keys()], ) mod: Model = self._tree.model() await mod.write( register_value_mapping=register_value_mapping, progress_callback=progress_callback, ) except asyncio.CancelledError: self._display_status(f"Write has been cancelled") raise except Exception as ex: _logger.exception("Register write failed") show_error("Write failed", "Could not read registers", repr(ex), self) self._display_status(f"Could not write registers: {ex!r}") else: self._display_status( f"{total_registers_assigned} registers have been written") self._cancel_task() self._running_task = asyncio.get_event_loop().create_task(executor()) def _get_selected_registers(self) -> typing.List[Register]: selected_indexes: typing.List[ QModelIndex] = self._tree.selectedIndexes() selected_registers = set() for si in selected_indexes: r = Model.get_register_from_index(si) if r is not None: selected_registers.add(r) # Beware that sets are not sorted, this may lead to weird user experience when watching the registers # read in a funny order. return list(sorted(selected_registers, key=lambda x: x.name)) def _cancel_task(self): # noinspection PyBroadException try: self._running_task.cancel() except Exception: pass else: _logger.info("A running task had to be cancelled: %r", self._running_task) finally: self._running_task = None def _display_status(self, text=None): self._status_display.setText(text)
def createTranformControls(grp_title): grp_tranform = QGroupBox(grp_title) lyt_grid = QGridLayout() # Camera Flip ckbox_flip_h = QCheckBox("Flip Horizontal") ckbox_flip_v = QCheckBox("Flip Vertical") ckbox_flip_h.stateChanged.connect( lambda: setDisplayFlipHorizontal(ckbox_flip_h.checkState())) ckbox_flip_v.stateChanged.connect( lambda: setDisplayFlipVertical(ckbox_flip_v.checkState())) # Camera Rotate lbl_rotate = QLabel("Rotate: ") cbbox_rotate = QComboBox() cbbox_rotate.addItem(("0: none - No rotation"), 0) cbbox_rotate.addItem(("1: 90CW - Rotate 90 degrees clockwise"), 1) cbbox_rotate.addItem(("2: 90CCW - Rotate 90 degrees counter-clockwise"), 2) cbbox_rotate.addItem(("3: 180 - Rotate 180 degrees"), 3) cbbox_rotate.currentIndexChanged.connect( lambda: setRotate(cbbox_rotate.currentData())) # Camera Crop ckbox_crop = QCheckBox("Crop") lbl_crop_x = QLabel("Crop X: ") lbl_crop_y = QLabel("Crop Y: ") lbl_crop_w = QLabel("Crop Width: ") lbl_crop_h = QLabel("Crop Height: ") edi_crop_x = QLineEdit("0") edi_crop_y = QLineEdit("0") edi_crop_w = QLineEdit("640") edi_crop_h = QLineEdit("360") edi_crop_x.textChanged.connect( lambda: setCropValue("x", int(edi_crop_x.text()))) edi_crop_y.textChanged.connect( lambda: setCropValue("y", int(edi_crop_y.text()))) edi_crop_w.textChanged.connect( lambda: setCropValue("w", int(edi_crop_w.text()))) edi_crop_h.textChanged.connect( lambda: setCropValue("h", int(edi_crop_h.text()))) edi_crop_x.setEnabled(False) edi_crop_y.setEnabled(False) edi_crop_w.setEnabled(False) edi_crop_h.setEnabled(False) ckbox_crop.stateChanged.connect(lambda: setCrop( ckbox_crop, [edi_crop_x, edi_crop_y, edi_crop_w, edi_crop_h])) # Adding Component to layout lyt_grid.addWidget(ckbox_flip_h, 0, 1) lyt_grid.addWidget(ckbox_flip_v, 0, 2) lyt_grid.addWidget(lbl_rotate, 2, 0, Qt.AlignRight) lyt_grid.addWidget(cbbox_rotate, 2, 1, 1, 3) lyt_grid.addWidget(ckbox_crop, 3, 1) lyt_grid.addWidget(lbl_crop_x, 4, 0, Qt.AlignRight) lyt_grid.addWidget(edi_crop_x, 4, 1) lyt_grid.addWidget(lbl_crop_y, 4, 2, Qt.AlignRight) lyt_grid.addWidget(edi_crop_y, 4, 3) lyt_grid.addWidget(lbl_crop_w, 5, 0, Qt.AlignRight) lyt_grid.addWidget(edi_crop_w, 5, 1) lyt_grid.addWidget(lbl_crop_h, 5, 2, Qt.AlignRight) lyt_grid.addWidget(edi_crop_h, 5, 3) grp_tranform.setLayout(lyt_grid) return grp_tranform
class MidiSettings(CueSettingsPage): Name = 'MIDI controls' def __init__(self, cue_class, **kwargs): super().__init__(cue_class, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setAlignment(Qt.AlignTop) self.midiGroup = QGroupBox(self) self.midiGroup.setTitle('MIDI') self.midiGroup.setEnabled(check_module('midi')) self.midiGroup.setLayout(QGridLayout()) self.layout().addWidget(self.midiGroup) self.midiModel = SimpleTableModel(['Type', 'Channel', 'Note', 'Action']) self.midiView = MidiView(cue_class, parent=self.midiGroup) self.midiView.setModel(self.midiModel) self.midiGroup.layout().addWidget(self.midiView, 0, 0, 1, 2) self.addButton = QPushButton('Add', self.midiGroup) self.addButton.clicked.connect(self.__new_message) self.midiGroup.layout().addWidget(self.addButton, 1, 0) self.removeButton = QPushButton('Remove', self.midiGroup) self.removeButton.clicked.connect(self.__remove_message) self.midiGroup.layout().addWidget(self.removeButton, 1, 1) self.midiCapture = QPushButton('Capture', self.midiGroup) self.midiCapture.clicked.connect(self.capture_message) self.midiGroup.layout().addWidget(self.midiCapture, 2, 0) self.msgTypeCombo = QComboBox(self.midiGroup) self.msgTypeCombo.addItem('Filter "note on"') self.msgTypeCombo.setItemData(0, 'note_on', Qt.UserRole) self.msgTypeCombo.addItem('Filter "note off"') self.msgTypeCombo.setItemData(1, 'note_off', Qt.UserRole) self.midiGroup.layout().addWidget(self.msgTypeCombo, 2, 1) self._default_action = self._cue_class.CueActions[0].name def enable_check(self, enabled): self.midiGroup.setCheckable(enabled) self.midiGroup.setChecked(False) def get_settings(self): settings = {} checkable = self.midiGroup.isCheckable() if not (checkable and not self.midiGroup.isChecked()): messages = [] for row in self.midiModel.rows: message = Midi.str_from_values(row[0], row[1], row[2]) messages.append((message, row[-1])) if messages: settings['midi'] = messages return settings def load_settings(self, settings): if 'midi' in settings: for options in settings['midi']: m_type, channel, note = Midi.from_string(options[0]) self.midiModel.append_row(m_type, channel, note, options[1]) def capture_message(self): handler = MIDIInputHandler() handler.alternate_mode = True handler.new_message_alt.connect(self.__add_message) QMessageBox.information(self, 'Input', 'Listening MIDI events ...') handler.new_message_alt.disconnect(self.__add_message) handler.alternate_mode = False def __add_message(self, msg): if self.msgTypeCombo.currentData(Qt.UserRole) == msg.type: self.midiModel.append_row(msg.type, msg.channel, msg.note, self._default_action) def __new_message(self): message_type = self.msgTypeCombo.currentData(Qt.UserRole) self.midiModel.append_row(message_type, 0, 0, self._default_action) def __remove_message(self): self.midiModel.removeRow(self.midiView.currentIndex().row())
class SubwindowBrowserSources(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('browser.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(mainWindow.size().width() * 0.8, self.sizeHint().height())) relativeChange = QPoint(mainWindow.size().width() / 2, mainWindow.size().height() / 3) -\ QPoint(self.size().width() / 2, self.size().height() / 3) self.move(mainWindow.pos() + relativeChange) self.setWindowTitle(_("Browser Sources")) except Exception: module_logger.exception("message") def createTabs(self, tab): """Create tabs.""" self.tabs = QTabWidget() self.createFormGroupIntro() self.createFormGroupMapstats() self.createFormGroupMapBox() self.createFormGroupMapLandscape() self.createFormGroupVetoes() # Add tabs self.tabs.addTab(self.formGroupIntro, _("Intros")) self.tabs.addTab(self.formGroupMapstats, _("Mapstats")) self.tabs.addTab(self.formGroupMapBox, _("Box Map Icons")) self.tabs.addTab(self.formGroupMapLandscape, _("Landscape Map Icons")) self.tabs.addTab(self.formGroupVetoes, _("Veto Icons")) table = dict() table['intro'] = 0 table['mapstats'] = 1 table['mapicons_box'] = 2 table['mapicons_landscape'] = 3 table['vetoes'] = 4 self.tabs.setCurrentIndex( table.get(tab, SubwindowBrowserSources.current_tab)) self.tabs.currentChanged.connect(self.tabChanged) @classmethod def tabChanged(cls, idx): """Save the current tab.""" SubwindowBrowserSources.current_tab = idx def addHotkey(self, ident, label): """Add a hotkey to the layout.""" element = HotkeyLayout( self, ident, label, scctool.settings.config.parser.get("Intros", ident)) self.hotkeys[ident] = element return element def connectHotkeys(self): """Connect the hotkeys.""" for ident, key in self.hotkeys.items(): if ident == 'hotkey_debug': for ident2, key2 in self.hotkeys.items(): if ident == ident2: continue key.modified.connect(key2.check_dublicate) key.modified.connect(self.hotkeyChanged) def hotkeyChanged(self, key, ident): """Handle change of hotkeys.""" self.changed() if (ident == 'hotkey_player1' and self.cb_single_hotkey.isChecked()): self.hotkeys['hotkey_player2'].setData( self.hotkeys['hotkey_player1'].getKey()) if not key: return if ((ident == 'hotkey_player1' and key == self.hotkeys['hotkey_player2'].getKey()['name']) or (ident == 'hotkey_player2' and key == self.hotkeys['hotkey_player1'].getKey()['name'])): self.cb_single_hotkey.setChecked(True) if (ident in ['hotkey_player1', 'hotkey_player2'] and key == self.hotkeys['hotkey_debug'].getKey()['name']): self.hotkeys['hotkey_debug'].clear() def singleHotkeyChanged(self): """Handle single hotkey changed event.""" checked = self.cb_single_hotkey.isChecked() self.hotkeys['hotkey_player2'].setDisabled(checked) if checked: self.hotkeys['hotkey_player2'].setData( self.hotkeys['hotkey_player1'].getKey()) elif (self.hotkeys['hotkey_player1'].getKey() == self.hotkeys['hotkey_player2'].getKey()): self.hotkeys['hotkey_player2'].clear() def createFormGroupMapstats(self): """Create the form group for mapstats.""" self.formGroupMapstats = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("General")) layout = QFormLayout() container = QHBoxLayout() self.qb_boxStyle = StyleComboBox( scctool.settings.casting_html_dir + "/src/css/mapstats", "mapstats") self.qb_boxStyle.connect2WS(self.controller, 'mapstats') label = QLabel(_("Style:")) label.setMinimumWidth(120) button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( scctool.settings.casting_html_dir + "/mapstats.html")) container.addWidget(self.qb_boxStyle, 2) container.addWidget(button, 1) layout.addRow(label, container) self.cb_mappool = QComboBox() self.cb_mappool.addItem(_("Current Ladder Map Pool")) self.cb_mappool.addItem(_("Custom Map Pool (defined below)")) self.cb_mappool.addItem(_("Currently entered Maps only")) self.cb_mappool.setCurrentIndex( self.controller.mapstatsManager.getMapPoolType()) self.cb_mappool.currentIndexChanged.connect(self.changed) layout.addRow(QLabel(_("Map Pool:")), self.cb_mappool) self.cb_autoset_map = QCheckBox(_("Select the next map automatically")) self.cb_autoset_map.setChecked( scctool.settings.config.parser.getboolean("Mapstats", "autoset_next_map")) self.cb_autoset_map.stateChanged.connect(self.changed) label = QLabel(_("Next Map:")) label.setMinimumWidth(120) layout.addRow(label, self.cb_autoset_map) self.cb_mark_played = QCheckBox(_("Mark already played maps")) self.cb_mark_played.setChecked( scctool.settings.config.parser.getboolean("Mapstats", "mark_played")) self.cb_mark_played.stateChanged.connect(self.changed) label = QLabel(_("Mark:")) label.setMinimumWidth(120) layout.addRow(label, self.cb_mark_played) self.cb_mark_vetoed = QCheckBox(_("Mark vetoed maps")) self.cb_mark_vetoed.setChecked( scctool.settings.config.parser.getboolean("Mapstats", "mark_vetoed")) self.cb_mark_vetoed.stateChanged.connect(self.changed) label = QLabel(" ") label.setMinimumWidth(120) layout.addRow(label, self.cb_mark_vetoed) box.setLayout(layout) mainLayout.addWidget(box) box = QGroupBox(_("Custom Map Pool")) layout = QGridLayout() self.maplist = QListWidget() self.maplist.setSortingEnabled(True) ls = list(self.controller.mapstatsManager.getCustomMapPool()) self.maplist.addItems(ls) self.maplist.setCurrentItem(self.maplist.item(0)) layout.addWidget(self.maplist, 0, 0, 3, 1) qb_add = QPushButton() pixmap = QIcon(scctool.settings.getResFile('add.png')) qb_add.setIcon(pixmap) qb_add.clicked.connect(self.addMap) layout.addWidget(qb_add, 0, 1) qb_remove = QPushButton() pixmap = QIcon(scctool.settings.getResFile('delete.png')) qb_remove.clicked.connect(self.removeMap) qb_remove.setIcon(pixmap) layout.addWidget(qb_remove, 1, 1) self.sc_removeMap = QShortcut(QKeySequence("Del"), self) self.sc_removeMap.setAutoRepeat(False) self.sc_removeMap.activated.connect(self.removeMap) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupMapstats.setLayout(mainLayout) def addMap(self): """Add a map to the list.""" maplist = list(scctool.settings.maps) for i in range(self.maplist.count()): sc2map = str(self.maplist.item(i).text()) if sc2map in maplist: maplist.remove(sc2map) if len(maplist) > 0: sc2map, ok = QInputDialog.getItem(self, _('Add Map'), _('Please select a map') + ':', maplist, editable=False) if ok: self.__dataChanged = True item = QListWidgetItem(sc2map) self.maplist.addItem(item) self.maplist.setCurrentItem(item) else: QMessageBox.information( self, _("No maps available"), _('All available maps have already been added.')) def removeMap(self): """Remove a map from the list.""" item = self.maplist.currentItem() if item: self.maplist.takeItem(self.maplist.currentRow()) self.__dataChanged = True def createFormGroupMapBox(self): """Create a QWidget for boxed map icons.""" self.formGroupMapBox = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("General")) layout = QFormLayout() container = QHBoxLayout() self.qb_boxStyle = StyleComboBox( scctool.settings.casting_html_dir + "/src/css/mapicons_box", "mapicons_box") self.qb_boxStyle.connect2WS(self.controller, 'mapicons_box') label = QLabel(_("Style:")) label.setMinimumWidth(120) button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( scctool.settings.casting_html_dir + "/mapicons_box_1.html")) container.addWidget(self.qb_boxStyle, 2) container.addWidget(button, 1) layout.addRow(label, container) self.sb_padding_box = QDoubleSpinBox() self.sb_padding_box.setRange(0, 50) self.sb_padding_box.setDecimals(1) self.sb_padding_box.setValue( scctool.settings.config.parser.getfloat("MapIcons", "padding_box")) self.sb_padding_box.setSuffix(" " + _("Pixel")) self.sb_padding_box.valueChanged.connect( lambda x: self.changePadding('box', x)) layout.addRow(QLabel(_("Icon Padding:") + " "), self.sb_padding_box) box.setLayout(layout) mainLayout.addWidget(box) options = self.controller.matchControl.scopes self.scope_box = dict() for idx in range(0, 3): self.scope_box[idx] = ScopeGroupBox( _("Icon Set {} Scope".format(idx + 1)), options, scctool.settings.config.parser.get( "MapIcons", "scope_box_{}".format(idx + 1)), self) self.scope_box[idx].dataModified.connect(self.changed) mainLayout.addWidget(self.scope_box[idx]) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupMapBox.setLayout(mainLayout) def createFormGroupVetoes(self): """Create a QWidget for veto icons.""" self.formGroupVetoes = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("General")) layout = QFormLayout() container = QHBoxLayout() self.qb_boxStyle = StyleComboBox( scctool.settings.casting_html_dir + "/src/css/vetoes", "vetoes") self.qb_boxStyle.connect2WS(self.controller, 'vetoes') label = QLabel(_("Style:")) label.setMinimumWidth(120) button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( scctool.settings.casting_html_dir + "/vetoes.html")) container.addWidget(self.qb_boxStyle, 2) container.addWidget(button, 1) layout.addRow(label, container) self.sb_padding_box = QDoubleSpinBox() self.sb_padding_box.setRange(0, 50) self.sb_padding_box.setDecimals(1) self.sb_padding_box.setValue( scctool.settings.config.parser.getfloat("Vetoes", "padding")) self.sb_padding_box.setSuffix(" " + _("Pixel")) self.sb_padding_box.valueChanged.connect( lambda x: self.changePadding('vetoes', x)) layout.addRow(QLabel(_("Icon Padding:") + " "), self.sb_padding_box) box.setLayout(layout) mainLayout.addWidget(box) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupVetoes.setLayout(mainLayout) def createFormGroupMapLandscape(self): """Create a QWidget for the landscape map icons.""" self.formGroupMapLandscape = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("General")) layout = QFormLayout() container = QHBoxLayout() self.qb_boxStyle = StyleComboBox( scctool.settings.casting_html_dir + "/src/css/mapicons_landscape", "mapicons_landscape") self.qb_boxStyle.connect2WS(self.controller, 'mapicons_landscape') label = QLabel(_("Style:")) label.setMinimumWidth(120) button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( scctool.settings.casting_html_dir + "/mapicons_landscape_1.html")) container.addWidget(self.qb_boxStyle, 2) container.addWidget(button, 1) layout.addRow(label, container) self.sb_padding_landscape = QDoubleSpinBox() self.sb_padding_landscape.setRange(0, 50) self.sb_padding_landscape.setDecimals(1) self.sb_padding_landscape.setValue( scctool.settings.config.parser.getfloat("MapIcons", "padding_landscape")) self.sb_padding_landscape.setSuffix(" " + _("Pixel")) self.sb_padding_landscape.valueChanged.connect( lambda x: self.changePadding('landscape', x)) layout.addRow(QLabel(_("Icon Padding:") + " "), self.sb_padding_landscape) box.setLayout(layout) mainLayout.addWidget(box) options = self.controller.matchControl.scopes self.scope_landscape = dict() for idx in range(0, 3): self.scope_landscape[idx] = ScopeGroupBox( _("Icon Set {} Scope".format(idx + 1)), options, scctool.settings.config.parser.get( "MapIcons", "scope_landscape_{}".format(idx + 1)), self) self.scope_landscape[idx].dataModified.connect(self.changed) mainLayout.addWidget(self.scope_landscape[idx]) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupMapLandscape.setLayout(mainLayout) def createFormGroupIntro(self): """Create forms for websocket connection to intro.""" self.formGroupIntro = QWidget() mainLayout = QVBoxLayout() box = QGroupBox(_("Style")) layout = QHBoxLayout() styleqb = StyleComboBox( scctool.settings.casting_html_dir + "/src/css/intro", "intro") styleqb.connect2WS(self.controller, 'intro') button = QPushButton(_("Show in Browser")) button.clicked.connect(lambda: self.openHTML( scctool.settings.casting_html_dir + "/intro.html")) layout.addWidget(styleqb, 2) layout.addWidget(button, 1) box.setLayout(layout) mainLayout.addWidget(box) self.hotkeyBox = QGroupBox(_("Hotkeys")) layout = QVBoxLayout() self.controller.websocketThread.unregister_hotkeys(force=True) self.cb_single_hotkey = QCheckBox( _("Use a single hotkey for both players")) self.cb_single_hotkey.stateChanged.connect(self.singleHotkeyChanged) layout.addWidget(self.cb_single_hotkey) self.hotkeys = dict() layout.addLayout(self.addHotkey("hotkey_player1", _("Player 1"))) layout.addLayout(self.addHotkey("hotkey_player2", _("Player 2"))) layout.addLayout(self.addHotkey("hotkey_debug", _("Debug"))) self.cb_single_hotkey.setChecked(self.hotkeys['hotkey_player1'].getKey( ) == self.hotkeys['hotkey_player2'].getKey()) self.connectHotkeys() label = QLabel( _("Player 1 is always the player your observer" " camera is centered on at start of a game.")) layout.addWidget(label) self.hotkeyBox.setLayout(layout) mainLayout.addWidget(self.hotkeyBox) self.introBox = QGroupBox(_("Animation")) layout = QFormLayout() self.cb_animation = QComboBox() animation = scctool.settings.config.parser.get("Intros", "animation") currentIdx = 0 idx = 0 options = dict() options['Fly-In'] = _("Fly-In") options['Slide'] = _("Slide") options['Fanfare'] = _("Fanfare") for key, item in options.items(): self.cb_animation.addItem(item, key) if (key == animation): currentIdx = idx idx += 1 self.cb_animation.setCurrentIndex(currentIdx) self.cb_animation.currentIndexChanged.connect(self.changed) label = QLabel(_("Animation:") + " ") label.setMinimumWidth(120) layout.addRow(label, self.cb_animation) self.sb_displaytime = QDoubleSpinBox() self.sb_displaytime.setRange(0, 10) self.sb_displaytime.setDecimals(1) self.sb_displaytime.setValue( scctool.settings.config.parser.getfloat("Intros", "display_time")) self.sb_displaytime.setSuffix(" " + _("Seconds")) self.sb_displaytime.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Display Duration:") + " "), self.sb_displaytime) self.sl_sound = QSlider(Qt.Horizontal) self.sl_sound.setMinimum(0) self.sl_sound.setMaximum(20) self.sl_sound.setValue( scctool.settings.config.parser.getint("Intros", "sound_volume")) self.sl_sound.setTickPosition(QSlider.TicksBothSides) self.sl_sound.setTickInterval(1) self.sl_sound.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Sound Volume:") + " "), self.sl_sound) self.introBox.setLayout(layout) mainLayout.addWidget(self.introBox) self.ttsBox = QGroupBox(_("Text-to-Speech")) layout = QFormLayout() self.cb_tts_active = QCheckBox() self.cb_tts_active.setChecked( scctool.settings.config.parser.getboolean("Intros", "tts_active")) self.cb_tts_active.stateChanged.connect(self.changed) label = QLabel(_("Activate Text-to-Speech:") + " ") label.setMinimumWidth(120) layout.addRow(label, self.cb_tts_active) self.icons = {} self.icons['MALE'] = QIcon(scctool.settings.getResFile('male.png')) self.icons['FEMALE'] = QIcon(scctool.settings.getResFile('female.png')) self.cb_tts_voice = QComboBox() currentIdx = 0 idx = 0 tts_voices = self.controller.tts.getVoices() tts_voice = scctool.settings.config.parser.get("Intros", "tts_voice") for voice in tts_voices: self.cb_tts_voice.addItem(self.icons[voice['ssmlGender']], ' ' + voice['name'], voice['name']) if (voice['name'] == tts_voice): currentIdx = idx idx += 1 self.cb_tts_voice.setCurrentIndex(currentIdx) self.cb_tts_voice.currentIndexChanged.connect(self.changed) layout.addRow(QLabel(_("Voice:") + " "), self.cb_tts_voice) self.ttsBox.setStyleSheet("QComboBox { combobox-popup: 0; }") self.ttsBox.setLayout(layout) mainLayout.addWidget(self.ttsBox) self.sb_tts_pitch = QDoubleSpinBox() self.sb_tts_pitch.setRange(-20, 20) self.sb_tts_pitch.setDecimals(2) self.sb_tts_pitch.setValue( scctool.settings.config.parser.getfloat("Intros", "tts_pitch")) self.sb_tts_pitch.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Pitch:") + " "), self.sb_tts_pitch) self.sb_tts_rate = QDoubleSpinBox() self.sb_tts_rate.setRange(0.25, 4.00) self.sb_tts_rate.setSingleStep(0.1) self.sb_tts_rate.setDecimals(2) self.sb_tts_rate.setValue( scctool.settings.config.parser.getfloat("Intros", "tts_rate")) self.sb_tts_rate.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Rate:") + " "), self.sb_tts_rate) self.cb_tts_scope = QComboBox() scope = scctool.settings.config.parser.get("Intros", "tts_scope") currentIdx = 0 idx = 0 options = self.controller.tts.getOptions() for key, item in options.items(): self.cb_tts_scope.addItem(item['desc'], key) if (key == scope): currentIdx = idx idx += 1 self.cb_tts_scope.setCurrentIndex(currentIdx) self.cb_tts_scope.currentIndexChanged.connect(self.changed) layout.addRow(QLabel(_("Line:") + " "), self.cb_tts_scope) self.sl_tts_sound = QSlider(Qt.Horizontal) self.sl_tts_sound.setMinimum(0) self.sl_tts_sound.setMaximum(20) self.sl_tts_sound.setValue( scctool.settings.config.parser.getint("Intros", "tts_volume")) self.sl_tts_sound.setTickPosition(QSlider.TicksBothSides) self.sl_tts_sound.setTickInterval(1) self.sl_tts_sound.valueChanged.connect(self.changed) layout.addRow(QLabel(_("Sound Volume:") + " "), self.sl_tts_sound) text = _( "Text-to-Speech provided by Google-Cloud is paid for " "by StarCraft Casting Tool Patrons. To keep this service up " "consider becoming a <a href='{patreon}'>Patron</a> yourself. " "You can test all voices at {tts}.") patreon = 'https://www.patreon.com/StarCraftCastingTool' url = 'https://cloud.google.com/text-to-speech/' tts = "<a href='{}'>cloud.google.com/text-to-speech</a>" tts = tts.format(url) label = QLabel(text.format(patreon=patreon, tts=tts)) label.setAlignment(Qt.AlignJustify) label.setOpenExternalLinks(True) label.setWordWrap(True) label.setMargin(5) layout.addRow(label) mainLayout.addItem( QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.formGroupIntro.setLayout(mainLayout) 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): self.saveWebsocketdata() maps = list() for i in range(self.maplist.count()): maps.append(str(self.maplist.item(i).text()).strip()) self.controller.mapstatsManager.setCustomMapPool(maps) self.controller.mapstatsManager.setMapPoolType( self.cb_mappool.currentIndex()) scctool.settings.config.parser.set( "Mapstats", "autoset_next_map", str(self.cb_autoset_map.isChecked())) scctool.settings.config.parser.set( "Mapstats", "mark_played", str(self.cb_mark_played.isChecked())) scctool.settings.config.parser.set( "Mapstats", "mark_vetoed", str(self.cb_mark_vetoed.isChecked())) self.controller.mapstatsManager.sendMapPool() self.mainWindow.updateAllMapButtons() for idx in range(0, 3): scctool.settings.config.parser.set( "MapIcons", "scope_box_{}".format(idx + 1), self.scope_box[idx].getScope()) scctool.settings.config.parser.set( "MapIcons", "scope_landscape_{}".format(idx + 1), self.scope_landscape[idx].getScope()) self.controller.matchMetaDataChanged() self.__dataChanged = False # self.controller.refreshButtonStatus() def saveWebsocketdata(self): """Save Websocket data.""" for ident, key in self.hotkeys.items(): string = scctool.settings.config.dumpHotkey(key.getKey()) scctool.settings.config.parser.set("Intros", ident, string) scctool.settings.config.parser.set("Intros", "display_time", str(self.sb_displaytime.value())) scctool.settings.config.parser.set("Intros", "sound_volume", str(self.sl_sound.value())) scctool.settings.config.parser.set( "Intros", "animation", self.cb_animation.currentData().strip()) scctool.settings.config.parser.set( "Intros", "tts_voice", self.cb_tts_voice.currentData().strip()) scctool.settings.config.parser.set( "Intros", "tts_scope", self.cb_tts_scope.currentData().strip()) scctool.settings.config.parser.set("Intros", "tts_active", str(self.cb_tts_active.isChecked())) scctool.settings.config.parser.set("Intros", "tts_volume", str(self.sl_tts_sound.value())) scctool.settings.config.parser.set("Intros", "tts_pitch", str(self.sb_tts_pitch.value())) scctool.settings.config.parser.set("Intros", "tts_rate", str(self.sb_tts_rate.value())) def openHTML(self, file): """Open file in browser.""" self.controller.openURL(scctool.settings.getAbsPath(file)) def changePadding(self, scope, padding): """Change the padding.""" if scope == 'vetoes': scctool.settings.config.parser.set("Vetoes", "padding", str(padding)) self.controller.websocketThread.changePadding("vetoes", padding) else: scctool.settings.config.parser.set("MapIcons", f"padding_{scope}", str(padding)) self.controller.websocketThread.changePadding( f"mapicons_{scope}", padding) 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): self.controller.updateHotkeys() 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() self.controller.updateHotkeys() event.accept() except Exception: module_logger.exception("message")
class OWCSVFileImport(widget.OWWidget): name = "CSV File Import" description = "Import a data table from a CSV formatted file." icon = "icons/CSVFile.svg" outputs = [ widget.OutputSignal( name="Data", type=Orange.data.Table, doc="Loaded data set."), widget.OutputSignal( name="Data Frame", type=pd.DataFrame, doc="" ) ] class Error(widget.OWWidget.Error): error = widget.Msg( "Unexpected error" ) encoding_error = widget.Msg( "Encoding error\n" "The file might be encoded in an unsupported encoding or it " "might be binary" ) #: Paths and options of files accessed in a 'session' _session_items = settings.Setting( [], schema_only=True) # type: List[Tuple[str, dict]] #: Saved dialog state (last directory and selected filter) dialog_state = settings.Setting({ "directory": "", "filter": "" }) # type: Dict[str, str] MaxHistorySize = 50 want_main_area = False buttons_area_orientation = None def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self.__committimer = QTimer(self, singleShot=True) self.__committimer.timeout.connect(self.commit) self.__executor = qconcurrent.ThreadExecutor() self.__watcher = None # type: Optional[qconcurrent.FutureWatcher] self.controlArea.layout().setSpacing(-1) # reset spacing grid = QGridLayout() grid.addWidget(QLabel("File:", self), 0, 0, 1, 1) self.import_items_model = QStandardItemModel(self) self.recent_combo = QComboBox( self, objectName="recent-combo", toolTip="Recent files.", sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, ) self.recent_combo.setModel(self.import_items_model) self.recent_combo.activated.connect(self.activate_recent) self.recent_combo.setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self.browse_button = QPushButton( "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon), toolTip="Browse filesystem", autoDefault=False, ) self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.browse_button.clicked.connect(self.browse) grid.addWidget(self.recent_combo, 0, 1, 1, 1) grid.addWidget(self.browse_button, 0, 2, 1, 1) self.controlArea.layout().addLayout(grid) ########### # Info text ########### box = gui.widgetBox(self.controlArea, "Info", addSpace=False) self.summary_text = QTextBrowser( verticalScrollBarPolicy=Qt.ScrollBarAsNeeded, readOnly=True, ) self.summary_text.viewport().setBackgroundRole(QPalette.NoRole) self.summary_text.setFrameStyle(QTextBrowser.NoFrame) self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4) self.summary_text.viewport().setAutoFillBackground(False) box.layout().addWidget(self.summary_text) button_box = QDialogButtonBox( orientation=Qt.Horizontal, standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry ) self.load_button = b = button_box.button(QDialogButtonBox.Retry) b.setText("Load") b.clicked.connect(self.__committimer.start) b.setEnabled(False) b.setDefault(True) self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel) b.clicked.connect(self.cancel) b.setEnabled(False) b.setAutoDefault(False) self.import_options_button = QPushButton( "Import Options…", enabled=False, autoDefault=False, clicked=self._activate_import_dialog ) self.recent_combo.currentIndexChanged.connect( lambda idx: self.import_options_button.setEnabled(idx != -1) or self.load_button.setEnabled(idx != -1) ) button_box.addButton( self.import_options_button, QDialogButtonBox.ActionRole ) button_box.setStyleSheet( "button-layout: {:d};".format(QDialogButtonBox.MacLayout) ) self.controlArea.layout().addWidget(button_box) self._restoreState() if self.current_item() is not None: self._invalidate() self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) @Slot(int) def activate_recent(self, index): """ Activate an item from the recent list. """ if 0 <= index < self.import_items_model.rowCount(): item = self.import_items_model.item(index) assert item is not None path = item.data(ImportItem.PathRole) opts = item.data(ImportItem.OptionsRole) if not isinstance(opts, Options): opts = None self.set_selected_file(path, opts) else: self.recent_combo.setCurrentIndex(-1) @Slot() def browse(self): """ Open a file dialog and select a user specified file. """ formats = [ "Text - comma separated (*.csv, *)", "Text - tab separated (*.tsv, *)", "Text - all files (*)" ] dlg = QFileDialog( self, windowTitle="Open Data File", acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile ) dlg.setNameFilters(formats) state = self.dialog_state lastdir = state.get("directory", "") lastfilter = state.get("filter", "") if lastdir and os.path.isdir(lastdir): dlg.setDirectory(lastdir) if lastfilter: dlg.selectNameFilter(lastfilter) status = dlg.exec_() dlg.deleteLater() if status == QFileDialog.Accepted: self.dialog_state["directory"] = dlg.directory().absolutePath() self.dialog_state["filter"] = dlg.selectedNameFilter() selected_filter = dlg.selectedNameFilter() path = dlg.selectedFiles()[0] # pre-flight check; try to determine the nature of the file mtype = _mime_type_for_path(path) if not mtype.inherits("text/plain"): mb = QMessageBox( parent=self, windowTitle="", icon=QMessageBox.Question, text="The '{basename}' may be a binary file.\n" "Are you sure you want to continue?".format( basename=os.path.basename(path)), standardButtons=QMessageBox.Cancel | QMessageBox.Yes ) mb.setWindowModality(Qt.WindowModal) if mb.exec() == QMessageBox.Cancel: return # initialize dialect based on selected extension if selected_filter in formats[:-1]: filter_idx = formats.index(selected_filter) if filter_idx == 0: dialect = csv.excel() elif filter_idx == 1: dialect = csv.excel_tab() else: dialect = csv.excel_tab() header = True else: try: dialect, header = sniff_csv_with_path(path) except Exception: dialect, header = csv.excel(), True options = None # Search for path in history. # If found use the stored params to initialize the import dialog items = self.itemsFromSettings() idx = index_where(items, lambda t: samepath(t[0], path)) if idx is not None: _, options_ = items[idx] if options_ is not None: options = options_ if options is None: if not header: rowspec = [] else: rowspec = [(range(0, 1), RowSpec.Header)] options = Options( encoding="utf-8", dialect=dialect, rowspec=rowspec) dlg = CSVImportDialog( self, windowTitle="Import Options", sizeGripEnabled=True) dlg.setWindowModality(Qt.WindowModal) dlg.setPath(path) dlg.setOptions(options) status = dlg.exec_() dlg.deleteLater() if status == QDialog.Accepted: self.set_selected_file(path, dlg.options()) def current_item(self): # type: () -> Optional[ImportItem] """ Return the current selected item (file) or None if there is no current item. """ idx = self.recent_combo.currentIndex() if idx == -1: return None item = self.recent_combo.model().item(idx) # type: QStandardItem if isinstance(item, ImportItem): return item else: return None def _activate_import_dialog(self): """Activate the Import Options dialog for the current item.""" item = self.current_item() assert item is not None dlg = CSVImportDialog( self, windowTitle="Import Options", sizeGripEnabled=True, ) dlg.setWindowModality(Qt.WindowModal) dlg.setAttribute(Qt.WA_DeleteOnClose) settings = QSettings() qualname = qname(type(self)) settings.beginGroup(qualname) size = settings.value("size", QSize(), type=QSize) # type: QSize if size.isValid(): dlg.resize(size) path = item.data(ImportItem.PathRole) options = item.data(ImportItem.OptionsRole) dlg.setPath(path) # Set path before options so column types can if isinstance(options, Options): dlg.setOptions(options) def update(): newoptions = dlg.options() item.setData(newoptions, ImportItem.OptionsRole) # update the stored item self._add_recent(path, newoptions) if newoptions != options: self._invalidate() dlg.accepted.connect(update) def store_size(): settings.setValue("size", dlg.size()) dlg.finished.connect(store_size) dlg.show() def set_selected_file(self, filename, options=None): """ Set the current selected filename path. """ self._add_recent(filename, options) self._invalidate() #: Saved options for a filename SCHEMA = { "path": str, # Local filesystem path "options": str, # json encoded 'Options' } @classmethod def _local_settings(cls): # type: () -> QSettings """Return a QSettings instance with local persistent settings.""" filename = "{}.ini".format(qname(cls)) fname = os.path.join(settings.widget_settings_dir(), filename) return QSettings(fname, QSettings.IniFormat) def _add_recent(self, filename, options=None): # type: (str, Optional[Options]) -> None """ Add filename to the list of recent files. """ model = self.import_items_model index = index_where( (model.index(i, 0).data(ImportItem.PathRole) for i in range(model.rowCount())), lambda path: isinstance(path, str) and samepath(path, filename) ) if index is not None: item, *_ = model.takeRow(index) else: item = ImportItem.fromPath(filename) model.insertRow(0, item) if options is not None: item.setOptions(options) self.recent_combo.setCurrentIndex(0) # store items to local persistent settings s = self._local_settings() arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA) item = {"path": filename} if options is not None: item["options"] = json.dumps(options.as_dict()) arr = [item for item in arr if item.get("path") != filename] arr.append(item) QSettings_writeArray(s, "recent", arr) # update workflow session items items = self._session_items[:] idx = index_where(items, lambda t: samepath(t[0], filename)) if idx is not None: del items[idx] items.insert(0, (filename, options.as_dict())) self._session_items = items[:OWCSVFileImport.MaxHistorySize] def _invalidate(self): # Invalidate the current output and schedule a new commit call. # (NOTE: The widget enters a blocking state) self.__committimer.start() if self.__watcher is not None: self.__cancel_task() self.setBlocking(True) def commit(self): """ Commit the current state and submit the load task for execution. Note ---- Any existing pending task is canceled. """ self.__committimer.stop() if self.__watcher is not None: self.__cancel_task() self.error() item = self.current_item() if item is None: return path = item.data(ImportItem.PathRole) opts = item.data(ImportItem.OptionsRole) if not isinstance(opts, Options): return task = state = TaskState() state.future = ... state.watcher = qconcurrent.FutureWatcher() state.progressChanged.connect(self.__set_read_progress, Qt.QueuedConnection) def progress_(i, j): task.emitProgressChangedOrCancel(i, j) task.future = self.__executor.submit( clear_stack_on_cancel(load_csv), path, opts, progress_, ) task.watcher.setFuture(task.future) w = task.watcher w.done.connect(self.__handle_result) w.progress = state self.__watcher = w self.__set_running_state() @Slot('qint64', 'qint64') def __set_read_progress(self, read, count): if count > 0: self.progressBarSet(100 * read / count) def __cancel_task(self): # Cancel and dispose of the current task assert self.__watcher is not None w = self.__watcher w.future().cancel() w.progress.cancel = True w.done.disconnect(self.__handle_result) w.progress.progressChanged.disconnect(self.__set_read_progress) w.progress.deleteLater() # wait until completion futures.wait([w.future()]) self.__watcher = None def cancel(self): """ Cancel current pending or executing task. """ if self.__watcher is not None: self.__cancel_task() self.__clear_running_state() self.setStatusMessage("Cancelled") self.summary_text.setText( "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>" ) def __set_running_state(self): self.progressBarInit() self.setBlocking(True) self.setStatusMessage("Running") self.cancel_button.setEnabled(True) self.load_button.setText("Restart") path = self.current_item().path() self.Error.clear() self.summary_text.setText( "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path)) ) def __clear_running_state(self, ): self.progressBarFinished() self.setStatusMessage("") self.setBlocking(False) self.cancel_button.setEnabled(False) self.load_button.setText("Reload") def __set_error_state(self, err): self.Error.clear() if isinstance(err, UnicodeDecodeError): self.Error.encoding_error(exc_info=err) else: self.Error.error(exc_info=err) path = self.current_item().path() basename = os.path.basename(path) if isinstance(err, UnicodeDecodeError): text = ( "<div><i>{basename}</i> was not loaded due to a text encoding " "error. The file might be saved in an unknown or invalid " "encoding, or it might be a binary file.</div>" ).format( basename=escape(basename) ) else: text = ( "<div><i>{basename}</i> was not loaded due to an error:" "<p style='white-space: pre;'>{err}</p>" ).format( basename=escape(basename), err="".join(traceback.format_exception_only(type(err), err)) ) self.summary_text.setText(text) def __clear_error_state(self): self.Error.error.clear() self.summary_text.setText("") def onDeleteWidget(self): """Reimplemented.""" if self.__watcher is not None: self.__cancel_task() self.__executor.shutdown() super().onDeleteWidget() @Slot(object) def __handle_result(self, f): # type: (qconcurrent.Future[pd.DataFrame]) -> None assert f.done() assert f is self.__watcher.future() self.__watcher = None self.__clear_running_state() try: df = f.result() assert isinstance(df, pd.DataFrame) except pd.errors.EmptyDataError: df = pd.DataFrame({}) except Exception as e: # pylint: disable=broad-except self.__set_error_state(e) df = None else: self.__clear_error_state() if df is not None: table = pandas_to_table(df) else: table = None self.send("Data Frame", df) self.send('Data', table) self._update_status_messages(table) def _update_status_messages(self, data): if data is None: return def pluralize(seq): return "s" if len(seq) != 1 else "" summary = ("{n_instances} row{plural_1}, " "{n_features} feature{plural_2}, " "{n_meta} meta{plural_3}").format( n_instances=len(data), plural_1=pluralize(data), n_features=len(data.domain.attributes), plural_2=pluralize(data.domain.attributes), n_meta=len(data.domain.metas), plural_3=pluralize(data.domain.metas)) self.summary_text.setText(summary) def itemsFromSettings(self): # type: () -> List[Tuple[str, Options]] """ Return items from local history. """ s = self._local_settings() items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA) items = [] # type: List[Tuple[str, Options]] for item in items_: path = item.get("path", "") if not path: continue opts_json = item.get("options", "") try: opts = Options.from_dict(json.loads(opts_json)) except (csv.Error, LookupError, TypeError, json.JSONDecodeError): _log.error("Could not reconstruct options for '%s'", path, exc_info=True) pass else: items.append((path, opts)) return items[::-1] def _restoreState(self): # Restore the state. Merge session (workflow) items with the # local history. model = self.import_items_model # local history items = self.itemsFromSettings() # stored session items sitems = [] for p, m in self._session_items: try: item_ = (p, Options.from_dict(m)) except (csv.Error, LookupError) as e: # Is it better to fail then to lose a item slot? _log.error("Failed to restore '%s'", p, exc_info=True) else: sitems.append(item_) items = sitems + items items = unique(items, key=lambda t: pathnormalize(t[0])) curr = self.recent_combo.currentIndex() if curr != -1: currentpath = self.recent_combo.currentData(ImportItem.PathRole) else: currentpath = None for path, options in items: item = ImportItem.fromPath(path) item.setOptions(options) model.appendRow(item) if currentpath is not None: idx = self.recent_combo.findData(currentpath, ImportItem.PathRole) if idx != -1: self.recent_combo.setCurrentIndex(idx)
class Adf4113NcountLatchWidget(QGroupBox): bitmapChanged = pyqtSignal() title = 'AB count latch' def __init__(self, parent=None): super().__init__(parent) self.setCheckable(True) self.setChecked(True) self._slideAcount = SpinSlide(0, 63, 0, '') self._slideBcount = SpinSlide(3, 8191, 1, '') self._comboCpGain = QComboBox() self._containerLayout = QVBoxLayout() self._formLayout = QFormLayout() self._bitLayout = QVBoxLayout() self._latch = Adf4113NcountLatch() self._tableBits = QTableView() self._bitModel = BitModel(rowSize=8, bits=self._latch.bin, labels=[ 'X', 'X', 'G1', 'B13', 'B12', 'B11', 'B10', 'B9', 'B8', 'B7', 'B6', 'B5', 'B4', 'B3', 'B2', 'B1', 'A6', 'A5', 'A4', 'A3', 'A2', 'A1', 'C2', 'C1' ], disabled=[ True, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, True ], parent=self) self._init() def _init(self): self._containerLayout.addLayout(self._formLayout) self._containerLayout.addLayout(self._bitLayout) self._formLayout.addRow('A counter', self._slideAcount) self._formLayout.addRow('B counter', self._slideBcount) self._formLayout.addRow('Charge pump gain', self._comboCpGain) self._bitLayout.addWidget(self._tableBits) self.setLayout(self._containerLayout) self._comboCpGain.setModel( MapModel(self, self._latch.cp_gain_mode_labels, sort=False)) self.setTitle( f'{self.title} (h:{self._latch.hex} b:{self._latch.bin})') self._tableBits.setModel(self._bitModel) self._tableBits.horizontalHeader().setVisible(False) self._tableBits.verticalHeader().setVisible(False) self._tableBits.verticalHeader().setDefaultSectionSize(20) self._tableBits.resizeColumnsToContents() self._tableBits.setSelectionMode(0) self._setupSignals() def _setupSignals(self): self._slideAcount.valueChanged.connect(self.updateBitmap) self._slideBcount.valueChanged.connect(self.updateBitmap) self._comboCpGain.currentIndexChanged.connect(self.updateBitmap) self._bitModel.bitChanged.connect(self.onBitChanged) def updateDisplay(self): self.setTitle( f'{self.title} (h:{self._latch.hex} b:{self._latch.bin})') self._bitModel.update(self._latch.bin) self.bitmapChanged.emit() @pyqtSlot(int) def updateBitmap(self, _): self._latch.a_counter = self._slideAcount.value() self._latch.b_counter = self._slideBcount.value() self._latch.cp_gain = self._comboCpGain.currentData( MapModel.RoleNodeId) self.updateDisplay() @pyqtSlot(int, int) def onBitChanged(self, row, col): self.latch.toggle_nth_bit(row * 8 + 7 - col) self.updateDisplay() @property def latch(self): return self._latch
class Downloader(QDialog): combobox = None grid = None selected_file = None session = None loggedIn = False openbutton = None save_dir = None choose_folder_button = None def __init__(self): super().__init__() self.session = requests.Session() self.session.cookies = http.cookiejar.MozillaCookieJar() self.init_ui() def init_ui(self): self.grid = QGridLayout() self.setLayout(self.grid) self.combobox = QComboBox() download_qualities = [ ("WAV", "?format=wav"), ("MP3 320", "?format=mp3&bitRate=320"), ("MP3 V0", "?format=mp3&quality=0"), ("MP3 V2", "?format=mp3&quality=2"), ("MP3 128", "?format=mp3&bitRate=128"), ("FLAC", "?format=flac") ] for i in range(len(download_qualities)): self.combobox.addItem(download_qualities[i][0], download_qualities[i][1]) self.openbutton = QPushButton("Select file") self.openbutton.clicked.connect(self.show_open_file_dialog) download_button = QPushButton("Download") download_button.clicked.connect(self.download) self.choose_folder_button = QPushButton("Select folder") self.choose_folder_button.clicked.connect(self.show_select_folder_dialog) # ADD WIDGETS self.grid.addWidget(QLabel("Select your quality: "), *(1, 1)) self.grid.addWidget(self.combobox, *(1, 2)) self.grid.addWidget(QLabel("Please select your JSON file: "), *(2, 1)) self.grid.addWidget(self.openbutton, *(2, 2)) self.grid.addWidget(QLabel("Destination folder:"), *(3, 1)) self.grid.addWidget(self.choose_folder_button, *(3, 2)) self.grid.addWidget(QLabel(""), *(4, 1)) self.grid.addWidget(download_button, *(5, 2)) # MOVE TO CENTER OF SCREEN self.move(QDesktopWidget().availableGeometry().center() - self.frameGeometry().center()) self.setWindowTitle('MonstercatConnectDownloader') self.show() def show_open_file_dialog(self): filepicker = QFileDialog.getOpenFileName(self, 'Open file', os.path.expanduser("~"), "JSON file (*.json)") if filepicker[0]: self.selected_file = filepicker[0] self.openbutton.setText("File selected") return True else: return False def show_select_folder_dialog(self): # DIALOG WHERE TO SAVE self.save_dir = QFileDialog.getExistingDirectory(self, "Select folder to download", os.path.expanduser("~")) if not self.save_dir: show_popup("Error", "No folder selected.") return False self.choose_folder_button.setText("Folder selected") return True def show_sign_in_dialog(self): dialog = SignInDialog(self) dialog.exec_() def download(self): # GET FILE if not self.selected_file: show_popup("Error", "Please select a file first.") return False if not self.save_dir: show_popup("Error", "Please select a destination folder first.") return False with open(self.selected_file) as f: album_ids = json.loads(f.read()) # GET SELECTED QUALITY quality = self.combobox.currentData() # LOAD COOKIES IF EXIST cj, successful = load_cookies(COOKIE_FILE) if successful: self.session.cookies = cj self.loggedIn = True show_popup("Logged in", "Automatically logged in.") # GET SESSION if not self.loggedIn: self.show_sign_in_dialog() # CHECK IF LOGIN SUCESSFUL if not self.loggedIn: show_popup("Error", "Login failed.") return length = str(len(album_ids)) bar = QProgressDialog("Downloading songs (1/" + length + ")", "Cancel", 0, int(length)) bar.setWindowTitle("Downloading songs") bar.setValue(0) count = 1 downloadsuccess = True # DOWNLOAD for album_id in album_ids: download_link = DOWNLOAD_BASE + album_id + "/download" + quality success = download_file(download_link, self.save_dir, self.session) if not success: show_popup("Cancelled", "Download was cancelled.") downloadsuccess = False break bar.setValue(count) bar.setLabelText("Downloading songs (" + str(count) + "/" + length + ")") count += 1 if bar.wasCanceled(): show_popup("Cancelled", "Download was cancelled.") downloadsuccess = False break QApplication.processEvents() # break # activate for testing if downloadsuccess: show_popup("Success!", "Download finished!") else: show_popup("Finished.", "Finished with errors. Probably cancelled.")
class Settings_Net(QWidget): def __init__(self, parent: QWidget): super(Settings_Net, self).__init__(parent) self._layout = QVBoxLayout() self.setLayout(self._layout) # construct layout min_width = 150 self.user_agents = dict() self.user_agents["chrome_win7_x64"] = ( "Chrome 41, Windows 7 x64", "Mozilla/5.0 (Windows NT 6.1; WOW64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/41.0.2227.0 Safari/537.36", ) self.user_agents["chrome_linux_64"] = ( "Chrome 41, Linux x86_64", "Mozilla/5.0 (X11; Linux x86_64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/41.0.2227.0 Safari/537.36", ) self.user_agents["chrome_android"] = ( "Chrome 47, Android 4.3 Galaxy-S3", "Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/47.0.2526.83 Mobile Safari/537.36", ) self.user_agents["firefox_win32"] = ( "Firefox 40, Windows 7 32-bit", "Mozilla/5.0 (Windows NT 6.1; rv:40.0) " "Gecko/20100101 Firefox/40.1", ) self.user_agents["firefox_android"] = ( "Firefox, Android 4.3 Galaxy-S3", "Mozilla/5.0 (Android 4.3; Mobile; rv:43.0) " "Gecko/43.0 Firefox/43.0", ) self.user_agents["edge_win10"] = ( "Microsoft Edge, Windows 10 x64", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", ) # server URL self._l_surl = QHBoxLayout() self._l_ua = QHBoxLayout() self._lbl_surl = QLabel(self.tr("Server URL:"), self) self._lbl_surl.setMinimumWidth(min_width) self._le_surl = QLineEdit(self) self._l_surl.addWidget(self._lbl_surl) self._l_surl.addWidget(self._le_surl) self._layout.addLayout(self._l_surl) # emulate browser combo box self._l_eb = QHBoxLayout() self._lbl_eb = QLabel(self.tr("Emulate browser:"), self) self._lbl_eb.setMinimumWidth(min_width) self._cb_eb = QComboBox(self) self._cb_eb.setEditable(False) self._cb_eb.setInsertPolicy(QComboBox.InsertAtBottom) ua_keylist = [i for i in self.user_agents.keys()] ua_keylist.sort() for key_id in ua_keylist: b_tuple = self.user_agents[key_id] display_string = b_tuple[0] self._cb_eb.addItem(display_string, QVariant(str(key_id))) self._cb_eb.addItem(self.tr("<Custom>"), QVariant("custom")) self._l_eb.addWidget(self._lbl_eb) self._l_eb.addWidget(self._cb_eb) self._layout.addLayout(self._l_eb) # custom user-agent string self._lbl_ua = QLabel(self.tr("User-agent string:"), self) self._lbl_ua.setMinimumWidth(min_width) self._le_ua = QLineEdit(self) self._l_ua.addWidget(self._lbl_ua) self._l_ua.addWidget(self._le_ua) self._layout.addLayout(self._l_ua) # proxy settings self._l_proxy = QHBoxLayout() self._lbl_proxy = QLabel(self.tr("Proxy type:"), self) self._lbl_proxy.setMinimumWidth(min_width) self._cb_proxy = QComboBox(self) self._cb_proxy.setEditable(False) self._cb_proxy.addItem(self.tr("No proxy"), QVariant("none")) self._cb_proxy.addItem(self.tr("HTTP proxy"), QVariant("http")) self._cb_proxy.addItem(self.tr("SOCKS5 proxy"), QVariant("socks5")) self._l_proxy.addWidget(self._lbl_proxy) self._l_proxy.addWidget(self._cb_proxy) self._layout.addLayout(self._l_proxy) self._l_proxy_s = QHBoxLayout() self._lbl_proxy_s = QLabel(self.tr("Proxy addr:port:"), self) self._lbl_proxy_s.setMinimumWidth(min_width) self._le_proxy_addr = QLineEdit(self) self._l_proxy_s.addWidget(self._lbl_proxy_s) self._l_proxy_s.addWidget(self._le_proxy_addr) self._layout.addLayout(self._l_proxy_s) # all connections self._cb_eb.currentIndexChanged.connect(self.on_cb_eb_current_index_changed) self._cb_proxy.currentIndexChanged.connect(self.on_cb_proxy_current_index_changed) # finalize self._layout.addStretch() @pyqtSlot(int) def on_cb_eb_current_index_changed(self, index: int): key_id = str(self._cb_eb.currentData(Qt.UserRole)) if key_id == "custom": self._le_ua.setEnabled(True) return self._le_ua.setEnabled(False) if key_id in self.user_agents: b_tuple = self.user_agents[key_id] ua_str = b_tuple[1] self._le_ua.setText(ua_str) @pyqtSlot(int) def on_cb_proxy_current_index_changed(self, index: int): if index == 0: self._le_proxy_addr.setEnabled(False) else: self._le_proxy_addr.setEnabled(True) def ua_select(self, key_id: str): cnt = self._cb_eb.count() for i in range(cnt): item_key_id = str(self._cb_eb.itemData(i, Qt.UserRole)) if item_key_id == key_id: self._cb_eb.setCurrentIndex(i) break def load_from_config(self, cfg: configparser.ConfigParser): # defaults xnova_url = "uni4.xnova.su" user_agent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0" user_agent_id = "custom" proxy = "" if "net" in cfg: xnova_url = cfg["net"]["xnova_url"] user_agent = cfg["net"]["user_agent"] user_agent_id = cfg["net"]["user_agent_id"] proxy = cfg["net"]["proxy"] self._le_surl.setText(xnova_url) self._le_surl.setEnabled(False) # cannot be edited by user, for safety! # deal with user-agent self._le_ua.setText(user_agent) if user_agent_id == "custom": self._le_ua.setEnabled(True) else: self._le_ua.setEnabled(False) self.ua_select(user_agent_id) # deal with proxy if proxy == "": self._le_proxy_addr.setText("") self._cb_proxy.setCurrentIndex(0) self._le_proxy_addr.setEnabled(False) elif proxy.startswith("http://"): self._cb_proxy.setCurrentIndex(1) proxy_addr = proxy[7:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) elif proxy.startswith("socks5://"): self._cb_proxy.setCurrentIndex(2) proxy_addr = proxy[9:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) else: raise ValueError("Invalid proxy setting: " + proxy) def save_to_config(self, cfg: configparser.ConfigParser): # ensure there is a 'net' section if "net" not in cfg: cfg.add_section("net") # skip server url # deal with user-agent user_agent_id = "" user_agent = "" idx = self._cb_eb.currentIndex() if idx >= 0: user_agent_id = str(self._cb_eb.itemData(idx, Qt.UserRole)) cfg["net"]["user_agent_id"] = user_agent_id user_agent = self._le_ua.text().strip() if user_agent != "": cfg["net"]["user_agent"] = user_agent # deal with proxy idx = self._cb_proxy.currentIndex() proxy_addr = self._le_proxy_addr.text().strip() if idx == 0: cfg["net"]["proxy"] = "" elif idx == 1: cfg["net"]["proxy"] = "http://" + proxy_addr elif idx == 2: cfg["net"]["proxy"] = "socks5://" + proxy_addr logger.debug("Saved network config")