def readSettings(self): s = QSettings() s.beginGroup("pitch-menu") self.actionCollection.pitch_relative_assume_first_pitch_absolute.setChecked( s.value("relative-first-pitch-absolute", False, bool)) self.actionCollection.pitch_relative_write_startpitch.setChecked( s.value("relative-write-startpitch", True, bool))
def writeSettings(self): s = QSettings() s.beginGroup("pitch-menu") s.setValue("relative-first-pitch-absolute", self.actionCollection.pitch_relative_assume_first_pitch_absolute.isChecked()) s.setValue("relative-write-startpitch", self.actionCollection.pitch_relative_write_startpitch.isChecked())
def loadSettings(self): s = QSettings() s.beginGroup("helper_applications") self.printCommand.setPath(s.value("printcommand", "", str)) self.printDialogCheck.setChecked(s.value("printcommand/dialog", False, bool)) with qutil.signalsBlocked(self.resolution): self.resolution.setEditText(format(s.value("printcommand/dpi", 300, int)))
def loadSettings(self): output_port = midihub.default_output() input_port = midihub.default_input() s = QSettings() s.beginGroup("midi") self._playerPort.setEditText(s.value("player/output_port", output_port, str)) self._inputPort.setEditText(s.value("midi/input_port", input_port, str))
def restoreSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup('preferences') if settings.contains('host'): self.host = settings.value('host', type=str) if settings.contains('port'): self.port = settings.value('port', type=int) if settings.contains('path_mapping'): self.mapping_str = settings.value('path_mapping') if settings.contains('target_platform'): self.target_platform = settings.value('target_platform') if settings.contains('batch_size'): self.batch_size = settings.value('batch_size', type=int) if settings.contains('cluster_support'): self.cluster_support = settings.value('cluster_support', type=bool) if settings.contains('stylesheet'): self.stylesheet = settings.value('stylesheet', type=str) settings.endGroup()
def saveSettings(self): s = QSettings() s.beginGroup("documentation") langs = ['default', 'C'] + lilydoc.translations s.setValue("language", langs[self.languages.currentIndex()]) s.setValue("fontfamily", self.fontChooser.currentFont().family()) s.setValue("fontsize", self.fontSize.value())
def saveSettings(self): s = QSettings() s.beginGroup('scorewiz/instrumentnames') s.setValue('enable', self.isChecked()) s.setValue('first', ('long', 'short')[self.firstSystem.currentIndex()]) s.setValue('other', ('long', 'short', 'none')[self.otherSystems.currentIndex()]) s.setValue('language', self._langs[self.language.currentIndex()])
def load_settings(self): """Load application-wide settings relating to extensions.""" s = QSettings() s.beginGroup('extension-settings') self._active = s.value('active', True, bool) self._inactive_extensions = s.value('installed/inactive', [], str) self._root_directory = s.value('root-directory', '', str)
def exportColoredHtml(self): doc = self.currentDocument() name, ext = os.path.splitext(os.path.basename(doc.url().path())) if name: if ext.lower() == ".html": name += "_html" name += ".html" dir = os.path.dirname(doc.url().toLocalFile()) if dir: name = os.path.join(dir, name) filename = QFileDialog.getSaveFileName(self, app.caption(_("Export as HTML")), name, "{0} (*.html)".format("HTML Files"))[0] if not filename: return #cancelled s = QSettings() s.beginGroup("source_export") number_lines = s.value("number_lines", False, bool) inline_style = s.value("inline_export", False, bool) wrap_tag = s.value("wrap_tag", "pre", str) wrap_attrib = s.value("wrap_attrib", "id", str) wrap_attrib_name = s.value("wrap_attrib_name", "document", str) import highlight2html html = highlight2html.html_document(doc, inline=inline_style, number_lines=number_lines, wrap_tag=wrap_tag, wrap_attrib=wrap_attrib, wrap_attrib_name=wrap_attrib_name) try: with open(filename, "wb") as f: f.write(html.encode('utf-8')) except IOError as e: msg = _("{message}\n\n{strerror} ({errno})").format( message = _("Could not write to: {url}").format(url=filename), strerror = e.strerror, errno = e.errno) QMessageBox.critical(self, app.caption(_("Error")), msg)
def _run_git_command(self, cmd, args = []): """ run a git command and return its output as a string list. Raise an exception if it returns an error. - cmd is the git command (without 'git') - args is a string or a list of strings """ from PyQt5.QtCore import QSettings s = QSettings() s.beginGroup("helper_applications") git_cmd = s.value("git", "git", str) git_cmd = git_cmd if git_cmd else "git" cmd = [git_cmd, cmd] cmd.extend(args) pr = subprocess.Popen(cmd, cwd = self.rootDir, stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines = True) (out, error) = pr.communicate() if error: raise GitError(error) result = out.split('\n') if result[-1] == '': result.pop() return result
def saveSettings(self): s = QSettings() s.beginGroup("documentstructure") if self.patternList.value() != documentstructure.default_outline_patterns: s.setValue("outline_patterns", self.patternList.value()) else: s.remove("outline_patterns")
def preferred(): """Return the quotes desired by the Frescobaldi user. Always returns a quote set. Only this function depends on Qt and Frescobaldi. """ from PyQt5.QtCore import QSettings import po.setup s = QSettings() s.beginGroup("typographical_quotes") language = s.value("language", "current", str) default = _quotes["C"] if language == "current": language = po.setup.current() elif language == "custom": return QuoteSet( primary = Quotes( left = s.value("primary_left", default.primary.left, str), right = s.value("primary_right", default.primary.right, str), ), secondary = Quotes( left = s.value("secondary_left", default.secondary.left, str), right = s.value("secondary_right", default.secondary.right, str), ) ) return quotes(language) or default
def sessionOpened(self): if self.networkSession is not None: config = self.networkSession.configuration() if config.type() == QNetworkConfiguration.UserChoice: id = self.networkSession.sessionProperty('UserChoiceConfiguration') else: id = config.identifier() settings = QSettings(QSettings.UserScope, 'QtProject') settings.beginGroup('QtNetwork') settings.setValue('DefaultNetworkConfiguration', id) settings.endGroup(); self.tcpServer = QTcpServer(self) if not self.tcpServer.listen(): QMessageBox.critical(self, "Fortune Server", "Unable to start the server: %s." % self.tcpServer.errorString()) self.close() return for ipAddress in QNetworkInterface.allAddresses(): if ipAddress != QHostAddress.LocalHost and ipAddress.toIPv4Address() != 0: break else: ipAddress = QHostAddress(QHostAddress.LocalHost) ipAddress = ipAddress.toString() self.statusLabel.setText("The server is running on\n\nIP: %s\nport %d\n\n" "Run the Fortune Client example now." % (ipAddress, self.tcpServer.serverPort()))
def run_command(cls, cmd, args=[], dir=None): """ run a git command and return its output as a string list. Raise an exception if it returns an error. - cmd is the git command (without 'git') - args is a string or a list of strings If no dir is passed the running dir of Frescobaldi is used as default """ dir = os.path.normpath(os.path.join(sys.path[0], '..')) if dir is None else dir from PyQt5.QtCore import QSettings s = QSettings() s.beginGroup("helper_applications") git_cmd = s.value("git", "git", str) git_cmd = git_cmd if git_cmd else "git" cmd = [git_cmd, cmd] cmd.extend(args) pr = subprocess.Popen(cmd, cwd=dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) (out, error) = pr.communicate() if error: raise GitError(error) result = out.split('\n') if result[-1] == '': result.pop() return result
def closeEvent(self, event): print("Main window close event") # Save layout settings settings = QSettings("PhotoCalendar") settings.beginGroup("MainWindow") curSize = self.size() settings.setValue("Size", QVariant(curSize)) curPos = self.pos() settings.setValue("Position", QVariant(curPos)) #settings.setValue("MainWindow/State", QVariant(self.saveState())) horzSplitterState = self.horzSplitter.saveState() #print("HorzSplitter save", horzSplitterState) settings.setValue("HorzSplitter", QVariant(horzSplitterState)) leftPaneSplitterState = self.leftPane.saveState() settings.setValue("LeftPaneSplitter", QVariant(leftPaneSplitterState)) #print("LeftPaneSplitter save", leftPaneSplitterState) rightPaneSplitterState = self.rightPane.saveState() settings.setValue("RightPaneSplitter", QVariant(rightPaneSplitterState)) #print("RightPaneSplitter save", leftPaneSplitterState) settings.endGroup() # Stop the sub-elements self.calendar.stop() self.clock.stop() self.photos.stop() # Accept the close event event.accept()
def requireScripts(self, urlList): """ Public method to get the sources of all required scripts. @param urlList list of URLs (list of string) @return sources of all required scripts (string) """ requiresDir = QDir(self.requireScriptsDirectory()) if not requiresDir.exists() or len(urlList) == 0: return "" script = "" settings = QSettings( os.path.join(self.requireScriptsDirectory(), "requires.ini"), QSettings.IniFormat) settings.beginGroup("Files") for url in urlList: if settings.contains(url): fileName = settings.value(url) try: f = open(fileName, "r", encoding="utf-8") source = f.read() f.close() except (IOError, OSError): source = "" script += source.strip() + "\n" return script
def saveSettings(self): s = QSettings() s.beginGroup("typographical_quotes") s.setValue("language", self._langs[self.languageCombo.currentIndex()]) s.setValue("primary_left", self.primaryLeft.text()) s.setValue("primary_right", self.primaryRight.text()) s.setValue("secondary_left", self.secondaryLeft.text()) s.setValue("secondary_right", self.secondaryRight.text())
def saveSettings(self): s = QSettings() s.beginGroup("log") s.setValue("fontfamily", self.fontChooser.currentFont().family()) s.setValue("fontsize", self.fontSize.value()) s.setValue("show_on_start", self.showlog.isChecked()) s.setValue("rawview", self.rawview.isChecked()) s.setValue("hide_auto_engrave", self.hideauto.isChecked())
def loadSettings(self): s = QSettings() s.beginGroup("documentstructure") try: patterns = s.value("outline_patterns", documentstructure.default_outline_patterns, str) except TypeError: patterns = [] self.patternList.setValue(patterns)
def loadSettings(self): """Loads the settings from config.""" s = QSettings() s.beginGroup("sidebar") line_numbers = s.value("line_numbers", self._line_numbers, bool) self.setLineNumbersVisible(line_numbers) folding = s.value("folding", self._folding, bool) self.setFoldingVisible(folding)
def writeSettings(self): s = QSettings() s.beginGroup("copy_image") s.setValue("dpi", self.dpiCombo.currentText()) s.setValue("papercolor", self.colorButton.color()) s.setValue("autocrop", self.crop.isChecked()) s.setValue("antialias", self.antialias.isChecked()) s.setValue("scaleup", self.scaleup.isChecked())
def saveSettings(self): s = QSettings() s.beginGroup("hyphenation") paths = self.listedit.value() if paths: s.setValue("paths", paths) else: s.remove("paths")
def save_window_geometry(size, pos, is_full_screen, splitter_sizes): settings = QSettings() settings.beginGroup(group_main_window) settings.setValue(key_size, size) settings.setValue(key_pos, pos) settings.setValue(key_is_full_screen, is_full_screen) settings.setValue(key_splitter_sizes, splitter_sizes) settings.endGroup()
def readSettings(self): s = QSettings() s.beginGroup("copy_image") self.dpiCombo.setEditText(s.value("dpi", "100", str)) self.colorButton.setColor(s.value("papercolor", QColor(Qt.white), QColor)) self.crop.setChecked(s.value("autocrop", False, bool)) self.antialias.setChecked(s.value("antialias", True, bool)) self.scaleup.setChecked(s.value("scaleup", False, bool))
def saveSettings(self): s = QSettings() s.beginGroup("documentation") paths = self.paths.value() if paths: s.setValue("paths", paths) else: s.remove("paths")
def loadSettings(self): """ get users previous settings """ s = QSettings() s.beginGroup('mode_shift') key = s.value('key', "", str) self.keyInput.setText(key) index = s.value('mode', 0, int) self.modeCombo.setCurrentIndex(index) self.readKeyInput()
def slot_saveSettings(self): settings = QSettings() settings.beginGroup("ConnectDialog") settings.setValue("index", self.ui.comboBox.currentIndex()) settings.setValue("bt-number", self.ui.sb_devnumber_bt.value()) settings.setValue("lan-ip", self.ui.le_ip_lan.text()) settings.setValue("lan-port", self.ui.sb_port_lan.value()) settings.setValue("usb-ip", self.ui.le_ip_usb.text())
def save_state(self): assert self.ROLE settings = QSettings() settings.beginGroup(self.ROLE) settings.setValue('geometry', self.saveGeometry()) if isinstance(self, QMainWindow): settings.setValue('state', self.saveState()) self._save_state(settings) settings.endGroup()
def loadSettings(self): settings = QSettings() settings.beginGroup("ConnectDialog") self.ui.comboBox.setCurrentIndex(settings.value("index", 0, type=int)) self.ui.sb_devnumber_bt.setValue(settings.value("bt-number", 1, type=int)) self.ui.le_ip_lan.setText(settings.value("lan-ip", "127.0.0.1", type=str)) self.ui.sb_port_lan.setValue(settings.value("lan-port", 8888, type=int)) self.ui.le_ip_usb.setText(settings.value("usb-ip", "192.168.51.1", type=str))
def loadSettings(self): s = QSettings() s.beginGroup("global_font_dialog") roman = s.value("roman", "Century Schoolbook L", str) self.romanCombo.setCurrentFont(QFont(roman)) sans = s.value("sans", "sans-serif", str) self.sansCombo.setCurrentFont(QFont(sans)) typewriter = s.value("typewriter", "monospace", str) self.typewriterCombo.setCurrentFont(QFont(typewriter))
class App(QMainWindow): def __init__(self): super().__init__(flags=Qt.WindowFlags()) self.settings = QSettings("SavSoft", "Fast Sweep Viewer") # prevent config from being re-written while loading self._loading = True self.central_widget = QWidget(self, flags=Qt.WindowFlags()) self.grid_layout = QGridLayout(self.central_widget) self.grid_layout.setColumnStretch(0, 1) # Frequency box self.group_frequency = QGroupBox(self.central_widget) self.grid_layout_frequency = QGridLayout(self.group_frequency) self.label_frequency_min = QLabel(self.group_frequency) self.label_frequency_max = QLabel(self.group_frequency) self.label_frequency_center = QLabel(self.group_frequency) self.label_frequency_span = QLabel(self.group_frequency) self.spin_frequency_min = QDoubleSpinBox(self.group_frequency) self.spin_frequency_min.setMinimum(MIN_FREQUENCY) self.spin_frequency_min.setMaximum(MAX_FREQUENCY) self.spin_frequency_max = QDoubleSpinBox(self.group_frequency) self.spin_frequency_max.setMinimum(MIN_FREQUENCY) self.spin_frequency_max.setMaximum(MAX_FREQUENCY) self.spin_frequency_center = QDoubleSpinBox(self.group_frequency) self.spin_frequency_center.setMinimum(MIN_FREQUENCY) self.spin_frequency_center.setMaximum(MAX_FREQUENCY) self.spin_frequency_span = QDoubleSpinBox(self.group_frequency) self.spin_frequency_span.setMinimum(0.01) self.spin_frequency_span.setMaximum(MAX_FREQUENCY - MIN_FREQUENCY) self.check_frequency_persists = QCheckBox(self.group_frequency) # Zoom X self.button_zoom_x_out_coarse = QPushButton(self.group_frequency) self.button_zoom_x_out_fine = QPushButton(self.group_frequency) self.button_zoom_x_in_fine = QPushButton(self.group_frequency) self.button_zoom_x_in_coarse = QPushButton(self.group_frequency) # Move X self.button_move_x_left_coarse = QPushButton(self.group_frequency) self.button_move_x_left_fine = QPushButton(self.group_frequency) self.button_move_x_right_fine = QPushButton(self.group_frequency) self.button_move_x_right_coarse = QPushButton(self.group_frequency) # Voltage box self.group_voltage = QGroupBox(self.central_widget) self.grid_layout_voltage = QGridLayout(self.group_voltage) self.label_voltage_min = QLabel(self.group_voltage) self.label_voltage_max = QLabel(self.group_voltage) self.spin_voltage_min = QDoubleSpinBox(self.group_voltage) self.spin_voltage_min.setMinimum(MIN_VOLTAGE) self.spin_voltage_min.setMaximum(MAX_VOLTAGE) self.spin_voltage_max = QDoubleSpinBox(self.group_voltage) self.spin_voltage_max.setMinimum(MIN_VOLTAGE) self.spin_voltage_max.setMaximum(MAX_VOLTAGE) self.check_voltage_persists = QCheckBox(self.group_voltage) # Zoom Y self.button_zoom_y_out_coarse = QPushButton(self.group_voltage) self.button_zoom_y_out_fine = QPushButton(self.group_voltage) self.button_zoom_y_in_fine = QPushButton(self.group_voltage) self.button_zoom_y_in_coarse = QPushButton(self.group_voltage) # Frequency Mark box self.group_mark = QGroupBox(self.central_widget) self.grid_layout_mark = QGridLayout(self.group_mark) self.grid_layout_mark.setColumnStretch(0, 1) self.grid_layout_mark.setColumnStretch(1, 1) self.label_mark_min = QLabel(self.group_mark) self.label_mark_max = QLabel(self.group_mark) self.spin_mark_min = QDoubleSpinBox(self.group_mark) self.spin_mark_min.setMinimum(MIN_FREQUENCY) self.spin_mark_min.setMaximum(MAX_FREQUENCY) self.spin_mark_max = QDoubleSpinBox(self.group_mark) self.spin_mark_max.setMinimum(MIN_FREQUENCY) self.spin_mark_max.setMaximum(MAX_FREQUENCY) self.spin_mark_min.setValue(MIN_FREQUENCY) self.spin_mark_max.setValue(MAX_FREQUENCY) self.button_mark_min_reset = QPushButton(self.group_mark) self.button_mark_max_reset = QPushButton(self.group_mark) for b in (self.button_mark_min_reset, self.button_mark_max_reset): b.setIcon(backend.load_icon('reset')) self.button_zoom_to_selection = QPushButton(self.group_mark) # Find Lines box self.group_find_lines = QGroupBox(self.central_widget) self.grid_layout_find_lines = QGridLayout(self.group_find_lines) self.label_threshold = QLabel(self.group_find_lines) self.spin_threshold = QDoubleSpinBox(self.group_find_lines) self.spin_threshold.setMinimum(1.0) self.spin_threshold.setMaximum(1000.0) self.button_find_lines = QPushButton(self.group_find_lines) self.button_clear_lines = QPushButton(self.group_find_lines) self.button_prev_line = QPushButton(self.group_find_lines) self.button_next_line = QPushButton(self.group_find_lines) # plot self.figure = Figure() self.canvas = FigureCanvas(self.figure) self.canvas.setFocusPolicy(Qt.ClickFocus) self.plot_toolbar = NavigationToolbar( self.canvas, self, parameters_icon=backend.load_icon('configure')) self.legend_figure = Figure(constrained_layout=True, frameon=False) self.legend_canvas = FigureCanvas(self.legend_figure) self.plot = backend.Plot(figure=self.figure, legend_figure=self.legend_figure, toolbar=self.plot_toolbar, settings=self.settings, on_xlim_changed=self.on_xlim_changed, on_ylim_changed=self.on_ylim_changed, on_data_loaded=self.load_data) self.setup_ui() # config self.load_config() # actions self.spin_frequency_min.valueChanged.connect( self.spin_frequency_min_changed) self.spin_frequency_max.valueChanged.connect( self.spin_frequency_max_changed) self.spin_frequency_center.valueChanged.connect( self.spin_frequency_center_changed) self.spin_frequency_span.valueChanged.connect( self.spin_frequency_span_changed) self.button_zoom_x_out_coarse.clicked.connect( lambda: self.button_zoom_x_clicked(1. / 0.5)) self.button_zoom_x_out_fine.clicked.connect( lambda: self.button_zoom_x_clicked(1. / 0.9)) self.button_zoom_x_in_fine.clicked.connect( lambda: self.button_zoom_x_clicked(0.9)) self.button_zoom_x_in_coarse.clicked.connect( lambda: self.button_zoom_x_clicked(0.5)) self.button_move_x_left_coarse.clicked.connect( lambda: self.button_move_x_clicked(-500.)) self.button_move_x_left_fine.clicked.connect( lambda: self.button_move_x_clicked(-50.)) self.button_move_x_right_fine.clicked.connect( lambda: self.button_move_x_clicked(50.)) self.button_move_x_right_coarse.clicked.connect( lambda: self.button_move_x_clicked(500.)) self.check_frequency_persists.toggled.connect( self.check_frequency_persists_toggled) self.spin_voltage_min.valueChanged.connect( self.spin_voltage_min_changed) self.spin_voltage_max.valueChanged.connect( self.spin_voltage_max_changed) self.button_zoom_y_out_coarse.clicked.connect( lambda: self.button_zoom_y_clicked(1. / 0.5)) self.button_zoom_y_out_fine.clicked.connect( lambda: self.button_zoom_y_clicked(1. / 0.9)) self.button_zoom_y_in_fine.clicked.connect( lambda: self.button_zoom_y_clicked(0.9)) self.button_zoom_y_in_coarse.clicked.connect( lambda: self.button_zoom_y_clicked(0.5)) self.check_voltage_persists.toggled.connect( self.check_voltage_persists_toggled) self.spin_mark_min.valueChanged.connect(self.spin_mark_min_changed) self.spin_mark_max.valueChanged.connect(self.spin_mark_max_changed) self.button_mark_min_reset.clicked.connect( self.button_mark_min_reset_clicked) self.button_mark_max_reset.clicked.connect( self.button_mark_max_reset_clicked) self.button_zoom_to_selection.clicked.connect( self.button_zoom_to_selection_clicked) self.spin_threshold.valueChanged.connect( lambda new_value: self.set_config_value('lineSearch', 'threshold', new_value)) self.button_find_lines.clicked.connect( lambda: self.plot.find_lines(self.spin_threshold.value())) self.button_clear_lines.clicked.connect(self.plot.clear_lines) self.button_prev_line.clicked.connect(self.prev_found_line) self.button_next_line.clicked.connect(self.next_found_line) self.mpl_connect_cid = self.canvas.mpl_connect('button_press_event', self.plot_on_click) def setup_ui(self): self.resize(484, 441) self.setWindowIcon(backend.load_icon('sweep')) self.grid_layout_frequency.addWidget(self.label_frequency_min, 1, 0, 1, 2) self.grid_layout_frequency.addWidget(self.label_frequency_max, 0, 0, 1, 2) self.grid_layout_frequency.addWidget(self.label_frequency_center, 2, 0, 1, 2) self.grid_layout_frequency.addWidget(self.label_frequency_span, 3, 0, 1, 2) self.grid_layout_frequency.addWidget(self.spin_frequency_min, 1, 2, 1, 2) self.grid_layout_frequency.addWidget(self.spin_frequency_max, 0, 2, 1, 2) self.grid_layout_frequency.addWidget(self.spin_frequency_center, 2, 2, 1, 2) self.grid_layout_frequency.addWidget(self.spin_frequency_span, 3, 2, 1, 2) self.grid_layout_frequency.addWidget(self.check_frequency_persists, 4, 0, 1, 4) self.grid_layout_voltage.addWidget(self.label_voltage_min, 1, 0, 1, 2) self.grid_layout_voltage.addWidget(self.label_voltage_max, 0, 0, 1, 2) self.grid_layout_voltage.addWidget(self.spin_voltage_min, 1, 2, 1, 2) self.grid_layout_voltage.addWidget(self.spin_voltage_max, 0, 2, 1, 2) self.grid_layout_voltage.addWidget(self.check_voltage_persists, 2, 0, 1, 4) self.grid_layout_frequency.addWidget(self.button_zoom_x_out_coarse, 5, 0) self.grid_layout_frequency.addWidget(self.button_zoom_x_out_fine, 5, 1) self.grid_layout_frequency.addWidget(self.button_zoom_x_in_fine, 5, 2) self.grid_layout_frequency.addWidget(self.button_zoom_x_in_coarse, 5, 3) self.grid_layout_frequency.addWidget(self.button_move_x_left_coarse, 6, 0) self.grid_layout_frequency.addWidget(self.button_move_x_left_fine, 6, 1) self.grid_layout_frequency.addWidget(self.button_move_x_right_fine, 6, 2) self.grid_layout_frequency.addWidget(self.button_move_x_right_coarse, 6, 3) self.grid_layout_voltage.addWidget(self.button_zoom_y_out_coarse, 3, 0) self.grid_layout_voltage.addWidget(self.button_zoom_y_out_fine, 3, 1) self.grid_layout_voltage.addWidget(self.button_zoom_y_in_fine, 3, 2) self.grid_layout_voltage.addWidget(self.button_zoom_y_in_coarse, 3, 3) self.grid_layout_mark.addWidget(self.label_mark_min, 1, 0) self.grid_layout_mark.addWidget(self.label_mark_max, 0, 0) self.grid_layout_mark.addWidget(self.spin_mark_min, 1, 1) self.grid_layout_mark.addWidget(self.spin_mark_max, 0, 1) self.grid_layout_mark.addWidget(self.button_mark_min_reset, 1, 2) self.grid_layout_mark.addWidget(self.button_mark_max_reset, 0, 2) self.grid_layout_mark.addWidget(self.button_zoom_to_selection, 2, 0, 1, 3) self.grid_layout_find_lines.addWidget(self.label_threshold, 0, 0) self.grid_layout_find_lines.addWidget(self.spin_threshold, 0, 1) self.grid_layout_find_lines.addWidget(self.button_find_lines, 1, 0, 1, 2) self.grid_layout_find_lines.addWidget(self.button_clear_lines, 2, 0, 1, 2) self.grid_layout_find_lines.addWidget(self.button_prev_line, 3, 0) self.grid_layout_find_lines.addWidget(self.button_next_line, 3, 1) _value_label_interaction_flags = (Qt.LinksAccessibleByKeyboard | Qt.LinksAccessibleByMouse | Qt.TextBrowserInteraction | Qt.TextSelectableByKeyboard | Qt.TextSelectableByMouse) self.grid_layout.addWidget(self.group_frequency, 2, 1) self.grid_layout.addWidget(self.group_voltage, 3, 1) self.grid_layout.addWidget(self.group_mark, 4, 1) self.grid_layout.addWidget(self.group_find_lines, 5, 1) self.grid_layout.addWidget(self.plot_toolbar, 0, 0, 1, 2) self.grid_layout.addWidget(self.canvas, 1, 0, 5, 1) self.grid_layout.addWidget(self.legend_canvas, 1, 1) self.setCentralWidget(self.central_widget) self.translate_ui() self.adjustSize() def translate_ui(self): _translate = QCoreApplication.translate self.setWindowTitle(_translate('main window', 'Fast Sweep Viewer')) self.plot_toolbar.parameters_title = _translate( 'plot config window title', 'Figure options') suffix_mhz = ' ' + _translate('unit', 'MHz') suffix_mv = ' ' + _translate('unit', 'mV') self.group_frequency.setTitle(_translate('main window', 'Frequency')) self.label_frequency_min.setText( _translate('main window', 'Minimum') + ':') self.label_frequency_max.setText( _translate('main window', 'Maximum') + ':') self.label_frequency_center.setText( _translate('main window', 'Center') + ':') self.label_frequency_span.setText( _translate('main window', 'Span') + ':') self.check_frequency_persists.setText( _translate('main window', 'Keep frequency range')) self.button_zoom_x_out_coarse.setText(_translate( 'main window', '−50%')) self.button_zoom_x_out_fine.setText(_translate('main window', '−10%')) self.button_zoom_x_in_fine.setText(_translate('main window', '+10%')) self.button_zoom_x_in_coarse.setText(_translate('main window', '+50%')) self.button_move_x_left_coarse.setText( _translate('main window', '−500') + suffix_mhz) self.button_move_x_left_fine.setText( _translate('main window', '−50') + suffix_mhz) self.button_move_x_right_fine.setText( _translate('main window', '+50') + suffix_mhz) self.button_move_x_right_coarse.setText( _translate('main window', '+500') + suffix_mhz) self.group_voltage.setTitle(_translate('main window', 'Voltage')) self.label_voltage_min.setText( _translate('main window', 'Minimum') + ':') self.label_voltage_max.setText( _translate('main window', 'Maximum') + ':') self.check_voltage_persists.setText( _translate('main window', 'Keep voltage range')) self.button_zoom_y_out_coarse.setText(_translate( 'main window', '−50%')) self.button_zoom_y_out_fine.setText(_translate('main window', '−10%')) self.button_zoom_y_in_fine.setText(_translate('main window', '+10%')) self.button_zoom_y_in_coarse.setText(_translate('main window', '+50%')) self.group_mark.setTitle(_translate('main window', 'Selection')) self.label_mark_min.setText(_translate('main window', 'Minimum') + ':') self.label_mark_max.setText(_translate('main window', 'Maximum') + ':') if self.button_mark_min_reset.icon().isNull(): self.button_mark_min_reset.setText( _translate('main window', 'Reset')) if self.button_mark_max_reset.icon().isNull(): self.button_mark_max_reset.setText( _translate('main window', 'Reset')) self.button_zoom_to_selection.setText( _translate('main window', 'Zoom to Selection')) self.group_find_lines.setTitle(_translate('main window', 'Find Lines')) self.group_find_lines.setToolTip( _translate('main window', 'Try to detect lines automatically')) self.label_threshold.setText( _translate('main window', 'Search threshold') + ':') self.button_find_lines.setText(_translate('main window', 'Find Lines')) self.button_clear_lines.setText( _translate('main window', 'Clear Lines')) self.button_prev_line.setText( _translate('main window', 'Previous Line')) self.button_next_line.setText(_translate('main window', 'Next Line')) self.spin_frequency_min.setSuffix(suffix_mhz) self.spin_frequency_max.setSuffix(suffix_mhz) self.spin_frequency_center.setSuffix(suffix_mhz) self.spin_frequency_span.setSuffix(suffix_mhz) self.spin_voltage_min.setSuffix(suffix_mv) self.spin_voltage_max.setSuffix(suffix_mv) self.spin_mark_min.setSuffix(suffix_mhz) self.spin_mark_max.setSuffix(suffix_mhz) def closeEvent(self, event): """ senseless joke in the loop """ _translate = QCoreApplication.translate close_code = QMessageBox.No while close_code == QMessageBox.No: close = QMessageBox() close.setText(_translate('main window', 'Are you sure?')) close.setIcon(QMessageBox.Question) close.setWindowIcon(self.windowIcon()) close.setWindowTitle(self.windowTitle()) close.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) close_code = close.exec() if close_code == QMessageBox.Yes: self.settings.setValue('windowGeometry', self.saveGeometry()) self.settings.setValue('windowState', self.saveState()) self.settings.sync() event.accept() elif close_code == QMessageBox.Cancel: event.ignore() return def load_config(self): self._loading = True # common settings if self.settings.contains('windowGeometry'): self.restoreGeometry(self.settings.value('windowGeometry', '')) else: window_frame = self.frameGeometry() desktop_center = QDesktopWidget().availableGeometry().center() window_frame.moveCenter(desktop_center) self.move(window_frame.topLeft()) _v = self.settings.value('windowState', '') if isinstance(_v, str): self.restoreState(_v.encode()) else: self.restoreState(_v) min_freq = self.get_config_value('frequency', 'lower', self.spin_frequency_min.minimum(), float) max_freq = self.get_config_value('frequency', 'upper', self.spin_frequency_min.maximum(), float) self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_span.setValue(max_freq - min_freq) self.spin_frequency_center.setValue(0.5 * (max_freq + min_freq)) self.plot.set_frequency_range(lower_value=min_freq, upper_value=max_freq) self.check_frequency_persists.setChecked( self.get_config_value('frequency', 'persists', False, bool)) min_voltage = self.get_config_value('voltage', 'lower', self.spin_voltage_min.minimum(), float) max_voltage = self.get_config_value('voltage', 'upper', self.spin_voltage_min.maximum(), float) self.spin_voltage_min.setValue(min_voltage) self.spin_voltage_max.setValue(max_voltage) self.spin_voltage_min.setMaximum(max_voltage) self.spin_voltage_max.setMinimum(min_voltage) self.plot.set_voltage_range(lower_value=min_voltage, upper_value=max_voltage) self.check_voltage_persists.setChecked( self.get_config_value('voltage', 'persists', False, bool)) self.spin_threshold.setValue( self.get_config_value('lineSearch', 'threshold', 200.0, float)) self._loading = False return def get_config_value(self, section, key, default, _type): if section not in self.settings.childGroups(): return default self.settings.beginGroup(section) # print(section, key) try: v = self.settings.value(key, default, _type) except TypeError: v = default self.settings.endGroup() return v def set_config_value(self, section, key, value): if self._loading: return self.settings.beginGroup(section) # print(section, key, value, type(value)) self.settings.setValue(key, value) self.settings.endGroup() def load_data(self, limits): if self._loading: return if limits is not None: min_freq, max_freq, min_voltage, max_voltage = limits self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.set_config_value('voltage', 'lower', min_voltage) self.set_config_value('voltage', 'upper', max_voltage) self._loading = True if not self.check_frequency_persists.isChecked(): self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_span.setValue(max_freq - min_freq) self.spin_frequency_center.setValue(0.5 * (max_freq + min_freq)) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) else: self.spin_frequency_min.setMaximum( max(max_freq, self.spin_frequency_min.value())) self.spin_frequency_max.setMinimum( min(min_freq, self.spin_frequency_max.value())) if not self.check_voltage_persists.isChecked(): self.spin_voltage_min.setValue(min_voltage) self.spin_voltage_max.setValue(max_voltage) self.spin_voltage_min.setMaximum(max_voltage) self.spin_voltage_max.setMinimum(min_voltage) else: self.spin_voltage_min.setMaximum( max(max_voltage, self.spin_voltage_min.value())) self.spin_voltage_max.setMinimum( min(min_voltage, self.spin_voltage_max.value())) self.spin_mark_min.setMinimum( min(min_freq, self.spin_mark_min.minimum())) self.spin_mark_max.setMaximum( max(max_freq, self.spin_mark_max.maximum())) self._loading = False self.plot.set_frequency_range( lower_value=self.spin_frequency_min.value(), upper_value=self.spin_frequency_max.value()) self.plot.set_voltage_range( lower_value=self.spin_voltage_min.value(), upper_value=self.spin_voltage_max.value()) self.figure.tight_layout() def spin_frequency_min_changed(self, new_value): if self._loading: return self.set_config_value('frequency', 'lower', new_value) self._loading = True self.spin_frequency_max.setMinimum(new_value) self.spin_frequency_center.setValue( 0.5 * (new_value + self.spin_frequency_max.value())) self.spin_frequency_span.setValue(self.spin_frequency_max.value() - new_value) self.plot.set_frequency_range(lower_value=new_value) self._loading = False def spin_frequency_max_changed(self, new_value): if self._loading: return self.set_config_value('frequency', 'upper', new_value) self._loading = True self.spin_frequency_min.setMaximum(new_value) self.spin_frequency_center.setValue( 0.5 * (self.spin_frequency_min.value() + new_value)) self.spin_frequency_span.setValue(new_value - self.spin_frequency_min.value()) self.plot.set_frequency_range(upper_value=new_value) self._loading = False def spin_frequency_center_changed(self, new_value): if self._loading: return freq_span = self.spin_frequency_span.value() min_freq = new_value - 0.5 * freq_span max_freq = new_value + 0.5 * freq_span self._loading = True self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.plot.set_frequency_range(upper_value=max_freq, lower_value=min_freq) self._loading = False def spin_frequency_span_changed(self, new_value): if self._loading: return freq_center = self.spin_frequency_center.value() min_freq = freq_center - 0.5 * new_value max_freq = freq_center + 0.5 * new_value self._loading = True self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.plot.set_frequency_range(upper_value=max_freq, lower_value=min_freq) self._loading = False def button_zoom_x_clicked(self, factor): if self._loading: return freq_span = self.spin_frequency_span.value() * factor freq_center = self.spin_frequency_center.value() min_freq = freq_center - 0.5 * freq_span max_freq = freq_center + 0.5 * freq_span self._loading = True self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_span.setValue(freq_span) self.plot.set_frequency_range(upper_value=max_freq, lower_value=min_freq) self._loading = False def button_move_x_clicked(self, shift): if self._loading: return freq_span = self.spin_frequency_span.value() freq_center = self.spin_frequency_center.value() + shift min_freq = freq_center - 0.5 * freq_span max_freq = freq_center + 0.5 * freq_span self._loading = True self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_center.setValue(freq_center) self.plot.set_frequency_range(upper_value=max_freq, lower_value=min_freq) self._loading = False def check_frequency_persists_toggled(self, new_value): if self._loading: return self.set_config_value('frequency', 'persists', new_value) def spin_voltage_min_changed(self, new_value): if self._loading: return self.set_config_value('voltage', 'lower', new_value) self._loading = True self.spin_voltage_max.setMinimum(new_value) self.plot.set_voltage_range(lower_value=new_value) self._loading = False def spin_voltage_max_changed(self, new_value): if self._loading: return self.set_config_value('voltage', 'upper', new_value) self._loading = True self.spin_voltage_min.setMaximum(new_value) self.plot.set_voltage_range(upper_value=new_value) self._loading = False def button_zoom_y_clicked(self, factor): if self._loading: return min_voltage = self.spin_voltage_min.value() max_voltage = self.spin_voltage_max.value() voltage_span = abs(max_voltage - min_voltage) * factor voltage_center = (max_voltage + min_voltage) * 0.5 min_voltage = voltage_center - 0.5 * voltage_span max_voltage = voltage_center + 0.5 * voltage_span self._loading = True self.set_config_value('voltage', 'lower', min_voltage) self.set_config_value('voltage', 'upper', max_voltage) self.spin_voltage_min.setMaximum(max_voltage) self.spin_voltage_max.setMinimum(min_voltage) self.spin_voltage_min.setValue(min_voltage) self.spin_voltage_max.setValue(max_voltage) self.plot.set_voltage_range(upper_value=max_voltage, lower_value=min_voltage) self._loading = False def check_voltage_persists_toggled(self, new_value): if self._loading: return self.set_config_value('voltage', 'persists', new_value) def spin_mark_min_changed(self, new_value): if self._loading: return self._loading = True self.spin_mark_max.setMinimum(new_value) self.plot.set_mark(lower_value=new_value, upper_value=self.spin_mark_max.value()) self._loading = False def spin_mark_max_changed(self, new_value): if self._loading: return self._loading = True self.spin_mark_min.setMaximum(new_value) self.plot.set_mark(lower_value=self.spin_mark_min.value(), upper_value=new_value) self._loading = False def button_mark_min_reset_clicked(self): self.spin_mark_min.setValue(self.spin_mark_min.minimum()) def button_mark_max_reset_clicked(self): self.spin_mark_max.setValue(self.spin_mark_max.maximum()) def button_zoom_to_selection_clicked(self): self.spin_frequency_min.setValue(self.spin_mark_min.value()) self.spin_frequency_max.setValue(self.spin_mark_max.value()) def prev_found_line(self): self.spin_frequency_center.setValue( self.plot.prev_found_line(self.spin_frequency_center.value())) def next_found_line(self): self.spin_frequency_center.setValue( self.plot.next_found_line(self.spin_frequency_center.value())) def plot_on_click(self, event): if self._loading: return if event.inaxes is not None: if event.dblclick \ and not self.plot.mark_mode \ and not self.plot.trace_mode \ and not self.plot.trace_multiple_mode: min_freq, max_freq, min_voltage, max_voltage = self.plot.on_double_click( event) self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self.set_config_value('voltage', 'lower', min_voltage) self.set_config_value('voltage', 'upper', max_voltage) self._loading = True self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_frequency_span.setValue(max_freq - min_freq) self.spin_frequency_center.setValue(0.5 * (max_freq + min_freq)) self.spin_voltage_min.setValue(min_voltage) self.spin_voltage_max.setValue(max_voltage) self.spin_voltage_min.setMaximum(max_voltage) self.spin_voltage_max.setMinimum(min_voltage) self._loading = False elif self.plot.mark_mode: if self.plot.mode: self.plot.actions_off() else: if event.button == 1: self.spin_mark_min.setValue(event.xdata) else: self.spin_mark_max.setValue(event.xdata) def open_file_dialog(self, _filter=''): directory = self.get_config_value('open', 'location', '', str) # native dialog misbehaves when running inside snap but Qt dialog is tortoise-like in NT options = QFileDialog.DontUseNativeDialog if os.name != 'nt' else QFileDialog.DontUseSheet filename, _filter = QFileDialog.getOpenFileName(filter=_filter, directory=directory, options=options) self.set_config_value('open', 'location', os.path.split(filename)[0]) return filename, _filter def on_xlim_changed(self, xlim): if not hasattr(self, 'plot'): return min_freq, max_freq = xlim self.set_config_value('frequency', 'lower', min_freq) self.set_config_value('frequency', 'upper', max_freq) self._loading = True self.spin_frequency_min.setValue(min_freq) self.spin_frequency_max.setValue(max_freq) self.spin_frequency_span.setValue(max_freq - min_freq) self.spin_frequency_center.setValue(0.5 * (max_freq + min_freq)) self.spin_frequency_min.setMaximum(max_freq) self.spin_frequency_max.setMinimum(min_freq) self.spin_mark_min.setMinimum( min(min_freq, self.spin_mark_min.minimum())) self.spin_mark_max.setMaximum( max(max_freq, self.spin_mark_max.maximum())) self._loading = False self.plot.set_frequency_range( lower_value=self.spin_frequency_min.value(), upper_value=self.spin_frequency_max.value()) def on_ylim_changed(self, ylim): if not hasattr(self, 'plot'): return min_voltage, max_voltage = ylim self.set_config_value('voltage', 'lower', min_voltage) self.set_config_value('voltage', 'upper', max_voltage) self._loading = True self.spin_voltage_min.setValue(min_voltage) self.spin_voltage_max.setValue(max_voltage) self.spin_voltage_min.setMaximum(max_voltage) self.spin_voltage_max.setMinimum(min_voltage) self._loading = False self.plot.set_voltage_range(lower_value=self.spin_voltage_min.value(), upper_value=self.spin_voltage_max.value())
class TasmotaDevicesModel(QAbstractTableModel): def __init__(self, tasmota_env): super().__init__() self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat) self.devices = QSettings("{}/TDM/devices.cfg".format(QDir.homePath()), QSettings.IniFormat) self.tasmota_env = tasmota_env self.columns = [] self.devices_short_version = self.settings.value("devices_short_version", True, bool) for d in self.tasmota_env.devices: d.property_changed = self.notify_change d.module_changed = self.module_change def setupColumns(self, columns): self.beginResetModel() self.columns = columns self.endResetModel() def deviceAtRow(self, row): if len(self.tasmota_env.devices) > 0: return self.tasmota_env.devices[row] return None def notify_change(self, d, key): row = self.tasmota_env.devices.index(d) if key.startswith("POWER") and "Power" in self.columns: power_idx = self.columns.index("Power") idx = self.index(row, power_idx) self.dataChanged.emit(idx, idx) elif key in ("RSSI", "LWT", "DeviceName", 'FriendlyName1'): idx = self.index(row, self.columns.index("Device")) self.dataChanged.emit(idx, idx) elif key in self.columns: col = self.columns.index(key) idx = self.index(row, col) self.dataChanged.emit(idx, idx) def module_change(self, d): self.notify_change(d, "Module") def columnCount(self, parent=None): return len(self.columns) def rowCount(self, parent=None): return len(self.tasmota_env.devices) def flags(self, idx): return Qt.ItemIsSelectable | Qt.ItemIsEnabled def headerData(self, col, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.columns[col] def data(self, idx, role=Qt.DisplayRole): if idx.isValid(): row = idx.row() col = idx.column() col_name = self.columns[col] d = self.tasmota_env.devices[row] if role in [Qt.DisplayRole, Qt.EditRole]: val = d.p.get(col_name, "") if col_name == "Device": val = d.name elif col_name == "Module": if val == 0: return d.p['Template'].get('NAME', "Fetching template name...") else: return d.module() elif col_name == "Version" and val: if self.devices_short_version and "(" in val: return val[0:val.index("(")] return val.replace("(", " (") elif col_name in ("Uptime", "Downtime") and val: val = str(val) if val.startswith("0T"): val = val.replace('0T', '') val = val.replace('T', 'd ') elif col_name == "Core" and val: return val.replace('_', '.') elif col_name == "Time" and val: return val.replace('T', ' ') elif col_name == "Power": return d.power() elif col_name == "Color": return d.color() elif col_name == "CommandTopic": return d.cmnd_topic() elif col_name == "StatTopic": return d.stat_topic() elif col_name == "TeleTopic": return d.tele_topic() elif col_name == "FallbackTopic": return "cmnd/{}_fb/".format(d.p.get('MqttClient')) elif col_name == "BSSId": alias = self.settings.value("BSSId/{}".format(val)) if alias: return alias elif col_name == "RSSI": val = int(d.p.get("RSSI", 0)) return val return val elif role == LWTRole: val = d.p.get('LWT', 'Offline') return val elif role == RestartReasonRole: val = d.p.get('RestartReason') return val elif role == RSSIRole: val = int(d.p.get('RSSI', 0)) return val elif role == FirmwareRole: val = d.p.get('Version', "") return val elif role == Qt.TextAlignmentRole: # Left-aligned columns if col_name in ("Device", "Module", "RestartReason", "OtaUrl", "Hostname") or col_name.endswith("Topic"): return Qt.AlignLeft | Qt.AlignVCenter | Qt.TextWordWrap # Right-aligned columns elif col_name in ("Uptime"): return Qt.AlignRight | Qt.AlignVCenter else: return Qt.AlignCenter elif role == Qt.DecorationRole and col_name == "Device": if d.p['LWT'] == "Online": rssi = int(d.p.get("RSSI", 0)) if rssi > 0 and rssi < 50: return QIcon(":/status_low.png") elif rssi < 75: return QIcon(":/status_medium.png") elif rssi >= 75: return QIcon(":/status_high.png") return QIcon(":/status_offline.png") elif role == Qt.InitialSortOrderRole: if col_name in ("Uptime", "Downtime"): val = d.p.get(col_name, "") if val: d, hms = val.split("T") h, m, s = hms.split(":") return int(s) + int(m) * 60 + int(h) * 3600 + int(d) * 86400 else: return idx.data() elif role == Qt.ToolTipRole: if col_name == "Version": val = d.p.get('Version') if val: return val[val.index("(")+1:val.index(")")] return "" elif col_name == "BSSId": return d.p.get('BSSId') elif col_name == "Device": fns = [d.name] for i in range(2, 5): fn = d.p.get("FriendlyName{}".format(i)) if fn: fns.append(fn) return "\n".join(fns) def addDevice(self, device): self.beginInsertRows(QModelIndex(), 0, 0) device.property_changed = self.notify_change device.module_changed = self.module_change self.endInsertRows() def removeRows(self, pos, rows, parent=QModelIndex()): if pos + rows <= self.rowCount(): self.beginRemoveRows(parent, pos, pos + rows - 1) device = self.deviceAtRow(pos) self.tasmota_env.devices.pop(self.tasmota_env.devices.index(device)) topic = device.p['Topic'] self.settings.beginGroup("Devices") if topic in self.settings.childGroups(): self.settings.remove(topic) self.settings.endGroup() self.endRemoveRows() return True return False def deleteDevice(self, idx): row = idx.row() mac = self.deviceAtRow(row).p['Mac'].replace(":", "-") self.devices.remove(mac) self.devices.sync() self.removeRows(row, 1) def columnIndex(self, column): return self.columns.index(column)
def saveSettings(self): """Saves the settings to config.""" s = QSettings() s.beginGroup("sidebar") s.setValue("line_numbers", self.lineNumbersVisible()) s.setValue("folding", self.foldingVisible())
def updateSettings(self): from PyQt5.QtCore import QSettings s = QSettings() s.beginGroup("editor_highlighting") self._match_duration = s.value("match", 1, int) * 1000
class DocumentWindow(QMainWindow, ui_mainwindow.Ui_MainWindow): """:class`DocumentWindow` subclasses :class`QMainWindow` and :class`Ui_MainWindow`. It performs some initialization operations that must be done in code rather than using Qt Creator. Attributes: controller: DocumentController """ def __init__(self, doc_ctrlr: DocCtrlT, parent=None): super(DocumentWindow, self).__init__(parent) self.controller = doc_ctrlr doc = doc_ctrlr.document() self.setupUi(self) self.settings = QSettings("cadnano.org", "cadnano2.5") # Appearance pref if not app().prefs.show_icon_labels: self.main_toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) # Outliner & PropertyEditor setup self.outliner_widget.configure(window=self, document=doc) self.property_widget.configure(window=self, document=doc) self.property_buttonbox.setVisible(False) self.tool_managers = None # initialize self.views = {} self.views[ViewSendEnum.SLICE] = self._initSliceview(doc) self.views[ViewSendEnum.GRID] = self._initGridview(doc) self.views[ViewSendEnum.PATH] = self._initPathview(doc) self._initPathviewToolbar() self._initEditMenu() self.path_dock_widget.setTitleBarWidget(QWidget()) self.grid_dock_widget.setTitleBarWidget(QWidget()) self.slice_dock_widget.setTitleBarWidget(QWidget()) self.inspector_dock_widget.setTitleBarWidget(QWidget()) self.setCentralWidget(None) if app().prefs.orthoview_style_idx == OrthoViewEnum.SLICE: self.splitDockWidget(self.slice_dock_widget, self.path_dock_widget, Qt.Horizontal) elif app().prefs.orthoview_style_idx == OrthoViewEnum.GRID: self.splitDockWidget(self.grid_dock_widget, self.path_dock_widget, Qt.Horizontal) self._restoreGeometryandState() self._finishInit() doc.setViewNames(['slice', 'path', 'inspector']) # end def def document(self) -> DocT: return self.controller.document() def destroyWin(self): '''Save window state and destroy the tool managers. Also destroy :class`DocumentController` object ''' self.settings.beginGroup("MainWindow") # Saves the current state of this mainwindow's toolbars and dockwidgets self.settings.setValue("windowState", self.saveState()) self.settings.endGroup() for mgr in self.tool_managers: mgr.destroyItem() self.controller = None ### ACCESSORS ### def undoStack(self) -> UndoStack: return self.controller.undoStack() def activateSelection(self, is_active: bool): self.path_graphics_view.activateSelection(is_active) self.slice_graphics_view.activateSelection(is_active) self.grid_graphics_view.activateSelection(is_active) ### EVENT HANDLERS ### def focusInEvent(self): """Handle an OS focus change into cadnano.""" app().undoGroup.setActiveStack(self.controller.undoStack()) def moveEvent(self, event: QMoveEvent): """Handle the moving of the cadnano window itself. Reimplemented to save state on move. """ self.settings.beginGroup("MainWindow") self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("pos", self.pos()) self.settings.endGroup() def resizeEvent(self, event: QResizeEvent): """Handle the resizing of the cadnano window itself. Reimplemented to save state on resize. """ self.settings.beginGroup("MainWindow") self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("size", self.size()) self.settings.endGroup() QWidget.resizeEvent(self, event) def changeEvent(self, event: QEvent): QWidget.changeEvent(self, event) # end def ### DRAWING RELATED ### ### PRIVATE HELPER METHODS ### def _restoreGeometryandState(self): settings = self.settings settings.beginGroup("MainWindow") geometry = settings.value("geometry") if geometry is not None: result = self.restoreGeometry(geometry) if result is False: print("MainWindow.restoreGeometry() failed.") else: print("Setting default MainWindow size: 1100x800") self.resize(settings.value("size", QSize(1100, 800))) self.move(settings.value("pos", QPoint(200, 200))) self.inspector_dock_widget.close() self.action_inspector.setChecked(False) # Restore the current state of this mainwindow's toolbars and dockwidgets window_state = settings.value("windowState") if window_state is not None: result = self.restoreState(window_state) if result is False: print("MainWindow.restoreState() failed.") settings.endGroup() # end def def destroyView(self, view_type: EnumType): ''' Args: view_type: the name of the view Raises: ValueError for :obj:`view_type` not existing ''' cnview = self.views.get(view_type) if cnview is not None: root_item = cnview.rootItem() root_item.destroyViewItems() else: raise ValueError("view_type: %s does not exist" % (view_type)) def rebuildView(self, view_type: EnumType): '''Rebuild views which match view_type ORed argument. Only allows SLICE or GRID view to be active. Not both at the same time Args: view_type: one or more O ''' doc = self.document() # turn OFF all but the views we care about doc.changeViewSignaling(view_type) delta = 0 if view_type & ViewSendEnum.SLICE: delta = ViewSendEnum.GRID self.destroyView(delta) elif view_type & ViewSendEnum.GRID: delta = ViewSendEnum.GRID self.destroyView(delta) for part in doc.getParts(): reEmitPart(part) # turn ON all but the views we care about doc.changeViewSignaling(ViewSendEnum.ALL - delta) # end def def _initGridview(self, doc: DocT) -> GraphicsViewT: """Initializes Grid View. Args: doc: The :class:`Document` corresponding to the design Returns: :class:`CustomQGraphicsView` """ grid_scene = QGraphicsScene(parent=self.grid_graphics_view) grid_root = GridRootItem(rect=grid_scene.sceneRect(), parent=None, window=self, document=doc) grid_scene.addItem(grid_root) grid_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert grid_root.scene() == grid_scene ggv = self.grid_graphics_view ggv.setScene(grid_scene) ggv.scene_root_item = grid_root ggv.setName("GridView") self.grid_tool_manager = GridToolManager(self, grid_root) return ggv # end def def _initPathview(self, doc: DocT) -> GraphicsViewT: """Initializes Path View. Args: doc: The :class:`Document` corresponding to the design Returns: :class:`CustomQGraphicsView` """ path_scene = QGraphicsScene(parent=self.path_graphics_view) path_root = PathRootItem(rect=path_scene.sceneRect(), parent=None, window=self, document=doc) path_scene.addItem(path_root) path_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert path_root.scene() == path_scene pgv = self.path_graphics_view pgv.setScene(path_scene) pgv.scene_root_item = path_root pgv.setScaleFitFactor(0.7) pgv.setName("PathView") return pgv # end def def _initPathviewToolbar(self): """Initializes Path View Toolbar. """ self.path_color_panel = ColorPanel() self.path_graphics_view.toolbar = self.path_color_panel # HACK for customqgraphicsview path_view = self.views[ViewSendEnum.PATH] path_scene = path_view.cnScene() path_scene.addItem(self.path_color_panel) self.path_tool_manager = PathToolManager(self, path_view.rootItem()) self.slice_tool_manager.path_tool_manager = self.path_tool_manager self.path_tool_manager.slice_tool_manager = self.slice_tool_manager self.grid_tool_manager.path_tool_manager = self.path_tool_manager self.path_tool_manager.grid_tool_manager = self.grid_tool_manager self.tool_managers = (self.path_tool_manager, self.slice_tool_manager, self.grid_tool_manager) self.insertToolBarBreak(self.main_toolbar) self.path_graphics_view.setupGL() self.slice_graphics_view.setupGL() self.grid_graphics_view.setupGL() # end def def _initSliceview(self, doc: DocT) -> GraphicsViewT: """Initializes Slice View. Args: doc: The :class:`Document` corresponding to the design Returns: :class:`CustomQGraphicsView` """ slice_scene = QGraphicsScene(parent=self.slice_graphics_view) slice_root = SliceRootItem(rect=slice_scene.sceneRect(), parent=None, window=self, document=doc) slice_scene.addItem(slice_root) slice_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert slice_root.scene() == slice_scene sgv = self.slice_graphics_view sgv.setScene(slice_scene) sgv.scene_root_item = slice_root sgv.setName("SliceView") sgv.setScaleFitFactor(0.7) self.slice_tool_manager = SliceToolManager(self, slice_root) return sgv # end def def _initEditMenu(self): """Initializes the Edit menu """ us = self.controller.undoStack() qatrans = QApplication.translate action_undo = us.createUndoAction(self) action_undo.setText(qatrans("MainWindow", "Undo", None)) action_undo.setShortcut(qatrans("MainWindow", "Ctrl+Z", None)) self.actionUndo = action_undo action_redo = us.createRedoAction(self) action_redo.setText(qatrans("MainWindow", "Redo", None)) action_redo.setShortcut(qatrans("MainWindow", "Ctrl+Shift+Z", None)) self.actionRedo = action_redo self.sep = sep = QAction(self) sep.setSeparator(True) self.menu_edit.insertAction(sep, action_redo) self.menu_edit.insertAction(action_redo, action_undo) # print([x.text() for x in self.menu_edit.actions()]) # self.main_splitter.setSizes([400, 400, 180]) # balance main_splitter size self.statusBar().showMessage("") # end def def _finishInit(self): """Handle the dockwindow visibility and action checked status. The console visibility is explicitly stored in the settings file, since it doesn't seem to work if we treat it like a normal dock widget. """ inspector_visible = self.inspector_dock_widget.isVisibleTo(self) self.action_inspector.setChecked(inspector_visible) path_visible = self.path_dock_widget.isVisibleTo(self) self.action_path.setChecked(path_visible) slice_visible = self.slice_dock_widget.isVisibleTo(self) # NOTE THIS IS ALWAYS FALSE FOR SOME REASON self.action_slice.setChecked(slice_visible) # end def def getMouseViewTool(self, tool_type_name: str) -> AbstractTool: """Give a tool type, return the tool for the view the mouse is over Args: tool_type_name: the tool which is active or None Returns: active tool for the view the tool is in """ return_tool = None for view in self.views.values(): if view.underMouse(): root_item = view.rootItem() # print("I am under mouse", view) if root_item.manager.isToolActive(tool_type_name): # print("{} is active".format(tool_type_name)) return_tool = root_item.manager.activeToolGetter() else: # print("no {} HERE!".format(tool_type_name)) pass break return return_tool # end def def doMouseViewDestroy(self): """Destroy the view the mouse is over """ return_tool = None for name, view in self.views.items(): if view.underMouse(): self.destroyView(name)
class DocumentController(): """ Connects UI buttons to their corresponding actions in the model. """ ### INIT METHODS ### def __init__(self, document): """docstring for __init__""" # initialize variables self._document = document print("the doc", self._document) self._document.setController(self) self._active_part = None self._filename = None self._file_open_path = None # will be set in _readSettings self._has_no_associated_file = True self._path_view_instance = None self._slice_view_instance = None self._undo_stack = None self.win = None self.fileopendialog = None self.filesavedialog = None self.settings = QSettings() self._readSettings() # call other init methods self._initWindow() app().document_controllers.add(self) def _initWindow(self): """docstring for initWindow""" self.win = DocumentWindow(doc_ctrlr=self) # self.win.setWindowIcon(app().icon) app().documentWindowWasCreatedSignal.emit(self._document, self.win) self._connectWindowSignalsToSelf() self.win.show() app().active_document = self def _initMaya(self): """ Initialize Maya-related state. Delete Maya nodes if there is an old document left over from the same session. Set up the Maya window. """ # There will only be one document if (app().active_document and app().active_document.win and not app().active_document.win.close()): return del app().active_document app().active_document = self import maya.OpenMayaUI as OpenMayaUI import sip ptr = OpenMayaUI.MQtUtil.mainWindow() mayaWin = sip.wrapinstance(int(ptr), QMainWindow) self.windock = QDockWidget("cadnano") self.windock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.windock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.windock.setWidget(self.win) mayaWin.addDockWidget(Qt.DockWidgetArea(Qt.LeftDockWidgetArea), self.windock) self.windock.setVisible(True) def _connectWindowSignalsToSelf(self): """This method serves to group all the signal & slot connections made by DocumentController""" self.win.action_new.triggered.connect(self.actionNewSlot) self.win.action_open.triggered.connect(self.actionOpenSlot) self.win.action_close.triggered.connect(self.actionCloseSlot) self.win.action_save.triggered.connect(self.actionSaveSlot) self.win.action_save_as.triggered.connect(self.actionSaveAsSlot) # self.win.action_SVG.triggered.connect(self.actionSVGSlot) # self.win.action_autostaple.triggered.connect(self.actionAutostapleSlot) # self.win.action_export_staples.triggered.connect(self.actionExportSequencesSlot) self.win.action_preferences.triggered.connect(self.actionPrefsSlot) self.win.action_modify.triggered.connect(self.actionModifySlot) self.win.action_new_honeycomb_part.triggered.connect(\ self.actionAddHoneycombPartSlot) self.win.action_new_square_part.triggered.connect(\ self.actionAddSquarePartSlot) self.win.closeEvent = self.windowCloseEventHandler self.win.action_about.triggered.connect(self.actionAboutSlot) self.win.action_cadnano_website.triggered.connect( self.actionCadnanoWebsiteSlot) self.win.action_feedback.triggered.connect(self.actionFeedbackSlot) self.win.action_filter_handle.triggered.connect( self.actionFilterHandleSlot) self.win.action_filter_endpoint.triggered.connect( self.actionFilterEndpointSlot) self.win.action_filter_strand.triggered.connect( self.actionFilterStrandSlot) self.win.action_filter_xover.triggered.connect( self.actionFilterXoverSlot) self.win.action_filter_scaf.triggered.connect( self.actionFilterScafSlot) self.win.action_filter_stap.triggered.connect( self.actionFilterStapSlot) ### SLOTS ### def undoStackCleanChangedSlot(self): """The title changes to include [*] on modification.""" self.win.setWindowModified(not self.undoStack().isClean()) self.win.setWindowTitle(self.documentTitle()) def actionAboutSlot(self): """Displays the about cadnano dialog.""" dialog = QDialog() dialog_about = Ui_About() # reusing this dialog, should rename dialog.setStyleSheet( "QDialog { background-image: url(ui/dialogs/images/cadnano2-about.png); background-repeat: none; }" ) dialog_about.setupUi(dialog) dialog.exec_() filter_list = ["strand", "endpoint", "xover", "virtual_helix"] def actionFilterHandleSlot(self): """Disables all other selection filters when active.""" fH = self.win.action_filter_handle fE = self.win.action_filter_endpoint fS = self.win.action_filter_strand fX = self.win.action_filter_xover fH.setChecked(True) if fE.isChecked(): fE.setChecked(False) if fS.isChecked(): fS.setChecked(False) if fX.isChecked(): fX.setChecked(False) self._document.documentSelectionFilterChangedSignal.emit( ["virtual_helix"]) def actionFilterEndpointSlot(self): """ Disables handle filters when activated. Remains checked if no other item-type filter is active. """ fH = self.win.action_filter_handle fE = self.win.action_filter_endpoint fS = self.win.action_filter_strand fX = self.win.action_filter_xover if fH.isChecked(): fH.setChecked(False) if not fS.isChecked() and not fX.isChecked(): fE.setChecked(True) self._strandFilterUpdate() # end def def actionFilterStrandSlot(self): """ Disables handle filters when activated. Remains checked if no other item-type filter is active. """ fH = self.win.action_filter_handle fE = self.win.action_filter_endpoint fS = self.win.action_filter_strand fX = self.win.action_filter_xover if fH.isChecked(): fH.setChecked(False) if not fE.isChecked() and not fX.isChecked(): fS.setChecked(True) self._strandFilterUpdate() # end def def actionFilterXoverSlot(self): """ Disables handle filters when activated. Remains checked if no other item-type filter is active. """ fH = self.win.action_filter_handle fE = self.win.action_filter_endpoint fS = self.win.action_filter_strand fX = self.win.action_filter_xover if fH.isChecked(): fH.setChecked(False) if not fE.isChecked() and not fS.isChecked(): fX.setChecked(True) self._strandFilterUpdate() # end def def actionFilterScafSlot(self): """Remains checked if no other strand-type filter is active.""" fSc = self.win.action_filter_scaf fSt = self.win.action_filter_stap if not fSc.isChecked() and not fSt.isChecked(): fSc.setChecked(True) self._strandFilterUpdate() def actionFilterStapSlot(self): """Remains checked if no other strand-type filter is active.""" fSc = self.win.action_filter_scaf fSt = self.win.action_filter_stap if not fSc.isChecked() and not fSt.isChecked(): fSt.setChecked(True) self._strandFilterUpdate() # end def def _strandFilterUpdate(self): win = self.win filter_list = [] if win.action_filter_endpoint.isChecked(): filter_list.append("endpoint") if win.action_filter_strand.isChecked(): filter_list.append("strand") if win.action_filter_xover.isChecked(): filter_list.append("xover") if win.action_filter_scaf.isChecked(): filter_list.append("scaffold") if win.action_filter_stap.isChecked(): filter_list.append("staple") self._document.documentSelectionFilterChangedSignal.emit(filter_list) # end def def actionNewSlot(self): """ 1. If document is has no parts, do nothing. 2. If document is dirty, call maybeSave and continue if it succeeds. 3. Create a new document and swap it into the existing ctrlr/window. """ # clear/reset the view! if len(self._document.parts()) == 0: return # no parts if self.maybeSave() == False: return # user canceled in maybe save else: # user did not cancel if self.filesavedialog != None: self.filesavedialog.finished.connect(self.newClickedCallback) else: # user did not save self.newClickedCallback() # finalize new def actionOpenSlot(self): """ 1. If document is untouched, proceed to open dialog. 2. If document is dirty, call maybesave and continue if it succeeds. Downstream, the file is selected in openAfterMaybeSave, and the selected file is actually opened in openAfterMaybeSaveCallback. """ if self.maybeSave() == False: return # user canceled in maybe save else: # user did not cancel if hasattr(self, "filesavedialog"): # user did save if self.filesavedialog != None: self.filesavedialog.finished.connect( self.openAfterMaybeSave) else: self.openAfterMaybeSave() # windows else: # user did not save self.openAfterMaybeSave() # finalize new def actionCloseSlot(self): """This will trigger a Window closeEvent.""" # if util.isWindows(): self.win.close() app().qApp.exit(0) def actionSaveSlot(self): """SaveAs if necessary, otherwise overwrite existing file.""" if self._has_no_associated_file: self.saveFileDialog() return self.writeDocumentToFile() def actionSaveAsSlot(self): """Open a save file dialog so user can choose a name.""" self.saveFileDialog() def actionSVGSlot(self): """docstring for actionSVGSlot""" fname = os.path.basename(str(self.filename())) if fname == None: directory = "." else: directory = QFileInfo(fname).path() fdialog = QFileDialog(self.win, "%s - Save As" % QApplication.applicationName(), directory, "%s (*.svg)" % QApplication.applicationName()) fdialog.setAcceptMode(QFileDialog.AcceptSave) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) self.svgsavedialog = fdialog self.svgsavedialog.filesSelected.connect(self.saveSVGDialogCallback) fdialog.open() class DummyChild(QGraphicsItem): def boundingRect(self): return QRect(200, 200) # self.parentObject().boundingRect() def paint(self, painter, option, widget=None): pass def saveSVGDialogCallback(self, selected): if isinstance(selected, (list, tuple)): fname = selected[0] else: fname = selected print("da fname", fname) if fname is None or os.path.isdir(fname): return False if not fname.lower().endswith(".svg"): fname += ".svg" if self.svgsavedialog != None: self.svgsavedialog.filesSelected.disconnect( self.saveSVGDialogCallback) del self.svgsavedialog # prevents hang self.svgsavedialog = None generator = QSvgGenerator() generator.setFileName(fname) generator.setSize(QSize(200, 200)) generator.setViewBox(QRect(0, 0, 2000, 2000)) painter = QPainter() # Render through scene # painter.begin(generator) # self.win.pathscene.render(painter) # painter.end() # Render item-by-item painter = QPainter() style_option = QStyleOptionGraphicsItem() q = [self.win.pathroot] painter.begin(generator) while q: graphics_item = q.pop() transform = graphics_item.itemTransform(self.win.sliceroot)[0] painter.setTransform(transform) if graphics_item.isVisible(): graphics_item.paint(painter, style_option, None) q.extend(graphics_item.childItems()) painter.end() def actionExportSequencesSlot(self): """ Triggered by clicking Export Staples button. Opens a file dialog to determine where the staples should be saved. The callback is exportStaplesCallback which collects the staple sequences and exports the file. """ # Validate that no staple oligos are loops. part = self.activePart() if part is None: return stap_loop_olgs = part.getStapleLoopOligos() if stap_loop_olgs: from ui.dialogs.ui_warning import Ui_Warning dialog = QDialog() dialogWarning = Ui_Warning() # reusing this dialog, should rename dialog.setStyleSheet( "QDialog { background-image: url(ui/dialogs/images/cadnano2-about.png); background-repeat: none; }" ) dialogWarning.setupUi(dialog) locs = ", ".join([o.locString() for o in stap_loop_olgs]) msg = "Part contains staple loop(s) at %s.\n\nUse the break tool to introduce 5' & 3' ends before exporting. Loops have been colored red; use undo to revert." % locs dialogWarning.title.setText("Staple validation failed") dialogWarning.message.setText(msg) for o in stap_loop_olgs: o.applyColor(styles.stapColors[0].name()) dialog.exec_() return # Proceed with staple export. fname = self.filename() if fname == None: directory = "." else: directory = QFileInfo(fname).path() if util.isWindows(): # required for native looking file window fname = QFileDialog.getSaveFileName( self.win, "%s - Export As" % QApplication.applicationName(), directory, "(*.csv)") self.saveStaplesDialog = None self.exportStaplesCallback(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "%s - Export As" % QApplication.applicationName(), directory, "(*.csv)") fdialog.setAcceptMode(QFileDialog.AcceptSave) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) self.saveStaplesDialog = fdialog self.saveStaplesDialog.filesSelected.connect( self.exportStaplesCallback) fdialog.open() # end def def actionPrefsSlot(self): app().prefsClicked() # end def def actionAutostapleSlot(self): part = self.activePart() if part: self.win.path_graphics_view.setViewportUpdateOn(False) part.autoStaple() self.win.path_graphics_view.setViewportUpdateOn(True) # end def def actionModifySlot(self): """ Notifies that part root items that parts should respond to modifier selection signals. """ pass # uncomment for debugging # isChecked = self.win.actionModify.isChecked() # self.win.pathroot.setModifyState(isChecked) # self.win.sliceroot.setModifyState(isChecked) # if app().isInMaya(): # isChecked = self.win.actionModify.isChecked() # self.win.pathroot.setModifyState(isChecked) # self.win.sliceroot.setModifyState(isChecked) # self.win.solidroot.setModifyState(isChecked) def actionAddHoneycombPartSlot(self): part = self._document.addHoneycombPart() self.setActivePart(part) # end def def actionAddSquarePartSlot(self): part = self._document.addSquarePart() self.setActivePart(part) # end def def actionAddHpxPartSlot(self): # part = self._document.addHpxPart() # self.setActivePart(part) pass # end def def actionAddSpxPartSlot(self): # part = self._document.addHpxPart() # self.setActivePart(part) pass # end def def actionRenumberSlot(self): part = self.activePart() if part: coordList = self.win.pathroot.getSelectedPartOrderedVHList() part.renumber(coordList) # end def ### ACCESSORS ### def document(self): return self._document # end def def window(self): return self.win # end def def setDocument(self, doc): """ Sets the controller's document, and informs the document that this is its controller. """ self._document = doc doc.setController(self) # end def def activePart(self): if self._active_part == None: self._active_part = self._document.selectedPart() return self._active_part # end def def setActivePart(self, part): self._active_part = part # end def def undoStack(self): return self._document.undoStack() # end def ### PRIVATE SUPPORT METHODS ### def newDocument(self, doc=None, fname=None): """Creates a new Document, reusing the DocumentController.""" if fname is not None and self._filename == fname: setReopen(True) self._document.resetViews() setBatch(True) self._document.removeAllParts() # clear out old parts setBatch(False) self._document.undoStack().clear() # reset undostack self._filename = fname if fname else "untitled.json" self._has_no_associated_file = fname == None self._active_part = None self.win.setWindowTitle(self.documentTitle() + '[*]') # end def def saveFileDialog(self): fname = self.filename() if fname == None: directory = "." else: directory = QFileInfo(fname).path() if util.isWindows(): # required for native looking file window fname = QFileDialog.getSaveFileName( self.win, "%s - Save As" % QApplication.applicationName(), directory, "%s (*.json)" % QApplication.applicationName()) if isinstance(fname, (list, tuple)): fname = fname[0] self.writeDocumentToFile(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "%s - Save As" % QApplication.applicationName(), directory, "%s (*.json)" % QApplication.applicationName()) fdialog.setAcceptMode(QFileDialog.AcceptSave) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) self.filesavedialog = fdialog self.filesavedialog.filesSelected.connect( self.saveFileDialogCallback) fdialog.open() # end def def _readSettings(self): self.settings.beginGroup("FileSystem") self._file_open_path = self.settings.value("openpath", QDir().homePath()) self.settings.endGroup() def _writeFileOpenPath(self, path): """docstring for _writePath""" self._file_open_path = path self.settings.beginGroup("FileSystem") self.settings.setValue("openpath", path) self.settings.endGroup() ### SLOT CALLBACKS ### def actionNewSlotCallback(self): """ Gets called on completion of filesavedialog after newClicked's maybeSave. Removes the dialog if necessary, but it was probably already removed by saveFileDialogCallback. """ if self.filesavedialog != None: self.filesavedialog.finished.disconnect(self.actionNewSlotCallback) del self.filesavedialog # prevents hang (?) self.filesavedialog = None self.newDocument() def exportStaplesCallback(self, selected): """Export all staple sequences to selected CSV file.""" if isinstance(selected, (list, tuple)): fname = selected[0] else: fname = selected if fname is None or os.path.isdir(fname): return False if not fname.lower().endswith(".csv"): fname += ".csv" if self.saveStaplesDialog != None: self.saveStaplesDialog.filesSelected.disconnect( self.exportStaplesCallback) # manual garbage collection to prevent hang (in osx) del self.saveStaplesDialog self.saveStaplesDialog = None # write the file output = self.activePart().getStapleSequences() with open(fname, 'w') as f: f.write(output) # end def def newClickedCallback(self): """ Gets called on completion of filesavedialog after newClicked's maybeSave. Removes the dialog if necessary, but it was probably already removed by saveFileDialogCallback. """ if self.filesavedialog != None: self.filesavedialog.finished.disconnect(self.newClickedCallback) del self.filesavedialog # prevents hang (?) self.filesavedialog = None self.newDocument() def openAfterMaybeSaveCallback(self, selected): """ Receives file selection info from the dialog created by openAfterMaybeSave, following user input. Extracts the file name and passes it to the decode method, which returns a new document doc, which is then set as the open document by newDocument. Calls finalizeImport and disconnects dialog signaling. """ if isinstance(selected, (list, tuple)): fname = selected[0] else: fname = selected if fname is None or fname == '' or os.path.isdir(fname): return False if not os.path.exists(fname): return False self._writeFileOpenPath(os.path.dirname(fname)) self.win.path_graphics_view.setViewportUpdateOn(False) self.win.slice_graphics_view.setViewportUpdateOn(False) self.newDocument(fname=fname) decodeFile(fname, document=self._document) self.win.path_graphics_view.setViewportUpdateOn(True) self.win.slice_graphics_view.setViewportUpdateOn(True) self.win.path_graphics_view.update() self.win.slice_graphics_view.update() if hasattr(self, "filesavedialog"): # user did save if self.fileopendialog != None: self.fileopendialog.filesSelected.disconnect(\ self.openAfterMaybeSaveCallback) # manual garbage collection to prevent hang (in osx) del self.fileopendialog self.fileopendialog = None def saveFileDialogCallback(self, selected): """If the user chose to save, write to that file.""" if isinstance(selected, (list, tuple)): fname = selected[0] else: fname = selected if fname is None or os.path.isdir(fname): return False if not fname.lower().endswith(".json"): fname += ".json" if self.filesavedialog != None: self.filesavedialog.filesSelected.disconnect( self.saveFileDialogCallback) del self.filesavedialog # prevents hang self.filesavedialog = None self.writeDocumentToFile(fname) self._writeFileOpenPath(os.path.dirname(fname)) ### EVENT HANDLERS ### def windowCloseEventHandler(self, event): """Intercept close events when user attempts to close the window.""" if self.maybeSave(): event.accept() # if app().isInMaya(): # self.windock.setVisible(False) # del self.windock # self.windock = action_new_honeycomb_part dcs = app().document_controllers if self in dcs: dcs.remove(self) else: event.ignore() self.actionCloseSlot() ### FILE INPUT ## def documentTitle(self): fname = os.path.basename(str(self.filename())) if not self.undoStack().isClean(): fname += '[*]' return fname def filename(self): return self._filename def setFilename(self, proposed_fname): if self._filename == proposed_fname: return True self._filename = proposed_fname self._has_no_associated_file = False self.win.setWindowTitle(self.documentTitle()) return True def openAfterMaybeSave(self): """ This is the method that initiates file opening. It is called by actionOpenSlot to spawn a QFileDialog and connect it to a callback method. """ path = self._file_open_path if util.isWindows(): # required for native looking file window#"/", fname = QFileDialog.getOpenFileName( None, "Open Document", path, "cadnano1 / cadnano2 Files (*.nno *.json *.cadnano)") self.filesavedialog = None self.openAfterMaybeSaveCallback(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "Open Document", path, "cadnano1 / cadnano2 Files (*.nno *.json *.cadnano)") fdialog.setAcceptMode(QFileDialog.AcceptOpen) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) self.fileopendialog = fdialog self.fileopendialog.filesSelected.connect( self.openAfterMaybeSaveCallback) fdialog.open() # end def ### FILE OUTPUT ### def maybeSave(self): """Save on quit, check if document changes have occured.""" if app().dontAskAndJustDiscardUnsavedChanges: return True if not self.undoStack().isClean(): # document dirty? savebox = QMessageBox( QMessageBox.Warning, "Application", "The document has been modified.\nDo you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, self.win, Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint | Qt.Sheet) savebox.setWindowModality(Qt.WindowModal) save = savebox.button(QMessageBox.Save) discard = savebox.button(QMessageBox.Discard) cancel = savebox.button(QMessageBox.Cancel) save.setShortcut("Ctrl+S") discard.setShortcut(QKeySequence("D,Ctrl+D")) cancel.setShortcut(QKeySequence("C,Ctrl+C,.,Ctrl+.")) ret = savebox.exec_() del savebox # manual garbage collection to prevent hang (in osx) if ret == QMessageBox.Save: return self.actionSaveAsSlot() elif ret == QMessageBox.Cancel: return False return True def writeDocumentToFile(self, filename=None): if filename == None or filename == '': if self._has_no_associated_file: return False filename = self.filename() try: with open(filename, 'w') as f: helix_order_list = self.win.pathroot.getSelectedPartOrderedVHList( ) encode(self._document, helix_order_list, f) except IOError: flags = Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint | Qt.Sheet errorbox = QMessageBox(QMessageBox.Critical, "cadnano", "Could not write to '%s'." % filename, QMessageBox.Ok, self.win, flags) errorbox.setWindowModality(Qt.WindowModal) errorbox.open() return False self.undoStack().setClean() self.setFilename(filename) return True def actionCadnanoWebsiteSlot(self): import webbrowser webbrowser.open("http://cadnano.org/") def actionFeedbackSlot(self): import webbrowser webbrowser.open("http://cadnano.org/feedback")
def load_ui_settings(self, q_path: str) -> None: settings = QSettings() settings.beginGroup("%s/%s/%s" % (q_path, self.name, 'ui')) for ui_label in settings.childKeys(): self.ui[ui_label] = settings.value(ui_label)
def saveSettings(self): s= QSettings() s.beginGroup("helper_applications") for name, title in self.items(): s.setValue(name, self.entries[name].path())
def saveSettings(self): s= QSettings() s.beginGroup("helper_applications") s.setValue("printcommand", self.printCommand.path()) s.setValue("printcommand/dialog", self.printDialogCheck.isChecked()) s.setValue("printcommand/dpi", int(self.resolution.currentText()))