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 save(self, scheme): """Save the settings to the scheme.""" s = QSettings() s.beginGroup("fontscolors/" + scheme) # save font s.setValue("fontfamily", self.font.family()) s.setValue("fontsize", self.font.pointSizeF()) # save base colors for name in baseColors: s.setValue("basecolors/"+name, self.baseColors[name].name()) # save default styles s.beginGroup("defaultstyles") for name in defaultStyles: s.beginGroup(name) self.saveTextFormat(self.defaultStyles[name], s) s.endGroup() s.endGroup() # save all specific styles s.beginGroup("allstyles") for group, styles in ly.colorize.default_mapping(): s.beginGroup(group) for style in styles: s.beginGroup(style.name) self.saveTextFormat(self.allStyles[group][style.name], s) s.endGroup() s.endGroup() s.endGroup()
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 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 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 load_window_geometry(): settings = QSettings() settings.beginGroup(group_main_window) size = settings.value(key_size, type=QSize) pos = settings.value(key_pos, type=QPoint) is_full_screen = settings.value(key_is_full_screen, type=bool) splitter_sizes = settings.value(key_splitter_sizes, type=int) settings.endGroup() return {key_size: size, key_pos: pos, key_is_full_screen: is_full_screen, key_splitter_sizes: splitter_sizes}
def _dont_show_start_page_again(self): settings.SHOW_START_PAGE = False qsettings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) qsettings.beginGroup('preferences') qsettings.beginGroup('general') qsettings.setValue('showStartPage', settings.SHOW_START_PAGE) qsettings.endGroup() qsettings.endGroup() self.mainContainer.actualTab.close_tab()
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 write(self): """ Write the current settings to native storage """ settings = QSettings(_qs_org, _qs_app) settings.beginGroup(_qs_group) for p in self.properties: settings.setValue(p.name, p.accessor()) settings.endGroup()
def saveSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup('preferences') settings.setValue('port', self.port) settings.setValue('host', self.host) settings.setValue('path_mapping', self.mapping_str) settings.setValue('target_platform', self.target_platform) settings.setValue('batch_size', self.batch_size) settings.setValue('cluster_support', self.cluster_support) settings.endGroup()
def write_settings(self): settings = QSettings('MyWallet') if system() == 'Windows' and self.__current_path: settings = QSettings(self.__current_path + 'mywallet.conf', QSettings.IniFormat) settings.beginGroup(self.MAIN_SETTINGS) settings.setValue('size', self.size()) settings.setValue('position', self.pos()) if self.__current_path: settings.setValue('path', self.__current_path) if self.__wallet_name: settings.setValue('wallet_name', self.__wallet_name) settings.endGroup()
def restoreSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup('Preferences') if settings.contains('compression'): value = settings.value('compression', None) self.compression = value if settings.contains('compression_opts'): value = settings.value('compression_opts', None) if isinstance(value, basestring): self.compression_opts = eval(value) else: self.compression_opts = value if settings.contains('max_sv_fraction'): value = settings.value('max_sv_fraction', type=float) self.max_sv_fraction = value if settings.contains('batch_size'): value = settings.value('batch_size', type=str) self.batch_size = value if settings.contains('default_sorter'): value = settings.value('default_sorter', type=str) self.default_sorter = value if settings.contains('default_feature_group'): value = settings.value('default_feature_group') self.default_feature_group = value if settings.contains('contours_complementary_color'): value = settings.value('contours_complementary_color', type=bool) self.contours_complementary_color = value if settings.contains('save_raw_images'): value = settings.value('save_raw_images', type=bool) self.save_raw_images = value if settings.contains('only_classifier_features'): value = settings.value('only_classifier_features', type=bool) self.only_classifier_features = value if settings.contains('features'): value = settings.value('features') self.features = value if settings.contains('enable_plugins'): value = settings.value('enable_plugins', type=bool) self.enable_plugins = value settings.endGroup()
def save(self): """ Save all shortcuts to settings """ settings = QSettings(resources.SETTINGS_PATH, QSettings.IniFormat) settings.beginGroup("shortcuts") for index in range(self.result_widget.topLevelItemCount()): item = self.result_widget.topLevelItem(index) shortcut_keys = item.text(1) shortcut_name = item.text(2) settings.setValue(shortcut_name, shortcut_keys) settings.endGroup() actions.Actions().update_shortcuts()
def restore_state(self): assert self.ROLE settings = QSettings() settings.beginGroup(self.ROLE) geometry = settings.value('geometry') if geometry is not None: self.restoreGeometry(geometry) if isinstance(self, QMainWindow): state = settings.value('state') if state is not None: self.restoreState(state) self._restore_state(settings) settings.endGroup()
def saveSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup("Preferences") settings.setValue("compression", self.compression) settings.setValue("compression_opts", self.compression_opts) settings.setValue("max_sv_fraction", self.max_sv_fraction) settings.setValue("interactive_item_limit", self.interactive_item_limit) settings.setValue("default_sorter", self.default_sorter) settings.setValue("default_feature_group", self.default_feature_group) settings.setValue("contours_complementary_color", self.contours_complementary_color) settings.setValue("save_raw_images", self.save_raw_images) settings.endGroup()
def toSettings(self): ''' Save my values to settings. ''' qsettings = QSettings() qsettings.beginGroup( "paperlessPrinter" ) # Although Paper is pickleable, simplify to int. QSettings stores objects correctly? qsettings.setValue( "paperEnum", self.paper.value ) qsettings.setValue( "paperOrientation", self.orientation.value ) integralOrientedSize = self.paper.integralOrientedSizeMM(self.orientation.value) qsettings.setValue( "paperintegralOrientedWidth", integralOrientedSize.width()) qsettings.setValue( "paperintegralOrientedHeight", integralOrientedSize.height()) qsettings.endGroup()
def saveSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup('preferences') settings.setValue('port', self.port) settings.setValue('host', self.host) settings.setValue('path_mapping', self.mapping_str) settings.setValue('target_platform', self.target_platform) settings.setValue('batch_size', self.batch_size) settings.setValue('cluster_support', self.cluster_support) settings.setValue('track_length', self.track_length) settings.setValue('cradius', self.cradius) settings.setValue('display_tracks', self.display_tracks) settings.setValue('write_logs', self.write_logs) settings.endGroup()
def sessionOpened(self): 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.statusLabel.setText("This examples requires that you run the " "Fortune Server example as well.") self.enableGetFortuneButton()
def saveSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup('Preferences') settings.setValue('compression', self.compression) settings.setValue('compression_opts', self.compression_opts) settings.setValue('max_sv_fraction', self.max_sv_fraction) settings.setValue('batch_size', self.batch_size) settings.setValue('default_sorter', self.default_sorter) settings.setValue('default_feature_group', self.default_feature_group) settings.setValue('contours_complementary_color', self.contours_complementary_color) settings.setValue('save_raw_images', self.save_raw_images) settings.setValue('only_classifier_features', self.only_classifier_features) settings.setValue('features', self.features) settings.setValue('enable_plugins', self.enable_plugins) settings.endGroup()
def __init__(self, parent=None): super(Server, self).__init__(parent) self.tcpServer = None self.networkSession = None self.statusLabel = QLabel() quitButton = QPushButton("Quit") quitButton.setAutoDefault(False) manager = QNetworkConfigurationManager() if manager.capabilities() & QNetworkConfigurationManager.NetworkSessionRequired: settings = QSettings(QSettings.UserScope, 'QtProject') settings.beginGroup('QtNetwork') id = settings.value('DefaultNetworkConfiguration', '') settings.endGroup() config = manager.configurationFromIdentifier(id) if config.state() & QNetworkConfiguration.Discovered == 0: config = manager.defaultConfiguration() self.networkSession = QNetworkSession(config, self) self.networkSession.opened.connect(self.sessionOpened) self.statusLabel.setText("Opening network session.") self.networkSession.open() else: self.sessionOpened() quitButton.clicked.connect(self.close) self.tcpServer.newConnection.connect(self.sendFortune) buttonLayout = QHBoxLayout() buttonLayout.addStretch(1) buttonLayout.addWidget(quitButton) buttonLayout.addStretch(1) mainLayout = QVBoxLayout() mainLayout.addWidget(self.statusLabel) mainLayout.addLayout(buttonLayout) self.setLayout(mainLayout) self.setWindowTitle("Fortune Server")
def done(self, r): if r == QDialog.Accepted: selected_project = self.projects_list.selectedItems() if len(selected_project) == 0: return selected_project = selected_project[0] self.open_project_name = selected_project.text() self.open_project_dir = selected_project.data(self._PROJECT_DIR_ROLE) s = QSettings() s.beginGroup("ProjectManager") s.setValue("exist_projects", self.exist_projects) s.endGroup() super(ProjectManagerDialog, self).done(r) super(ProjectManagerDialog, self).done(r)
def saveSettings(self, currentKey, namesGroup, removePrefix=None): # first save new scheme names s = QSettings() s.beginGroup(namesGroup) for i in range(self.scheme.count()): if self.scheme.itemData(i) != 'default': s.setValue(self.scheme.itemData(i), self.scheme.itemText(i)) for scheme in self._schemesToRemove: s.remove(scheme) s.endGroup() if removePrefix: for scheme in self._schemesToRemove: s.remove("{0}/{1}".format(removePrefix, scheme)) # then save current scheme = self.currentScheme() s.setValue(currentKey, scheme) # clean up self._schemesToRemove = set()
def read_settings(self): settings = QSettings('MyWallet') if system() == 'Windows': self.set_current_path(QDir.home().path() + '/MyWallet/') settings = QSettings(self.__current_path + 'mywallet.conf', QSettings.IniFormat) settings.beginGroup(self.MAIN_SETTINGS) if settings.value('size'): self.resize(settings.value('size', type=QSize)) if settings.value('position'): self.move(settings.value('position', type=QPoint)) if settings.value('path') and settings.value('wallet_name'): self.set_current_path(settings.value('path')) self.__wallet_name = settings.value('wallet_name') else: if system() == 'Windows': self.set_current_path(QDir.home().path() + '/MyWallet/') elif system() == 'Linux': self.set_current_path(QDir.home().path() + '/.MyWallet/') self.__wallet_name = None settings.endGroup()
def read(self): """ Read in the Qt settings from native storage """ settings = QSettings(_qs_org, _qs_app) settings.beginGroup(_qs_group) for p in self.properties: if p.type is None: # read without the type parameter, # let PyQt attempt to convert automatically v = settings.value(p.name, p.default) else: v = settings.value(p.name, p.default, p.type) # call the on_read callback p.on_read(v) settings.endGroup()
def restoreSettings(self): settings = QSettings(version.organisation, version.appname) settings.beginGroup("Preferences") if settings.contains("compression"): value = settings.value("compression", None) self.compression = value if settings.contains("compression_opts"): value = settings.value("compression_opts", None) if isinstance(value, basestring): self.compression_opts = eval(value) else: self.compression_opts = value if settings.contains("max_sv_fraction"): value = settings.value("max_sv_fraction", type=float) self.max_sv_fraction = value if settings.contains("interactive_item_limit"): value = settings.value("interactive_item_limit", 5000, type=int) self.interactive_item_limit = value if settings.contains("default_sorter"): value = settings.value("default_sorter", type=str) self.default_sorter = value if settings.contains("default_feature_group"): value = settings.value("default_feature_group") self.default_feature_group = value if settings.contains("contours_complementary_color"): value = settings.value("contours_complementary_color", type=bool) self.contours_complementary_color = value if settings.contains("save_raw_images"): value = settings.value("save_raw_images", type=bool) self.save_raw_images = value settings.endGroup()
def fromSettings(self, getDefaultsFromPrinterAdaptor=None): ''' Set my values from settings (that persist across app sessions.) If settings don't exist yet (first run of app after installation), AND a printerAdaptor is passed, default my values from printerAdaptor. ''' qsettings = QSettings() qsettings.beginGroup( "paperlessPrinter" ) # TODO better name # Prepare default values if getDefaultsFromPrinterAdaptor is not None: defaultPaperEnum = getDefaultsFromPrinterAdaptor.paper().value defaultOrientation = getDefaultsFromPrinterAdaptor.orientation() else: defaultPaperEnum = 0 # Hack TODO PaperSizeModel.default() defaultOrientation = 0 # PageOrientationModel.default() defaultSize = CustomPaper.defaultSize() # Get settings enumValue = qsettings.value( "paperEnum", defaultPaperEnum) orientationValue = qsettings.value( "paperOrientation", defaultOrientation ) integralOrientedWidthValue = qsettings.value( "paperintegralOrientedWidth", defaultSize.width()) integralOrientedHeightValue = qsettings.value( "paperintegralOrientedHeight", defaultSize.height()) # Copy settings to self # Orientation first, needed for orienting paper size self.orientation = Orientation(initialValue=self._intForSetting(orientationValue)) size = QSize(self._intForSetting(integralOrientedWidthValue), self._intForSetting(integralOrientedHeightValue)) self.paper = self._paperFromSettings(paperEnum=self._intForSetting(enumValue), integralOrientedPaperSize=size, orientation=self.orientation) qsettings.endGroup() assert isinstance(self.paper, Paper) # !!! Might be custom of unknown size assert isinstance(self.orientation, Orientation)
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('track_length'): self.track_length = settings.value('track_length', type=int) if settings.contains('cradius'): self.cradius = settings.value('cradius', type=int) if settings.contains('display_tracks'): self.display_tracks = settings.value('display_tracks', type=bool) if settings.contains('write_logs'): self.display_tracks = settings.value('write_logs', type=bool) settings.endGroup()
def saveSettings(self) -> None: self.incrementTip() settings = QSettings() settings.beginGroup("DidYouKnowWindow") settings.setValue("windowSize", self.size()) settings.endGroup()
class DocumentWindow(QMainWindow, ui_mainwindow.Ui_MainWindow): """docstring for DocumentWindow""" def __init__(self, parent=None, doc_ctrlr=None): super(DocumentWindow, self).__init__(parent) self.controller = doc_ctrlr doc = doc_ctrlr.document() self.setupUi(self) self.settings = QSettings() self._readSettings() # Slice setup self.slicescene = QGraphicsScene(parent=self.slice_graphics_view) self.sliceroot = SliceRootItem(rect=self.slicescene.sceneRect(),\ parent=None,\ window=self,\ document=doc) self.sliceroot.setFlag(QGraphicsItem.ItemHasNoContents) self.slicescene.addItem(self.sliceroot) self.slicescene.setItemIndexMethod(QGraphicsScene.NoIndex) assert self.sliceroot.scene() == self.slicescene self.slice_graphics_view.setScene(self.slicescene) self.slice_graphics_view.scene_root_item = self.sliceroot self.slice_graphics_view.setName("SliceView") self.slice_tool_manager = SliceToolManager(self) # Part toolbar splitter_size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) splitter_size_policy.setHorizontalStretch(0) splitter_size_policy.setVerticalStretch(0) splitter_size_policy.setHeightForWidth(self.main_splitter.sizePolicy().hasHeightForWidth()) self.slice_splitter.setSizePolicy(splitter_size_policy) self.slice_splitter.setFrameShape(QFrame.NoFrame) self.slice_splitter.setFrameShadow(QFrame.Plain) self.slice_splitter.setLineWidth(0) self.slice_splitter.setOrientation(Qt.Horizontal) self.slice_splitter.setOpaqueResize(False) self.slice_splitter.setHandleWidth(0) self.part_toolbar = PartToolBar(doc, self.slice_splitter) self.slice_splitter.addWidget(self.slice_graphics_view) # reorder # Path setup self.pathscene = QGraphicsScene(parent=self.path_graphics_view) self.pathroot = PathRootItem(rect=self.pathscene.sceneRect(),\ parent=None,\ window=self,\ document=doc) self.pathroot.setFlag(QGraphicsItem.ItemHasNoContents) self.pathscene.addItem(self.pathroot) self.pathscene.setItemIndexMethod(QGraphicsScene.NoIndex) assert self.pathroot.scene() == self.pathscene self.path_graphics_view.setScene(self.pathscene) self.path_graphics_view.scene_root_item = self.pathroot self.path_graphics_view.setScaleFitFactor(0.9) self.path_graphics_view.setName("PathView") # Path toolbar self.path_splitter.setSizePolicy(splitter_size_policy) self.path_splitter.setFrameShape(QFrame.NoFrame) self.path_splitter.setFrameShadow(QFrame.Plain) self.path_splitter.setLineWidth(0) self.path_splitter.setOrientation(Qt.Horizontal) self.path_splitter.setOpaqueResize(False) self.path_splitter.setHandleWidth(0) self.path_splitter.setObjectName("path_splitter") self.path_splitter.setSizes([600,0]) # for path_splitter horizontal self.path_splitter.addWidget(self.selectionToolBar) self.path_toolbar = PathToolBar(doc, self.path_splitter) # self.path_splitter.addWidget(self.path_graphics_view) # reorder self.path_color_panel = ColorPanel() self.path_graphics_view.toolbar = self.path_color_panel # HACK for customqgraphicsview self.pathscene.addItem(self.path_color_panel) self.path_tool_manager = PathToolManager(self, self.path_toolbar) self.slice_tool_manager.path_tool_manager = self.path_tool_manager self.path_tool_manager.slice_tool_manager = self.slice_tool_manager # set the selection filter default doc.documentSelectionFilterChangedSignal.emit(["endpoint", "scaffold", "staple", "xover"]) self.path_graphics_view.setupGL() self.slice_graphics_view.setupGL() if GL: pass # self.slicescene.drawBackground = self.drawBackgroundGL # self.pathscene.drawBackground = self.drawBackgroundGL # Edit menu setup self.actionUndo = doc_ctrlr.undoStack().createUndoAction(self) self.actionRedo = doc_ctrlr.undoStack().createRedoAction(self) self.actionUndo.setText(QApplication.translate( "MainWindow", "Undo", None)) self.actionUndo.setShortcut(QApplication.translate( "MainWindow", "Ctrl+Z", None)) self.actionRedo.setText(QApplication.translate( "MainWindow", "Redo", None)) self.actionRedo.setShortcut(QApplication.translate( "MainWindow", "Ctrl+Shift+Z", None)) self.sep = QAction(self) self.sep.setSeparator(True) self.menu_edit.insertAction(self.action_modify, self.sep) self.menu_edit.insertAction(self.sep, self.actionRedo) self.menu_edit.insertAction(self.actionRedo, self.actionUndo) self.main_splitter.setSizes([250, 550]) # balance main_splitter size self.statusBar().showMessage("") ### ACCESSORS ### def undoStack(self): return self.controller.undoStack() def selectedPart(self): return self.controller.document().selectedPart() def activateSelection(self, isActive): self.path_graphics_view.activateSelection(isActive) self.slice_graphics_view.activateSelection(isActive) # end def ### EVENT HANDLERS ### def focusInEvent(self): app().undoGroup.setActiveStack(self.controller.undoStack()) def moveEvent(self, event): """Reimplemented to save state on move.""" self.settings.beginGroup("MainWindow") self.settings.setValue("pos", self.pos()) self.settings.endGroup() def resizeEvent(self, event): """Reimplemented to save state on resize.""" self.settings.beginGroup("MainWindow") self.settings.setValue("size", self.size()) self.settings.endGroup() QWidget.resizeEvent(self, event) def changeEvent(self, event): QWidget.changeEvent(self, event) # end def ### DRAWING RELATED ### # def drawBackgroundGL(self, painter, rect): # """ # This method is for overloading the QGraphicsScene. # """ # if painter.paintEngine().type() != QPaintEngine.OpenGL and \ # painter.paintEngine().type() != QPaintEngine.OpenGL2: # # qWarning("OpenGLScene: drawBackground needs a QGLWidget to be set as viewport on the graphics view"); # return # # end if # painter.beginNativePainting() # GL.glDisable(GL.GL_DEPTH_TEST) # disable for 2D drawing # GL.glClearColor(1.0, 1.0, 1.0, 1.0) # GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) # # painter.endNativePainting() # # end def # def drawBackgroundNonGL(self, painter, rect): # """ # This method is for overloading the QGraphicsScene. # """ # print self # return QGraphicsScene.drawBackground(self, painter, rect) # # end def ### PRIVATE HELPER METHODS ### def _readSettings(self): self.settings.beginGroup("MainWindow") self.resize(self.settings.value("size", QSize(1100, 800))) self.move(self.settings.value("pos", QPoint(200, 200))) self.settings.endGroup()
class TasmotaDevicesModel(QAbstractTableModel): def __init__(self, *args, **kwargs): super(TasmotaDevicesModel, self).__init__(*args, **kwargs) self.settings = QSettings() self.settings.beginGroup("Devices") self._devices = [] for d in self.settings.childGroups(): self.loadDevice(d, self.settings.value("{}/full_topic".format(d)), self.settings.value("{}/friendly_name".format(d))) self.settings.endGroup() def addDevice(self, topic, full_topic, lwt="undefined"): rc = self.rowCount() self.beginInsertRows(QModelIndex(), rc, rc) self._devices.append([lwt, topic, full_topic, topic] + ([''] * (len(columns) - 4))) self.settings.beginGroup("Devices") self.settings.setValue("{}/full_topic".format(topic), full_topic) self.settings.setValue("{}/friendly_name".format(topic), full_topic) self.settings.endGroup() self.endInsertRows() return self.index(rc, 0) def loadDevice(self, topic, full_topic, friendly_name="", lwt="undefined"): rc = self.rowCount() self.beginInsertRows(QModelIndex(), rc, rc) self._devices.append([ lwt, topic, full_topic, friendly_name if friendly_name else topic ] + ([''] * (len(columns) - 4))) self.endInsertRows() return True def findDevice(self, topic): split_topic = topic.split('/') possible_topic = split_topic[1] if possible_topic in ('tele', 'stat'): possible_topic = split_topic[0] for i, d in enumerate(self._devices): match = match_topic(d[DevMdl.FULL_TOPIC], topic) if match: found = match.groupdict() if found['topic'] == d[DevMdl.TOPIC]: found.update({'index': self.index(i, DevMdl.LWT)}) return found_obj(found) return found_obj({ 'index': QModelIndex(), 'topic': possible_topic, 'reply': split_topic[-1] }) def columnCount(self, parent=None): return len(columns) def rowCount(self, parent=None): return len(self._devices) def insertRows(self, pos, rows, parent=QModelIndex()): self.beginInsertRows(parent, pos, pos + rows - 1) for i in range(rows): self._devices.append(['undefined'] + ([''] * (len(columns) - 1))) self.endInsertRows() return True def removeRows(self, pos, rows, parent=QModelIndex()): if pos + rows <= self.rowCount(): self.beginRemoveRows(parent, pos, pos + rows - 1) for r in range(rows): d = self._devices[pos][DevMdl.TOPIC] self.settings.beginGroup("Devices") if d in self.settings.childGroups(): self.settings.remove(d) self.settings.endGroup() self._devices.pop(pos + r) self.endRemoveRows() return True return False def headerData(self, col, orientation, role=Qt.DisplayRole): if orientation == Qt.Horizontal and role == Qt.DisplayRole: if col <= len(columns): return columns[col][0] else: return '' def data(self, idx, role=Qt.DisplayRole): if idx.isValid(): row = idx.row() col = idx.column() if role in (Qt.DisplayRole, Qt.EditRole): val = self._devices[row][col] if val and col == DevMdl.UPTIME: if val.startswith("0T"): val = val.replace('0T', '') return val.replace('T', 'd ') elif val and col == DevMdl.MODULE: return modules.get(val, 'Unknown') elif val and col == DevMdl.FIRMWARE: return val.replace('(', ' (') elif col == DevMdl.LOADAVG: if val: return val return "n/a" if self._devices[row][ DevMdl.LWT] == 'online' else '' elif col == DevMdl.BSSID: alias = self.settings.value("BSSID/{}".format(val)) if alias: return alias return self._devices[row][col] elif role == Qt.TextAlignmentRole: if col in (DevMdl.RSSI, DevMdl.MAC, DevMdl.IP, DevMdl.SSID, DevMdl.BSSID, DevMdl.CHANNEL, DevMdl.POWER, DevMdl.LOADAVG, DevMdl.CORE, DevMdl.TELEPERIOD): return Qt.AlignCenter elif col == DevMdl.UPTIME: return Qt.AlignRight | Qt.AlignVCenter elif col == DevMdl.RESTART_REASON: return Qt.AlignLeft | Qt.AlignVCenter | Qt.TextWordWrap elif role == Qt.BackgroundColorRole and col == DevMdl.RSSI: rssi = self._devices[row][DevMdl.RSSI] if rssi: rssi = int(rssi) if rssi < 50: return QColor("#ef4522") elif rssi > 75: return QColor("#7eca27") else: return QColor("#fcdd0f") elif role == Qt.ToolTipRole: if col == DevMdl.FIRMWARE: return self._devices[row][DevMdl.FIRMWARE] elif col == DevMdl.FRIENDLY_NAME: return "Topic: {}\nFull topic: {}".format( self._devices[row][DevMdl.TOPIC], self._devices[row][DevMdl.FULL_TOPIC]) def setData(self, idx, val, role=Qt.EditRole): row = idx.row() col = idx.column() if role == Qt.EditRole: dev = self._devices[row][DevMdl.TOPIC] old_val = self._devices[row][col] if val != old_val: self.settings.beginGroup("Devices") if col == DevMdl.FRIENDLY_NAME: self.settings.setValue("{}/friendly_name".format(dev), val) elif col == DevMdl.FULL_TOPIC: self.settings.setValue("{}/full_topic".format(dev), val) self.settings.endGroup() self._devices[row][col] = val self.dataChanged.emit(idx, idx) self.settings.sync() return True return False def flags(self, idx): return Qt.ItemIsSelectable | Qt.ItemIsEnabled def updateValue(self, idx, column, val): if idx.isValid(): row = idx.row() idx = self.index(row, column) self.setData(idx, val) def topic(self, idx): if idx and idx.isValid(): row = idx.row() return self._devices[row][DevMdl.TOPIC] return None def fullTopic(self, idx): if idx and idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FULL_TOPIC] return None def module(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.MODULE] return None def firmware(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FIRMWARE] return None def core(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.CORE] return None def friendly_name(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FRIENDLY_NAME] return None def commandTopic(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FULL_TOPIC].replace( "%prefix%", "cmnd").replace("%topic%", self._devices[row][DevMdl.TOPIC]) return None def statTopic(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FULL_TOPIC].replace( "%prefix%", "stat").replace("%topic%", self._devices[row][DevMdl.TOPIC]) return None def teleTopic(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.FULL_TOPIC].replace( "%prefix%", "tele").replace("%topic%", self._devices[row][DevMdl.TOPIC]) return None def isDefaultTemplate(self, idx): if idx.isValid(): return self._devices[idx.row()][DevMdl.FULL_TOPIC] in [ "%prefix%/%topic%/", "%topic%/%prefix%/" ] def bssid(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.BSSID] return None def power(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.POWER] return None def ip(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.IP] return None def mac(self, idx): if idx.isValid(): row = idx.row() return self._devices[row][DevMdl.MAC] return None def refreshBSSID(self): first = self.index(0, DevMdl.BSSID) last = self.index(self.rowCount(), DevMdl.BSSID) self.dataChanged.emit(first, last)
def __init__(self, parent=None): super(Client, self).__init__(parent) self.networkSession = None self.blockSize = 0 self.currentFortune = '' hostLabel = QLabel("&Server name:") portLabel = QLabel("S&erver port:") self.hostCombo = QComboBox() self.hostCombo.setEditable(True) name = QHostInfo.localHostName() if name != '': self.hostCombo.addItem(name) domain = QHostInfo.localDomainName() if domain != '': self.hostCombo.addItem(name + '.' + domain) if name != 'localhost': self.hostCombo.addItem('localhost') ipAddressesList = QNetworkInterface.allAddresses() for ipAddress in ipAddressesList: if not ipAddress.isLoopback(): self.hostCombo.addItem(ipAddress.toString()) for ipAddress in ipAddressesList: if ipAddress.isLoopback(): self.hostCombo.addItem(ipAddress.toString()) self.portLineEdit = QLineEdit() self.portLineEdit.setValidator(QIntValidator(1, 65535, self)) hostLabel.setBuddy(self.hostCombo) portLabel.setBuddy(self.portLineEdit) self.statusLabel = QLabel("This examples requires that you run " "the Fortune Server example as well.") self.getFortuneButton = QPushButton("Get Fortune") self.getFortuneButton.setDefault(True) self.getFortuneButton.setEnabled(False) quitButton = QPushButton("Quit") buttonBox = QDialogButtonBox() buttonBox.addButton(self.getFortuneButton, QDialogButtonBox.ActionRole) buttonBox.addButton(quitButton, QDialogButtonBox.RejectRole) self.tcpSocket = QTcpSocket(self) self.hostCombo.editTextChanged.connect(self.enableGetFortuneButton) self.portLineEdit.textChanged.connect(self.enableGetFortuneButton) self.getFortuneButton.clicked.connect(self.requestNewFortune) quitButton.clicked.connect(self.close) self.tcpSocket.readyRead.connect(self.readFortune) self.tcpSocket.error.connect(self.displayError) mainLayout = QGridLayout() mainLayout.addWidget(hostLabel, 0, 0) mainLayout.addWidget(self.hostCombo, 0, 1) mainLayout.addWidget(portLabel, 1, 0) mainLayout.addWidget(self.portLineEdit, 1, 1) mainLayout.addWidget(self.statusLabel, 2, 0, 1, 2) mainLayout.addWidget(buttonBox, 3, 0, 1, 2) self.setLayout(mainLayout) self.setWindowTitle("Fortune Client") self.portLineEdit.setFocus() manager = QNetworkConfigurationManager() if manager.capabilities( ) & QNetworkConfigurationManager.NetworkSessionRequired: settings = QSettings(QSettings.UserScope, 'QtProject') settings.beginGroup('QtNetwork') id = settings.value('DefaultNetworkConfiguration') settings.endGroup() config = manager.configurationFromIdentifier(id) if config.state() & QNetworkConfiguration.Discovered == 0: config = manager.defaultConfiguration() self.networkSession = QNetworkSession(config, self) self.networkSession.opened.connect(self.sessionOpened) self.getFortuneButton.setEnabled(False) self.statusLabel.setText("Opening network session.") self.networkSession.open()
def __init__(self, app_handle = None): super().__init__() self.app_handle = app_handle if os.name == 'posix': pyecog_string = '🇵 🇾 🇪 🇨 🇴 🇬' else: pyecog_string = 'PyEcog' print('\n',pyecog_string,'\n') # Initialize Main Window geometry # self.title = "ℙ𝕪𝔼𝕔𝕠𝕘" self.title = pyecog_string (size, rect) = self.get_available_screen() icon_file = pkg_resources.resource_filename('pyecog2', 'icons/icon.png') print('ICON:', icon_file) self.setWindowIcon(QtGui.QIcon(icon_file)) self.app_handle.setWindowIcon(QtGui.QIcon(icon_file)) self.setWindowTitle(self.title) self.setGeometry(0, 0, size.width(), size.height()) # self.setWindowFlags(QtCore.Qt.FramelessWindowHint) # fooling around # self.setWindowFlags(QtCore.Qt.CustomizeWindowHint| QtCore.Qt.Tool) # self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.main_model = MainModel() self.autosave_timer = QtCore.QTimer() self.live_recording_timer = QtCore.QTimer() # Populate Main window with widgets # self.createDockWidget() self.dock_list = {} self.paired_graphics_view = PairedGraphicsView(parent=self) self.tree_element = FileTreeElement(parent=self) self.main_model.sigProjectChanged.connect(lambda: self.tree_element.set_rootnode_from_project(self.main_model.project)) self.dock_list['File Tree'] = QDockWidget("File Tree", self) self.dock_list['File Tree'].setWidget(self.tree_element.widget) self.dock_list['File Tree'].setFloating(False) self.dock_list['File Tree'].setObjectName("File Tree") self.dock_list['File Tree'].setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['File Tree'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.plot_controls = PlotControls(self.main_model) self.plot_controls.sigUpdateXrange_i.connect(self.paired_graphics_view.insetview_set_xrange) self.plot_controls.sigUpdateXrange_o.connect(self.paired_graphics_view.overview_set_xrange) self.plot_controls.sigUpdateFilter.connect(self.paired_graphics_view.updateFilterSettings) self.dock_list['Plot Controls'] = QDockWidget("Plot controls", self) self.dock_list['Plot Controls'].setWidget(self.plot_controls) self.dock_list['Plot Controls'].setFloating(False) self.dock_list['Plot Controls'].setObjectName("Plot Controls") self.dock_list['Plot Controls'].setAllowedAreas( Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['Plot Controls'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Hints'] = QDockWidget("Hints", self) self.text_edit = QTextBrowser() hints_file = pkg_resources.resource_filename('pyecog2', 'HelperHints.md') # text = open('HelperHints.md').read() print('hints file:',hints_file) text = open(hints_file).read() text = text.replace('icons/banner_small.png',pkg_resources.resource_filename('pyecog2', 'icons/banner_small.png')) self.text_edit.setMarkdown(text) self.dock_list['Hints'].setWidget(self.text_edit) self.dock_list['Hints'].setObjectName("Hints") # self.dock_list['Hints'].setFloating(False) # self.dock_list['Hints'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.annotation_table = AnnotationTableWidget(self.main_model.annotations,self) # passing self as parent in position 2 self.dock_list['Annotations Table'] = QDockWidget("Annotations Table", self) self.dock_list['Annotations Table'].setWidget(self.annotation_table) self.dock_list['Annotations Table'].setObjectName("Annotations Table") self.dock_list['Annotations Table'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.annotation_parameter_tree = AnnotationParameterTee(self.main_model.annotations) self.dock_list['Annotation Parameter Tree'] = QDockWidget("Annotation Parameter Tree", self) self.dock_list['Annotation Parameter Tree'].setWidget(self.annotation_parameter_tree) self.dock_list['Annotation Parameter Tree'].setObjectName("Annotation Parameter Tree") self.dock_list['Annotation Parameter Tree'].setFeatures(QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.video_element = VideoWindow(project=self.main_model.project) self.dock_list['Video'] = QDockWidget("Video", self) self.dock_list['Video'].setWidget(self.video_element) self.dock_list['Video'].setObjectName("Video") # self.dock_list['Video'].setFloating(True) # self.dock_list['Video'].hide() self.video_element.mediaPlayer.setNotifyInterval(40) # 25 fps # Video units are in miliseconds, pyecog units are in seconds self.video_element.sigTimeChanged.connect(self.main_model.set_time_position) self.main_model.sigTimeChanged.connect(self.video_element.setGlobalPosition) self.dock_list['FFT'] = QDockWidget("FFT", self) self.dock_list['FFT'].setWidget(FFTwindow(self.main_model)) self.dock_list['FFT'].setObjectName("FFT") # self.dock_list['FFT'].hide() self.dock_list['Wavelet'] = QDockWidget("Wavelet", self) self.dock_list['Wavelet'].setWidget(WaveletWindow(self.main_model)) self.dock_list['Wavelet'].setObjectName("Wavelet") # self.dock_list['Wavelet'].hide() self.dock_list['Console'] = QDockWidget("Console", self) self.dock_list['Console'].setWidget(ConsoleWidget(namespace={'MainWindow':self})) self.dock_list['Console'].setObjectName("Console") self.dock_list['Console'].hide() self.build_menubar() self.setCentralWidget(self.paired_graphics_view.splitter) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Console']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['File Tree']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Hints']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Plot Controls']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Video']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotation Parameter Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotations Table']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['FFT']) self.setCorner(Qt.BottomLeftCorner,Qt.LeftDockWidgetArea) self.setCorner(Qt.BottomRightCorner,Qt.RightDockWidgetArea) self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_list['Wavelet']) # self.tabifyDockWidget(self.dock_list['Hints'], self.dock_list['File Tree']) self.resizeDocks([self.dock_list['File Tree'], self.dock_list['Hints'], self.dock_list['Plot Controls'],self.dock_list['Video']],[350,100,100,300],Qt.Vertical) self.resizeDocks([self.dock_list['Wavelet']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['Video']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['Video']],[400],Qt.Horizontal) self.resizeDocks([self.dock_list['FFT']],[400],Qt.Vertical) self.resizeDocks([self.dock_list['FFT']],[400],Qt.Horizontal) settings = QSettings("PyEcog","PyEcog") settings.beginGroup("StandardMainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.setValue("darkMode", False) settings.setValue("autoSave", True) settings.endGroup() self.settings = QSettings("PyEcog", "PyEcog") print("reading configurations from: " + self.settings.fileName()) self.settings.beginGroup("MainWindow") # print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry(self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) self.action_darkmode.setChecked(self.settings.value("darkMode",type=bool)) self.toggle_darkmode() # pre toggle darkmode to make sure project loading dialogs are made with the correct color pallete try: settings = QSettings("PyEcog","PyEcog") settings.beginGroup("ProjectSettings") fname = settings.value("ProjectFileName") self.show() if fname.endswith('.pyecog'): print('Loading last opened Project:', fname) self.load_project(fname) else: print('Loading last opened directory:', fname) self.load_directory(fname) except Exception as e: print('ERROR in tree build') print(e) self.action_darkmode.setChecked(self.settings.value("darkMode",type=bool)) self.toggle_darkmode() # toggle again darkmode just to make sure the wavelet window and FFT are updated as well self.action_autosave.setChecked(self.settings.value("autoSave",type=bool)) self.toggle_auto_save()
def create(self): """Go ahead and create the relevant files. """ if self.ui.e_name_of_script.text().strip() == "": # Don't create script with empty name return def full_dir(path): # convenience function return os.path.join(self.target_directory, path) # should already be done, but just in case: self.calculate_file_names(self.ui.e_name_of_script.text()) menu_entry = self.ui.e_menu_entry.text() if menu_entry.strip() == "": menu_entry = self.ui.e_name_of_script.text() comment = self.ui.e_comment.text() if comment.strip() == "": comment = "Replace this text with your description" values = { SCRIPT_NAME: self.ui.e_name_of_script.text(), SCRIPT_COMMENT: comment, KRITA_ID: "pykrita_" + self.package_name, SCRIPT_TYPE: SCRIPT_DOCKER if self.ui.rb_docker.isChecked() else SCRIPT_EXTENSION, # noqa: E501 MENU_ENTRY: menu_entry, LIBRARY_NAME: self.package_name, CLASS_NAME: self.class_name } try: # create package directory package_directory = full_dir(self.package_name) # needs to be lowercase and no spaces os.mkdir(package_directory) except FileExistsError: # if package directory exists write into it, overwriting # existing files. pass # create desktop file fn = full_dir(self.desktop_fn) with open(fn, 'w+t') as f: f.write(DESKTOP_TEMPLATE.format(**values)) fn = full_dir(self.init_name) with open(fn, 'w+t') as f: f.write(INIT_TEMPLATE.format(**values)) # create main package file fn = full_dir(self.package_file) if self.ui.rb_docker.isChecked(): with open(fn, 'w+t') as f: f.write(DOCKER_TEMPLATE.format(**values)) else: # Default to extension type with open(fn, 'w+t') as f: f.write(EXTENSION_TEMPLATE.format(**values)) # create manual file. fn = full_dir(self.manual_file) with open(fn, 'w+t') as f: f.write(MANUAL_TEMPLATE.format(**values)) # enable script in krita settings (!) if self.ui.cb_enable_script.isChecked(): configPath = QStandardPaths.writableLocation( QStandardPaths.GenericConfigLocation) configPath = os.path.join(configPath, 'kritarc') settings = QSettings(configPath, QSettings.IniFormat) settings.beginGroup(SCRIPT_SETTINGS) settings.setValue('enable_' + self.package_name, 'true') settings.endGroup() settings.sync() # notify success # Assemble message title = "Krita Script files created" message = [] message.append("<h3>Directory</h3>") message.append("Project files were created in the directory<p>%s" % self.target_directory) message.append( "<h3>Files Created</h3>The following files were created:<p>") for f in self.files: message.append("%s<p>" % f) message.append("%s<p>" % self.manual_file) message.append("<h3>Location of script</h3>") message.append("Open this file to edit your script:<p>") script_path = os.path.join(self.target_directory, self.package_file) message.append("%s<p>" % script_path) message.append("Open this file to edit your Manual:<p>") script_path = os.path.join(self.target_directory, self.manual_file) message.append("%s<p>" % script_path) message.append("<h3>Record these locations</h3>") message.append( "Make a note of these locations before you click ok.<p>") message = "\n".join(message) # Display message box if CONTEXT_KRITA: msgbox = QMessageBox() else: msgbox = QMessageBox(self) msgbox.setWindowTitle(title) msgbox.setText(message) msgbox.setStandardButtons(QMessageBox.Ok) msgbox.setDefaultButton(QMessageBox.Ok) msgbox.setIcon(QMessageBox.Information) msgbox.exec() self.ui.close()
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)
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)
def __init__(self): super().__init__() # Initialize Main Window geometry self.title = "PyEcog Main" (size, rect) = self.get_available_screen() self.setWindowIcon(QtGui.QIcon("icon.png")) self.setWindowTitle(self.title) self.setGeometry(0, 0, size.width(), size.height()) self.main_model = MainModel() # Populate Main window with widgets # self.createDockWidget() self.build_menubar() self.dock_list = {} self.paired_graphics_view = PairedGraphicsView(parent=self) self.tree_element = FileTreeElement(parent=self) self.dock_list['File Tree'] = QDockWidget("File Tree", self) self.dock_list['File Tree'].setWidget(self.tree_element.widget) self.dock_list['File Tree'].setFloating(False) self.dock_list['File Tree'].setObjectName("File Tree") self.dock_list['File Tree'].setAllowedAreas(Qt.RightDockWidgetArea | Qt.LeftDockWidgetArea | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) self.dock_list['File Tree'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Text'] = QDockWidget("Text", self) self.dock_list['Text'].setWidget(QPlainTextEdit()) self.dock_list['Text'].setObjectName("Text") self.annotation_table = AnnotationTableWidget( self.main_model.annotations) self.dock_list['Annotations Table'] = QDockWidget( "Annotations Table", self) self.dock_list['Annotations Table'].setWidget(self.annotation_table) self.dock_list['Annotations Table'].setObjectName("Annotations Table") self.dock_list['Annotations Table'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.dock_list['Annotation Parameter Tree'] = QDockWidget( "Annotation Parameter Tree", self) self.dock_list['Annotation Parameter Tree'].setWidget( AnnotationParameterTee(self.main_model.annotations)) self.dock_list['Annotation Parameter Tree'].setObjectName( "Annotation Parameter Tree") self.dock_list['Annotation Parameter Tree'].setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) self.video_element = VideoWindow(project=self.main_model.project) self.dock_list['Video'] = QDockWidget("Video", self) self.dock_list['Video'].setWidget(self.video_element) self.dock_list['Video'].setObjectName("Video") self.dock_list['Video'].setFloating(True) self.dock_list['Video'].hide() self.video_element.mediaPlayer.setNotifyInterval(40) # 25 fps # Video units are in miliseconds, pyecog units are in seconds self.video_element.sigTimeChanged.connect( self.main_model.set_time_position) self.main_model.sigTimeChanged.connect( self.video_element.setGlobalPosition) self.dock_list['FFT'] = QDockWidget("FFT", self) self.dock_list['FFT'].setWidget(FFTwindow(self.main_model)) self.dock_list['FFT'].setObjectName("FFT") self.dock_list['FFT'].hide() self.dock_list['Wavelet'] = QDockWidget("Wavelet", self) self.dock_list['Wavelet'].setWidget(WaveletWindow(self.main_model)) self.dock_list['Wavelet'].setObjectName("Wavelet") self.dock_list['Wavelet'].hide() self.setCentralWidget(self.paired_graphics_view.splitter) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['File Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotation Parameter Tree']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Annotations Table']) self.addDockWidget(Qt.RightDockWidgetArea, self.dock_list['Text']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['FFT']) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_list['Wavelet']) settings = QSettings("PyEcog", "PyEcog") settings.beginGroup("StandardMainWindow") settings.setValue("windowGeometry", self.saveGeometry()) settings.setValue("windowState", self.saveState()) settings.endGroup() self.settings = QSettings("PyEcog", "PyEcog") print("reading configurations from: " + self.settings.fileName()) self.settings.beginGroup("MainWindow") # print(self.settings.value("windowGeometry", type=QByteArray)) self.restoreGeometry( self.settings.value("windowGeometry", type=QByteArray)) self.restoreState(self.settings.value("windowState", type=QByteArray)) try: settings = QSettings("PyEcog", "PyEcog") settings.beginGroup("ProjectSettings") fname = settings.value("ProjectFileName") print('Loading Project:', fname) self.show() self.load_project(fname) # self.main_model.project.load_from_json(fname) # self.main_model.project.load_from_json('/home/mfpleite/Shared/ele_data/proj.pyecog') # print(self.main_model.project.__dict__) self.tree_element.set_rootnode_from_project( self.main_model.project) except Exception as e: print('ERROR in tree build') print(e)
class ListWidget(QWidget): deviceSelected = pyqtSignal(TasmotaDevice) openRulesEditor = pyqtSignal() openConsole = pyqtSignal() openTelemetry = pyqtSignal() openWebUI = pyqtSignal() def __init__(self, parent, *args, **kwargs): super(ListWidget, self).__init__(*args, **kwargs) self.setWindowTitle("Devices list") self.setWindowState(Qt.WindowMaximized) self.setLayout(VLayout(margin=0, spacing=0)) self.mqtt = parent.mqtt self.env = parent.env self.device = None self.idx = None self.nam = QNetworkAccessManager() self.backup = bytes() self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat) views_order = self.settings.value("views_order", []) self.views = {} self.settings.beginGroup("Views") views = self.settings.childKeys() if views and views_order: for view in views_order.split(";"): view_list = self.settings.value(view).split(";") self.views[view] = base_view + view_list else: self.views = default_views self.settings.endGroup() self.tb = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.tb_relays = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonIconOnly) # self.tb_filter = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.tb_views = Toolbar(Qt.Horizontal, 24, Qt.ToolButtonTextBesideIcon) self.pwm_sliders = [] self.layout().addWidget(self.tb) self.layout().addWidget(self.tb_relays) # self.layout().addWidget(self.tb_filter) self.device_list = TableView() self.device_list.setIconSize(QSize(24, 24)) self.model = parent.device_model self.model.setupColumns(self.views["Home"]) self.sorted_device_model = QSortFilterProxyModel() self.sorted_device_model.setFilterCaseSensitivity(Qt.CaseInsensitive) self.sorted_device_model.setSourceModel(parent.device_model) self.sorted_device_model.setSortRole(Qt.InitialSortOrderRole) self.sorted_device_model.setSortLocaleAware(True) self.sorted_device_model.setFilterKeyColumn(-1) self.device_list.setModel(self.sorted_device_model) self.device_list.setupView(self.views["Home"]) self.device_list.setSortingEnabled(True) self.device_list.setWordWrap(True) self.device_list.setItemDelegate(DeviceDelegate()) self.device_list.sortByColumn(self.model.columnIndex("FriendlyName"), Qt.AscendingOrder) self.device_list.setContextMenuPolicy(Qt.CustomContextMenu) self.device_list.verticalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.layout().addWidget(self.device_list) self.layout().addWidget(self.tb_views) self.device_list.clicked.connect(self.select_device) self.device_list.customContextMenuRequested.connect( self.show_list_ctx_menu) self.ctx_menu = QMenu() self.create_actions() self.create_view_buttons() # self.create_view_filter() self.device_list.doubleClicked.connect(lambda: self.openConsole.emit()) def create_actions(self): actConsole = self.tb.addAction(QIcon(":/console.png"), "Console", self.openConsole.emit) actConsole.setShortcut("Ctrl+E") actRules = self.tb.addAction(QIcon(":/rules.png"), "Rules", self.openRulesEditor.emit) actRules.setShortcut("Ctrl+R") actTimers = self.tb.addAction(QIcon(":/timers.png"), "Timers", self.configureTimers) actButtons = self.tb.addAction(QIcon(":/buttons.png"), "Buttons", self.configureButtons) actButtons.setShortcut("Ctrl+B") actSwitches = self.tb.addAction(QIcon(":/switches.png"), "Switches", self.configureSwitches) actSwitches.setShortcut("Ctrl+S") actPower = self.tb.addAction(QIcon(":/power.png"), "Power", self.configurePower) actPower.setShortcut("Ctrl+P") # setopts = self.tb.addAction(QIcon(":/setoptions.png"), "SetOptions", self.configureSO) # setopts.setShortcut("Ctrl+S") self.tb.addSpacer() actTelemetry = self.tb.addAction(QIcon(":/telemetry.png"), "Telemetry", self.openTelemetry.emit) actTelemetry.setShortcut("Ctrl+T") actWebui = self.tb.addAction(QIcon(":/web.png"), "WebUI", self.openWebUI.emit) actWebui.setShortcut("Ctrl+U") self.ctx_menu.addActions([ actRules, actTimers, actButtons, actSwitches, actPower, actTelemetry, actWebui ]) self.ctx_menu.addSeparator() self.ctx_menu_cfg = QMenu("Configure") self.ctx_menu_cfg.setIcon(QIcon(":/settings.png")) self.ctx_menu_cfg.addAction("Module", self.configureModule) self.ctx_menu_cfg.addAction("GPIO", self.configureGPIO) self.ctx_menu_cfg.addAction("Template", self.configureTemplate) # self.ctx_menu_cfg.addAction("Wifi", self.ctx_menu_teleperiod) # self.ctx_menu_cfg.addAction("Time", self.cfgTime.emit) # self.ctx_menu_cfg.addAction("MQTT", self.ctx_menu_teleperiod) # self.ctx_menu_cfg.addAction("Logging", self.ctx_menu_teleperiod) self.ctx_menu.addMenu(self.ctx_menu_cfg) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon(":/refresh.png"), "Refresh", self.ctx_menu_refresh) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon(":/clear.png"), "Clear retained", self.ctx_menu_clear_retained) self.ctx_menu.addAction("Clear Backlog", self.ctx_menu_clear_backlog) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon(":/copy.png"), "Copy", self.ctx_menu_copy) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon(":/restart.png"), "Restart", self.ctx_menu_restart) self.ctx_menu.addAction(QIcon(), "Reset", self.ctx_menu_reset) self.ctx_menu.addSeparator() self.ctx_menu.addAction(QIcon(":/delete.png"), "Delete", self.ctx_menu_delete_device) # self.tb.addAction(QIcon(), "Multi Command", self.ctx_menu_webui) self.agAllPower = QActionGroup(self) self.agAllPower.addAction(QIcon(":/P_ON.png"), "All ON") self.agAllPower.addAction(QIcon(":/P_OFF.png"), "All OFF") self.agAllPower.setEnabled(False) self.agAllPower.setExclusive(False) self.agAllPower.triggered.connect(self.toggle_power_all) self.tb_relays.addActions(self.agAllPower.actions()) self.agRelays = QActionGroup(self) self.agRelays.setVisible(False) self.agRelays.setExclusive(False) for a in range(1, 9): act = QAction(QIcon(":/P{}_OFF.png".format(a)), "") act.setShortcut("F{}".format(a)) self.agRelays.addAction(act) self.agRelays.triggered.connect(self.toggle_power) self.tb_relays.addActions(self.agRelays.actions()) self.tb_relays.addSeparator() self.actColor = self.tb_relays.addAction(QIcon(":/color.png"), "Color", self.set_color) self.actColor.setEnabled(False) self.actChannels = self.tb_relays.addAction(QIcon(":/sliders.png"), "Channels") self.actChannels.setEnabled(False) self.mChannels = QMenu() self.actChannels.setMenu(self.mChannels) self.tb_relays.widgetForAction(self.actChannels).setPopupMode( QToolButton.InstantPopup) def create_view_buttons(self): self.tb_views.addWidget(QLabel("View mode: ")) ag_views = QActionGroup(self) ag_views.setExclusive(True) for v in self.views.keys(): a = QAction(v) a.triggered.connect(self.change_view) a.setCheckable(True) ag_views.addAction(a) self.tb_views.addActions(ag_views.actions()) ag_views.actions()[0].setChecked(True) stretch = QWidget() stretch.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)) self.tb_views.addWidget(stretch) # actEditView = self.tb_views.addAction("Edit views...") # def create_view_filter(self): # # self.tb_filter.addWidget(QLabel("Show devices: ")) # # self.cbxLWT = QComboBox() # # self.cbxLWT.addItems(["All", "Online"d, "Offline"]) # # self.cbxLWT.currentTextChanged.connect(self.build_filter_regex) # # self.tb_filter.addWidget(self.cbxLWT) # # self.tb_filter.addWidget(QLabel(" Search: ")) # self.leSearch = QLineEdit() # self.leSearch.setClearButtonEnabled(True) # self.leSearch.textChanged.connect(self.build_filter_regex) # self.tb_filter.addWidget(self.leSearch) # # def build_filter_regex(self, txt): # query = self.leSearch.text() # # if self.cbxLWT.currentText() != "All": # # query = "{}|{}".format(self.cbxLWT.currentText(), query) # self.sorted_device_model.setFilterRegExp(query) def change_view(self, a=None): view = self.views[self.sender().text()] self.model.setupColumns(view) self.device_list.setupView(view) def ctx_menu_copy(self): if self.idx: string = dumps(self.model.data(self.idx)) if string.startswith('"') and string.endswith('"'): string = string[1:-1] QApplication.clipboard().setText(string) def ctx_menu_clear_retained(self): if self.device: relays = self.device.power() if relays and len(relays.keys()) > 0: for r in relays.keys(): self.mqtt.publish(self.device.cmnd_topic(r), retain=True) QMessageBox.information(self, "Clear retained", "Cleared retained messages.") def ctx_menu_clear_backlog(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("backlog"), "") QMessageBox.information(self, "Clear Backlog", "Backlog cleared.") def ctx_menu_restart(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("restart"), payload="1") for k in list(self.device.power().keys()): self.device.p.pop(k) def ctx_menu_reset(self): if self.device: reset, ok = QInputDialog.getItem(self, "Reset device and restart", "Select reset mode", resets, editable=False) if ok: self.mqtt.publish(self.device.cmnd_topic("reset"), payload=reset.split(":")[0]) for k in list(self.device.power().keys()): self.device.p.pop(k) def ctx_menu_refresh(self): if self.device: for k in list(self.device.power().keys()): self.device.p.pop(k) for c in initial_commands(): cmd, payload = c cmd = self.device.cmnd_topic(cmd) self.mqtt.publish(cmd, payload, 1) def ctx_menu_delete_device(self): if self.device: if QMessageBox.question( self, "Confirm", "Do you want to remove the following device?\n'{}' ({})". format(self.device.p['FriendlyName1'], self.device.p['Topic'])) == QMessageBox.Yes: self.model.deleteDevice(self.idx) def ctx_menu_teleperiod(self): if self.device: teleperiod, ok = QInputDialog.getInt( self, "Set telemetry period", "Input 1 to reset to default\n[Min: 10, Max: 3600]", self.device.p['TelePeriod'], 1, 3600) if ok: if teleperiod != 1 and teleperiod < 10: teleperiod = 10 self.mqtt.publish(self.device.cmnd_topic("teleperiod"), teleperiod) def ctx_menu_config_backup(self): if self.device: self.backup = bytes() self.dl = self.nam.get( QNetworkRequest( QUrl("http://{}/dl".format(self.device.p['IPAddress'])))) self.dl.readyRead.connect(self.get_dump) self.dl.finished.connect(self.save_dump) def ctx_menu_ota_set_url(self): if self.device: url, ok = QInputDialog.getText( self, "Set OTA URL", '100 chars max. Set to "1" to reset to default.', text=self.device.p['OtaUrl']) if ok: self.mqtt.publish(self.device.cmnd_topic("otaurl"), payload=url) def ctx_menu_ota_set_upgrade(self): if self.device: if QMessageBox.question( self, "OTA Upgrade", "Are you sure to OTA upgrade from\n{}".format( self.device.p['OtaUrl']), QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: self.mqtt.publish(self.device.cmnd_topic("upgrade"), payload="1") def show_list_ctx_menu(self, at): self.select_device(self.device_list.indexAt(at)) self.ctx_menu.popup(self.device_list.viewport().mapToGlobal(at)) def select_device(self, idx): self.idx = self.sorted_device_model.mapToSource(idx) self.device = self.model.deviceAtRow(self.idx.row()) self.deviceSelected.emit(self.device) relays = self.device.power() self.agAllPower.setEnabled(len(relays) >= 1) for i, a in enumerate(self.agRelays.actions()): a.setVisible(len(relays) > 1 and i < len(relays)) color = self.device.color().get("Color", False) has_color = bool(color) self.actColor.setEnabled(has_color and not self.device.setoption(68)) self.actChannels.setEnabled(has_color) if has_color: self.actChannels.menu().clear() max_val = 100 if self.device.setoption(15) == 0: max_val = 1023 for k, v in self.device.pwm().items(): channel = SliderAction(self, k) channel.slider.setMaximum(max_val) channel.slider.setValue(int(v)) self.mChannels.addAction(channel) channel.slider.valueChanged.connect(self.set_channel) dimmer = self.device.color().get("Dimmer") if dimmer: saDimmer = SliderAction(self, "Dimmer") saDimmer.slider.setValue(int(dimmer)) self.mChannels.addAction(saDimmer) saDimmer.slider.valueChanged.connect(self.set_channel) def toggle_power(self, action): if self.device: idx = self.agRelays.actions().index(action) relay = sorted(list(self.device.power().keys()))[idx] self.mqtt.publish(self.device.cmnd_topic(relay), "toggle") def toggle_power_all(self, action): if self.device: idx = self.agAllPower.actions().index(action) for r in sorted(self.device.power().keys()): self.mqtt.publish(self.device.cmnd_topic(r), idx ^ 1) def set_color(self): if self.device: color = self.device.color().get("Color") if color: dlg = QColorDialog() new_color = dlg.getColor(QColor("#{}".format(color))) if new_color.isValid(): new_color = new_color.name() if new_color != color: self.mqtt.publish(self.device.cmnd_topic("color"), new_color) def set_channel(self, value=0): cmd = self.sender().objectName() if self.device: self.mqtt.publish(self.device.cmnd_topic(cmd), str(value)) def configureSO(self): if self.device: dlg = SetOptionsDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureModule(self): if self.device: dlg = ModuleDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureGPIO(self): if self.device: dlg = GPIODialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureTemplate(self): if self.device: dlg = TemplateDialog(self.device) dlg.sendCommand.connect(self.mqtt.publish) dlg.exec_() def configureTimers(self): if self.device: self.mqtt.publish(self.device.cmnd_topic("timers")) timers = TimersDialog(self.device) self.mqtt.messageSignal.connect(timers.parseMessage) timers.sendCommand.connect(self.mqtt.publish) timers.exec_() def configureButtons(self): if self.device: backlog = [] buttons = ButtonsDialog(self.device) if buttons.exec_() == QDialog.Accepted: for c, cw in buttons.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) so_error = False for so, sow in buttons.setoption_widgets.items(): current_value = None try: current_value = self.device.setoption(so) except ValueError: so_error = True new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if not so_error and current_value and current_value != new_value: backlog.append("SetOption{} {}".format(so, new_value)) if backlog: backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def configureSwitches(self): if self.device: backlog = [] switches = SwitchesDialog(self.device) if switches.exec_() == QDialog.Accepted: for c, cw in switches.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) so_error = False for so, sow in switches.setoption_widgets.items(): current_value = None try: current_value = self.device.setoption(so) except ValueError: so_error = True new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if not so_error and current_value != new_value: backlog.append("SetOption{} {}".format(so, new_value)) for sw, sw_mode in enumerate(self.device.p['SwitchMode']): new_value = switches.sm.inputs[sw].currentIndex() if sw_mode != new_value: backlog.append("switchmode{} {}".format( sw + 1, new_value)) if backlog: backlog.append("status") backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def configurePower(self): if self.device: backlog = [] power = PowerDialog(self.device) if power.exec_() == QDialog.Accepted: for c, cw in power.command_widgets.items(): current_value = self.device.p.get(c) new_value = "" if isinstance(cw.input, SpinBox): new_value = cw.input.value() if isinstance(cw.input, QComboBox): new_value = cw.input.currentIndex() if current_value != new_value: backlog.append("{} {}".format(c, new_value)) so_error = False for so, sow in power.setoption_widgets.items(): current_value = None try: current_value = self.device.setoption(so) except ValueError: so_error = True new_value = -1 if isinstance(sow.input, SpinBox): new_value = sow.input.value() if isinstance(sow.input, QComboBox): new_value = sow.input.currentIndex() if not so_error and current_value != new_value: backlog.append("SetOption{} {}".format(so, new_value)) new_interlock_value = power.ci.input.currentData() new_interlock_grps = " ".join([ grp.text().replace(" ", "") for grp in power.ci.groups ]).rstrip() if new_interlock_value != self.device.p.get( "Interlock", "OFF"): backlog.append("interlock {}".format(new_interlock_value)) if new_interlock_grps != self.device.p.get("Groups", ""): backlog.append("interlock {}".format(new_interlock_grps)) for i, pt in enumerate(power.cpt.inputs): ptime = "PulseTime{}".format(i + 1) current_ptime = self.device.p.get(ptime) if current_ptime: current_value = list(current_ptime.keys())[0] new_value = str(pt.value()) if new_value != current_value: backlog.append("{} {}".format(ptime, new_value)) if backlog: backlog.append("status") backlog.append("status 3") self.mqtt.publish(self.device.cmnd_topic("backlog"), "; ".join(backlog)) def get_dump(self): self.backup += self.dl.readAll() def save_dump(self): fname = self.dl.header(QNetworkRequest.ContentDispositionHeader) if fname: fname = fname.split('=')[1] save_file = QFileDialog.getSaveFileName( self, "Save config backup", "{}/TDM/{}".format(QDir.homePath(), fname))[0] if save_file: with open(save_file, "wb") as f: f.write(self.backup) def check_fulltopic(self, fulltopic): fulltopic += "/" if not fulltopic.endswith('/') else '' return "%prefix%" in fulltopic and "%topic%" in fulltopic def closeEvent(self, event): event.ignore()
class Settings(): def __init__(self): self.__settings = QSettings("./settings.ini", QSettings.IniFormat) self.__settings.setIniCodec("UTF8") # 检查设置文件是否存在 if self.__settings.value("isInitialized", False) == False: self.__settings.setValue("isInitialized", True) self.initSettings() else: self.readSettings() def initSettings(self): self.__channels = "6132/ai0" self.__sampleFreq = 0 self.__sampleMode = "持续采样" self.__samplesPerChan = 0 self.__samplesPerFile = 0 self.__maxVal = 5.0 self.__minVal = -5.0 self.__coupling = "DC" self.__activeEdge = "上升沿" self.writeSettings() def readSettings(self): self.__settings.beginGroup("Settings") self.__channels = self.__settings.value("channels") self.__sampleFreq = self.__settings.value("sampleFreq") self.__sampleMode = self.__settings.value("sampleMode") self.__samplesPerChan = self.__settings.value("samplesPerChan") self.__samplesPerFile = self.__settings.value("samplesPerFile") self.__maxVal = self.__settings.value("maxVal") self.__minVal = self.__settings.value("minVal") self.__coupling = self.__settings.value("coupling") self.__activeEdge = self.__settings.value("activeEdge") self.__settings.endGroup() def writeSettings(self): self.__settings.beginGroup("Settings") self.__settings.setValue("channels", self.__channels) self.__settings.setValue("sampleFreq", self.__sampleFreq) self.__settings.setValue("sampleMode", self.__sampleMode) self.__settings.setValue("samplesPerChan", self.__samplesPerChan) self.__settings.setValue("samplesPerFile", self.__samplesPerFile) self.__settings.setValue("maxVal", self.__maxVal) self.__settings.setValue("minVal", self.__minVal) self.__settings.setValue("coupling", self.__coupling) self.__settings.setValue("activeEdge", self.__activeEdge) self.__settings.endGroup() self.__settings.sync() def readChannels(self): return self.__channels def readSampleFreq(self): return self.__sampleFreq def readSampleMode(self): return self.__sampleMode def readSamplesPerChan(self): return self.__samplesPerChan def readSamplesPerFile(self): return self.__samplesPerFile def readMaxVal(self): return self.__maxVal def readMinVal(self): return self.__minVal def readCoupling(self): return self.__coupling def readActiveEdge(self): return self.__activeEdge def writeChannels(self, channels): self.__channels = channels def writeSampleFreq(self, sampleFreq): self.__sampleFreq = sampleFreq def writeSampleMode(self, sampleMode): self.__sampleMode = sampleMode def writeSamplesPerChan(self, samplesPerChan): self.__samplesPerChan = samplesPerChan def writeSamplesPerFile(self, samplesPerFile): self.__samplesPerChan = samplesPerFile def writeMaxVal(self, maxVal): self.__maxVal = maxVal def writeMinVal(self, minVal): self.__minVal = minVal def writeCoupling(self, coupling): self.__coupling = coupling def writeActiveEdge(self, activeEdge): self.__activeEdge = activeEdge
class jide(QMainWindow): """This is the primary class which serves as the glue for JIDE. This class interfaces between the various canvases, pixel and color palettes, centralized data source, and data output routines. """ def __init__(self): """jide constructor """ super().__init__() self.setupWindow() self.setupTabs() self.setupDocks() self.setupToolbar() self.setupActions() self.setupStatusBar() self.setupPrefs() def setupWindow(self): """Entry point to set up primary window attributes """ self.setWindowTitle("JIDE") self.sprite_view = QGraphicsView() self.tile_view = QGraphicsView() self.sprite_view.setStyleSheet("background-color: #494949;") self.tile_view.setStyleSheet("background-color: #494949;") def setupDocks(self): """Set up pixel palette, color palette, and tile map docks """ self.sprite_color_palette_dock = ColorPaletteDock(Source.SPRITE, self) self.sprite_pixel_palette_dock = PixelPaletteDock(Source.SPRITE, self) self.tile_color_palette_dock = ColorPaletteDock(Source.TILE, self) self.tile_pixel_palette_dock = PixelPaletteDock(Source.TILE, self) self.addDockWidget(Qt.RightDockWidgetArea, self.sprite_color_palette_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.sprite_pixel_palette_dock) self.removeDockWidget(self.tile_color_palette_dock) self.removeDockWidget(self.tile_pixel_palette_dock) def setupToolbar(self): """Set up graphics tools toolbar """ self.canvas_toolbar = QToolBar() self.addToolBar(Qt.LeftToolBarArea, self.canvas_toolbar) self.tool_actions = QActionGroup(self) self.select_tool = QAction(QIcon(":/icons/select_tool.png"), "&Select tool", self.tool_actions) self.select_tool.setShortcut("S") self.pen_tool = QAction(QIcon(":/icons/pencil_tool.png"), "&Pen tool", self.tool_actions) self.pen_tool.setShortcut("P") self.fill_tool = QAction(QIcon(":/icons/fill_tool.png"), "&Fill tool", self.tool_actions) self.fill_tool.setShortcut("G") self.line_tool = QAction(QIcon(":/icons/line_tool.png"), "&Line tool", self.tool_actions) self.line_tool.setShortcut("L") self.rect_tool = QAction( QIcon(":/icons/rect_tool.png"), "&Rectangle tool", self.tool_actions, ) self.rect_tool.setShortcut("R") self.ellipse_tool = QAction( QIcon(":/icons/ellipse_tool.png"), "&Ellipse tool", self.tool_actions, ) self.ellipse_tool.setShortcut("E") self.tools = [ self.select_tool, self.pen_tool, self.fill_tool, self.line_tool, self.rect_tool, self.ellipse_tool, ] for tool in self.tools: tool.setCheckable(True) tool.setEnabled(False) self.canvas_toolbar.addAction(tool) def setupTabs(self): """Set up main window sprite/tile/tile map tabs """ self.canvas_tabs = QTabWidget() self.canvas_tabs.addTab(self.sprite_view, "Sprites") self.canvas_tabs.addTab(self.tile_view, "Tiles") self.canvas_tabs.setTabEnabled(0, False) self.canvas_tabs.setTabEnabled(1, False) self.setCentralWidget(self.canvas_tabs) def setupActions(self): """Set up main menu actions """ # Exit exit_act = QAction("&Exit", self) exit_act.setShortcut("Ctrl+Q") exit_act.setStatusTip("Exit application") exit_act.triggered.connect(qApp.quit) # Open file open_file = QAction("&Open", self) open_file.setShortcut("Ctrl+O") open_file.setStatusTip("Open file") open_file.triggered.connect(self.selectFile) # Open preferences open_prefs = QAction("&Preferences", self) open_prefs.setStatusTip("Edit preferences") open_prefs.triggered.connect(self.openPrefs) # Undo/redo self.undo_stack = QUndoStack(self) undo_act = self.undo_stack.createUndoAction(self, "&Undo") undo_act.setShortcut(QKeySequence.Undo) redo_act = self.undo_stack.createRedoAction(self, "&Redo") redo_act.setShortcut(QKeySequence.Redo) # Copy/paste self.copy_act = QAction("&Copy", self) self.copy_act.setShortcut("Ctrl+C") self.copy_act.setStatusTip("Copy") self.copy_act.setEnabled(False) self.paste_act = QAction("&Paste", self) self.paste_act.setShortcut("Ctrl+V") self.paste_act.setStatusTip("Paste") self.paste_act.setEnabled(False) # JCAP compile/load self.gendat_act = QAction("&Generate DAT Files", self) self.gendat_act.setShortcut("Ctrl+D") self.gendat_act.setStatusTip("Generate DAT Files") self.gendat_act.triggered.connect(self.genDATFiles) self.gendat_act.setEnabled(False) self.load_jcap = QAction("&Load JCAP System", self) self.load_jcap.setShortcut("Ctrl+L") self.load_jcap.setStatusTip("Load JCAP System") self.load_jcap.setEnabled(False) self.load_jcap.triggered.connect(self.loadJCAP) # Build menu bar menu_bar = self.menuBar() file_menu = menu_bar.addMenu("&File") file_menu.addAction(open_file) file_menu.addSeparator() file_menu.addAction(open_prefs) file_menu.addAction(exit_act) edit_menu = menu_bar.addMenu("&Edit") edit_menu.addAction(undo_act) edit_menu.addAction(redo_act) edit_menu.addAction(self.copy_act) edit_menu.addAction(self.paste_act) jcap_menu = menu_bar.addMenu("&JCAP") jcap_menu.addAction(self.gendat_act) jcap_menu.addAction(self.load_jcap) def setupStatusBar(self): """Set up bottom status bar """ self.statusBar = self.statusBar() def setupPrefs(self): QCoreApplication.setOrganizationName("Connor Spangler") QCoreApplication.setOrganizationDomain("https://github.com/cspang1") QCoreApplication.setApplicationName("JIDE") self.prefs = QSettings() self.prefs.setValue("test", 69) @pyqtSlot(bool) def setCopyActive(self, active): """Set whether the copy action is available :param active: Variable representing whether copy action should be set to available or unavailable :type active: bool """ self.copy_act.isEnabled(active) @pyqtSlot(bool) def setPasteActive(self, active): """Set whether the paste action is available :param active: Variable representing whether paste action should be set to available or unavailable :type active: bool """ self.paste_act.isEnabled(active) def selectFile(self): """Open file action to hand file handle to GameData """ file_name, _ = QFileDialog.getOpenFileName( self, "Open file", "", "JCAP Resource File (*.jrf)") self.loadProject(file_name) def loadProject(self, file_name): """Load project file data and populate UI elements/set up signals and slots """ if file_name: try: self.data = GameData.fromFilename(file_name, self) except KeyError: QMessageBox( QMessageBox.Critical, "Error", "Unable to load project due to malformed data", ).exec() return except OSError: QMessageBox( QMessageBox.Critical, "Error", "Unable to open project file", ).exec() return else: return self.setWindowTitle("JIDE - " + self.data.getGameName()) self.gendat_act.setEnabled(True) self.load_jcap.setEnabled(True) self.data.setUndoStack(self.undo_stack) self.sprite_scene = GraphicsScene(self.data, Source.SPRITE, self) self.tile_scene = GraphicsScene(self.data, Source.TILE, self) self.sprite_view = GraphicsView(self.sprite_scene, self) self.tile_view = GraphicsView(self.tile_scene, self) self.sprite_view.setStyleSheet("background-color: #494949;") self.tile_view.setStyleSheet("background-color: #494949;") sprite_pixel_palette = self.sprite_pixel_palette_dock.pixel_palette tile_pixel_palette = self.tile_pixel_palette_dock.pixel_palette sprite_color_palette = self.sprite_color_palette_dock.color_palette tile_color_palette = self.tile_color_palette_dock.color_palette sprite_pixel_palette.subject_selected.connect( self.sprite_scene.setSubject) self.sprite_scene.set_color_switch_enabled.connect( sprite_color_palette.color_preview.setColorSwitchEnabled) self.sprite_color_palette_dock.palette_updated.connect( self.sprite_scene.setColorPalette) self.sprite_color_palette_dock.palette_updated.connect( self.sprite_pixel_palette_dock.palette_updated) sprite_color_palette.color_selected.connect( self.sprite_scene.setPrimaryColor) tile_pixel_palette.subject_selected.connect(self.tile_scene.setSubject) self.tile_scene.set_color_switch_enabled.connect( tile_color_palette.color_preview.setColorSwitchEnabled) self.tile_color_palette_dock.palette_updated.connect( self.tile_scene.setColorPalette) self.tile_color_palette_dock.palette_updated.connect( self.tile_pixel_palette_dock.palette_updated) tile_color_palette.color_selected.connect( self.tile_scene.setPrimaryColor) self.sprite_color_palette_dock.setup(self.data) self.tile_color_palette_dock.setup(self.data) self.sprite_pixel_palette_dock.setup(self.data) self.tile_pixel_palette_dock.setup(self.data) self.canvas_tabs = QTabWidget() self.canvas_tabs.addTab(self.sprite_view, "Sprites") self.canvas_tabs.addTab(self.tile_view, "Tiles") self.canvas_tabs.setTabEnabled(0, True) self.canvas_tabs.setTabEnabled(1, True) self.setCentralWidget(self.canvas_tabs) self.canvas_tabs.currentChanged.connect(self.setCanvas) self.setCanvas(0) self.data.col_pal_updated.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.data.col_pal_renamed.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.data.col_pal_added.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.data.col_pal_removed.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.data.pix_batch_updated.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.data.row_count_updated.connect( lambda source, *_: self.canvas_tabs.setCurrentIndex(int(source))) self.select_tool.triggered.connect( lambda checked, tool=Tools.SELECT: self.sprite_scene.setTool(tool)) self.select_tool.triggered.connect( lambda checked, tool=Tools.SELECT: self.tile_scene.setTool(tool)) self.pen_tool.triggered.connect( lambda checked, tool=Tools.PEN: self.sprite_scene.setTool(tool)) self.pen_tool.triggered.connect( lambda checked, tool=Tools.PEN: self.tile_scene.setTool(tool)) self.fill_tool.triggered.connect(lambda checked, tool=Tools.FLOODFILL: self.sprite_scene.setTool(tool)) self.fill_tool.triggered.connect(lambda checked, tool=Tools.FLOODFILL: self.tile_scene.setTool(tool)) self.line_tool.triggered.connect( lambda checked, tool=Tools.LINE: self.sprite_scene.setTool(tool)) self.line_tool.triggered.connect( lambda checked, tool=Tools.LINE: self.tile_scene.setTool(tool)) self.rect_tool.triggered.connect(lambda checked, tool=Tools.RECTANGLE: self.sprite_scene.setTool(tool)) self.rect_tool.triggered.connect(lambda checked, tool=Tools.RECTANGLE: self.tile_scene.setTool(tool)) self.ellipse_tool.triggered.connect(lambda checked, tool=Tools.ELLIPSE: self.sprite_scene.setTool(tool)) self.ellipse_tool.triggered.connect( lambda checked, tool=Tools.ELLIPSE: self.tile_scene.setTool(tool)) for tool in self.tools: tool.setEnabled(True) self.pen_tool.setChecked(True) self.pen_tool.triggered.emit(True) def setCanvas(self, index): """Set the dock and signal/slot layout to switch between sprite/tile/ tile map tabs :param index: Index of canvas tab :type index: int """ self.paste_act.triggered.disconnect() self.copy_act.triggered.disconnect() if index == 0: self.copy_act.triggered.connect(self.sprite_scene.copy) self.paste_act.triggered.connect(self.sprite_scene.startPasting) self.tile_color_palette_dock.hide() self.tile_pixel_palette_dock.hide() self.sprite_color_palette_dock.show() self.sprite_pixel_palette_dock.show() self.removeDockWidget(self.tile_color_palette_dock) self.removeDockWidget(self.tile_pixel_palette_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.sprite_color_palette_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.sprite_pixel_palette_dock) self.copy_act.triggered.connect(self.sprite_scene.copy) self.paste_act.triggered.connect(self.sprite_scene.startPasting) self.sprite_scene.region_copied.connect(self.paste_act.setEnabled) self.sprite_scene.region_selected.connect(self.copy_act.setEnabled) elif index == 1: self.copy_act.triggered.connect(self.tile_scene.copy) self.paste_act.triggered.connect(self.tile_scene.startPasting) self.sprite_color_palette_dock.hide() self.sprite_pixel_palette_dock.hide() self.tile_color_palette_dock.show() self.tile_pixel_palette_dock.show() self.removeDockWidget(self.sprite_color_palette_dock) self.removeDockWidget(self.sprite_pixel_palette_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.tile_color_palette_dock) self.addDockWidget(Qt.RightDockWidgetArea, self.tile_pixel_palette_dock) self.copy_act.triggered.connect(self.tile_scene.copy) self.paste_act.triggered.connect(self.tile_scene.startPasting) self.tile_scene.region_copied.connect(self.paste_act.setEnabled) self.tile_scene.region_selected.connect(self.copy_act.setEnabled) def openPrefs(self): prefs = Preferences() prefs.exec() def genDATFiles(self): """Generate .dat files from project for use by JCAP """ dat_path = Path(__file__).parents[1] / "data" / "DAT Files" dat_path.mkdir(exist_ok=True) tcp_path = dat_path / "tile_color_palettes.dat" tpp_path = dat_path / "tiles.dat" scp_path = dat_path / "sprite_color_palettes.dat" spp_path = dat_path / "sprites.dat" tile_pixel_data = self.data.getPixelPalettes(Source.TILE) tile_color_data = self.data.getColPals(Source.TILE) sprite_pixel_data = self.data.getPixelPalettes(Source.SPRITE) sprite_color_data = self.data.getColPals(Source.SPRITE) self.genPixelDATFile(tile_pixel_data, tpp_path) self.genColorDATFile(tile_color_data, tcp_path) self.genPixelDATFile(sprite_pixel_data, spp_path) self.genColorDATFile(sprite_color_data, scp_path) def genPixelDATFile(self, source, path): """Generate sprite/tile pixel palette .dat file :param source: List containing sprite/tile pixel data :type source: list :param path: File path to .dat :type path: str """ with path.open("wb") as dat_file: for element in source: for line in element: total = 0 for pixel in line: total = (total << 4) + pixel dat_file.write(total.to_bytes(4, byteorder="big")[::-1]) def genColorDATFile(self, source, path): """Generate sprite/tile color palette .dat file :param source: List containing sprite/tile color data :type source: list :param path: File path to .dat :type path: str """ with path.open("wb") as dat_file: for palette in source: for color in palette: r, g, b = downsample(color.red(), color.green(), color.blue()) rgb = (r << 5) | (g << 2) | (b) dat_file.write(bytes([rgb])) def loadJCAP(self): """Generate .dat files and execute command-line serial loading of JCAP """ self.statusBar.showMessage("Loading JCAP...") self.genDATFiles() dat_path = Path(__file__).parents[1] / "data" / "DAT Files" jcap_path = Path(__file__).parents[2] / "jcap" / "dev" / "software" sysload_path = jcap_path / "sysload.sh" for dat_file in dat_path.glob("**/*"): shutil.copy(str(dat_file), str(jcap_path)) self.prefs.beginGroup("ports") if not self.prefs.contains("cpu_port") or not self.prefs.contains( "gpu_port"): # Popup error self.openPrefs() return cpu_port = self.prefs.value("cpu_port") gpu_port = self.prefs.value("gpu_port") self.prefs.endGroup() result = subprocess.run( ["bash.exe", str(sysload_path), "-c", cpu_port, "-g", gpu_port], capture_output=True, ) print(result.stderr) self.statusBar.showMessage("JCAP Loaded!", 5000)
class Preferences(object): """docstring for Preferences""" def __init__(self): self.qs = QSettings() self.ui_prefs = Ui_Preferences() self.widget = QWidget() self.ui_prefs.setupUi(self.widget) self.readPreferences() self.widget.addAction(self.ui_prefs.actionClose) self.ui_prefs.actionClose.triggered.connect(self.hideDialog) self.ui_prefs.grid_appearance_type_combo_box.currentIndexChanged.connect( self.setGridAppearanceType) self.ui_prefs.zoom_speed_slider.valueChanged.connect(self.setZoomSpeed) self.ui_prefs.button_box.clicked.connect(self.handleButtonClick) self.ui_prefs.add_plugin_button.clicked.connect(self.addPlugin) self.ui_prefs.show_icon_labels.clicked.connect(self.setShowIconLabels) self.ui_prefs.grid_appearance_type_combo_box.activated.connect( self.updateGrid) self.document = None # end def def showDialog(self): self.readPreferences() self.widget.show() # launch prefs in mode-less dialog # end def def updateGrid(self, index): self.setGridAppearanceType(index) value = self.getGridAppearanceType() for part in self.document.getParts(): part.partDocumentSettingChangedSignal.emit(part, 'grid', value) # end def def hideDialog(self): self.widget.hide() # end def def handleButtonClick(self, button): """ Restores defaults. Other buttons are ignored because connections are already set up in qt designer. """ if self.ui_prefs.button_box.buttonRole( button) == QDialogButtonBox.ResetRole: self.restoreDefaults() # end def def readPreferences(self): self.qs.beginGroup("Preferences") self.grid_appearance_type_index = self.qs.value( "grid_appearance_type_index", styles.PREF_GRID_APPEARANCE_TYPE_INDEX) self.zoom_speed = self.qs.value("zoom_speed", styles.PREF_ZOOM_SPEED) self.show_icon_labels = self.qs.value("ui_icons_labels", styles.PREF_SHOW_ICON_LABELS) self.qs.endGroup() self.ui_prefs.grid_appearance_type_combo_box.setCurrentIndex( self.grid_appearance_type_index) self.ui_prefs.zoom_speed_slider.setProperty("value", self.zoom_speed) self.ui_prefs.show_icon_labels.setChecked(self.show_icon_labels) ptw = self.ui_prefs.plugin_table_widget loaded_plugin_paths = util.loadedPlugins.keys() ptw.setRowCount(len(loaded_plugin_paths)) for i in range(len(loaded_plugin_paths)): row = QTableWidgetItem(loaded_plugin_paths[i]) row.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) ptw.setItem(i, 0, row) # end def def restoreDefaults(self): self.ui_prefs.grid_appearance_type_combo_box.setCurrentIndex( styles.PREF_GRID_APPEARANCE_TYPE_INDEX) self.ui_prefs.zoom_speed_slider.setProperty("value", styles.PREF_ZOOM_SPEED) self.ui_prefs.show_icon_labels.setChecked(styles.PREF_SHOW_ICON_LABELS) # end def def setGridAppearanceType(self, grid_appearance_type_index): self.grid_appearance_type_index = grid_appearance_type_index self.qs.beginGroup("Preferences") self.qs.setValue("grid_appearance_type_index", grid_appearance_type_index) self.qs.endGroup() # end def def getGridAppearanceType(self): return ['circles', 'lines and points', 'points'][self.grid_appearance_type_index] # end def def setZoomSpeed(self, speed): self.zoom_speed = speed self.qs.beginGroup("Preferences") self.qs.setValue("zoom_speed", self.zoom_speed) self.qs.endGroup() # end def def setShowIconLabels(self, checked): self.show_icon_labels = checked self.qs.beginGroup("Preferences") self.qs.setValue("ui_icons_labels", self.show_icon_labels) self.qs.endGroup() # end def def addPlugin(self): fdialog = QFileDialog(self.widget, "Install Plugin", util.this_path(), "Cadnano Plugins (*.cnp)") fdialog.setAcceptMode(QFileDialog.AcceptOpen) fdialog.setWindowFlags(Qt.Sheet) fdialog.setWindowModality(Qt.WindowModal) fdialog.filesSelected.connect(self.addPluginAtPath) self.fileopendialog = fdialog fdialog.open() # end def def addPluginAtPath(self, fname): self.fileopendialog.close() fname = str(fname[0]) print("Attempting to open plugin %s" % fname) try: zf = zipfile.ZipFile(fname, 'r') except Exception as e: self.failWithMsg("Plugin file seems corrupt: %s." % e) return tdir = tempfile.mkdtemp() try: for f in zf.namelist(): if f.endswith('/'): os.makedirs(os.path.join(tdir, f)) for f in zf.namelist(): if not f.endswith('/'): zf.extract(f, tdir) except Exception as e: self.failWithMsg("Extraction of plugin archive failed: %s." % e) return files_in_zip = [(f, os.path.join(tdir, f)) for f in os.listdir(tdir)] try: self.confirmDestructiveIfNecessary(files_in_zip) self.removePluginsToBeOverwritten(files_in_zip) self.movePluginsIntoPluginsFolder(files_in_zip) except OSError: print("Couldn't copy files into plugin directory, attempting\ again after boosting privileges.") if platform.system() == 'Darwin': self.darwinAuthedMvPluginsIntoPluginsFolder(files_in_zip) elif platform.system() == 'Linux': self.linuxAuthedMvPluginsIntoPluginsFolder(files_in_zip) else: print("Can't boost privelages on platform %s" % platform.system()) loadedAPlugin = util.loadAllPlugins() if not loadedAPlugin: print("Unable to load anythng from plugin %s" % fname) self.readPreferences() shutil.rmtree(tdir) # end def def darwinAuthedMvPluginsIntoPluginsFolder(self, files_in_zip): envirn = {"DST": util.this_path() + '/plugins'} srcstr = '' for i in range(len(files_in_zip)): file_name, file_path = files_in_zip[i] srcstr += ' \\"$SRC' + str(i) + '\\"' envirn['SRC' + str(i)] = file_path proc = subprocess.Popen(['osascript','-e',\ 'do shell script "cp -fR ' + srcstr +\ ' \\"$DST\\"" with administrator privileges'],\ env=envirn) retval = self.waitForProcExit(proc) if retval != 0: self.failWithMsg('cp failed with code %i' % retval) # end def def linuxAuthedMvPluginsIntoPluginsFolder(self, files_in_zip): args = ['gksudo', 'cp', '-fR'] args.extend(file_path for file_name, file_path in files_in_zip) args.append(util.this_path() + '/plugins') proc = subprocess.Popen(args) retval = self.waitForProcExit(proc) if retval != 0: self.failWithMsg('cp failed with code %i' % retval) # end def def confirmDestructiveIfNecessary(self, files_in_zip): for file_name, file_path in files_in_zip: target = os.path.join(util.this_path(), 'plugins', file_name) if os.path.isfile(target): return self.confirmDestructive() elif os.path.isdir(target): return self.confirmDestructive() # end def def confirmDestructive(self): mb = QMessageBox(self.widget) mb.setIcon(QMessageBox.Warning) mb.setInformativeText("The plugin you are trying to install\ has already been installed. Replace the currently installed one?") mb.setStandardButtons(QMessageBox.No | QMessageBox.Yes) mb.exec_() return mb.clickedButton() == mb.button(QMessageBox.Yes) # end def def removePluginsToBeOverwritten(self, files_in_zip): for file_name, file_path in files_in_zip: target = os.path.join(util.this_path(), 'plugins', file_name) if os.path.isfile(target): os.unlink(target) elif os.path.isdir(target): shutil.rmtree(target) # end def def movePluginsIntoPluginsFolder(self, files_in_zip): for file_name, file_path in files_in_zip: target = os.path.join(util.this_path(), 'plugins', file_name) shutil.move(file_path, target) # end def def waitForProcExit(self, proc): procexit = False while not procexit: try: retval = proc.wait() procexit = True except OSError as e: if e.errno != errno.EINTR: raise ose return retval # end def def failWithMsg(self, str): mb = QMessageBox(self.widget) mb.setIcon(QMessageBox.Warning) mb.setInformativeText(str) mb.buttonClicked.connect(self.closeFailDialog) self.fail_message_box = mb mb.open() # end def def closeFailDialog(self, button): self.fail_message_box.close() del self.fail_message_box self.fail_message_box = None
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, test_registry, test_context): super().__init__() self._test_registry = test_registry self._test_context = test_context self._test_items = [] self.setupUi(self) self._setup_test_tree() self.test_selected_button.clicked.connect( self._test_selected_button_clicked) self.test_failed_button.clicked.connect( self._test_failed_button_clicked) self.exit_action.triggered.connect(self.close) self.reset_status_log_action.triggered.connect( self._reset_status_log_action_triggered) self.about_qt_action.triggered.connect( lambda: QtWidgets.QMessageBox.aboutQt(self)) self._settings = QSettings("gui.ini", QSettings.IniFormat) self._settings_resore() self._update_chip() self._reload_test_tree() self._update_current_test_info() self.current_test_tab_widget.setCurrentIndex(0) def closeEvent(self, event): self._settings_save() def _setup_test_tree(self): self.test_tree.setColumnCount(2) self.test_tree.setHeaderLabels(["Тест", "Статус"]) self.test_tree.header().setSectionResizeMode( 0, QtWidgets.QHeaderView.Stretch) self.test_tree.itemChanged.connect(self._test_tree_update_item) self.test_tree.itemSelectionChanged.connect( self._update_current_test_info) def _settings_resore(self): self._settings.beginGroup("Main") self.board_number_auto_inc_action.setChecked( self._settings.value("board_number_auto_inc", 0, int) != 0) self.board_number_spin_box.setValue( self._settings.value("board_number", 0, int)) self._settings.endGroup() self._settings.beginGroup("MainWindow") self.resize(self._settings.value("size", self.size(), QSize)) self.move(self._settings.value("pos", self.pos(), QPoint)) self._settings.endGroup() def _settings_save(self): self._settings.beginGroup("Main") self._settings.setValue( "board_number_auto_inc", 1 if self.board_number_auto_inc_action.isChecked() else 0) self._settings.setValue("board_number", self.board_number_spin_box.value()) self._settings.endGroup() self._settings.beginGroup("MainWindow") self._settings.setValue("size", self.size()) self._settings.setValue("pos", self.pos()) self._settings.endGroup() def _update_chip(self): self.chip_name_line_edit.setText( f"{self._test_context.chip.name} ({self._test_context.chip.part})") def _reload_test_tree(self): update_suitable(self._test_registry, self._test_context) self.test_tree.clear() self._test_items = [] self._reload_test_tree_recursive(self._test_registry.all_tests, None) self._update_tests_status() self.test_tree.expandAll() def _reload_test_tree_recursive(self, tests, parent): for key, value in tests.items(): item = QtWidgets.QTreeWidgetItem(self.test_tree if parent == None else parent) if isinstance(value, TestDesc): item.test_desc = value self._test_items.append(item) item.setText(0, value.name) item.setCheckState(0, Qt.Unchecked) if value.suitable: item.setFlags(item.flags() | Qt.ItemIsUserCheckable) else: item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable) item.setForeground(0, Qt.gray) else: item.test_desc = None item.setText(0, key) item.setCheckState(0, Qt.Unchecked) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) self._reload_test_tree_recursive(value, item) def _update_current_test_info(self): self.current_test_log_plain_text_edit.setPlainText(None) self.current_test_log_plain_text_edit.hide() self.current_test_description_label.setText(None) self.current_test_description_label.hide() test_desc = None if self.test_tree.selectedItems(): item = self.test_tree.selectedItems()[0] test_desc = item.test_desc if test_desc: if test_desc.log_text: self.current_test_log_plain_text_edit.setPlainText( test_desc.log_text) self.current_test_log_plain_text_edit.show() if "description" in test_desc.params: self.current_test_description_label.setText( test_desc.params["description"]) else: self.current_test_description_label.setText( "<Нет описания теста>") self.current_test_description_label.show() @pyqtSlot() def _update_tests_status(self): for item in self._test_items: item.setForeground( 1, Qt.black if item.test_desc.suitable else Qt.gray) if item.test_desc.status == TEST_STATUS_NOT_EXECUTED: item.setText(1, "Не запускался") item.setBackground( 1, Qt.gray if item.test_desc.suitable else Qt.white) elif item.test_desc.status == TEST_STATUS_PASSED: item.setText(1, "Пройден") item.setBackground(1, Qt.green) elif item.test_desc.status == TEST_STATUS_FAULT: item.setText(1, "Ошибка") item.setBackground(1, Qt.red) else: raise Exception("Unknown test status") @pyqtSlot(QtWidgets.QTreeWidgetItem, int) def _test_tree_update_item(self, item, column): if not item.test_desc: for i in range(item.childCount()): child = item.child(i) if not child.test_desc or child.test_desc.suitable: child.setCheckState(0, item.checkState(0)) @pyqtSlot() def _test_selected_button_clicked(self): test_desc_list = [] for item in self._test_items: if item.checkState(0) == Qt.Checked: test_desc = item.test_desc test_desc.status = TEST_STATUS_NOT_EXECUTED test_desc.log_text = None test_desc_list.append(test_desc) self._test_list(test_desc_list) @pyqtSlot() def _test_failed_button_clicked(self): test_desc_list = [] for item in self._test_items: test_desc = item.test_desc if test_desc.status == TEST_STATUS_FAULT: test_desc_list.append(test_desc) self._test_list(test_desc_list) def _test_list(self, test_desc_list): if not test_desc_list: QtWidgets.QMessageBox.critical(self, "Ошибка", "Нет выбранных тестов") return self._update_tests_status() self._update_current_test_info() testing_dialog = TestingDialog(self, test_desc_list, self._test_context, self.board_number_spin_box.value()) testing_dialog.exec_() self._update_tests_status() self._update_current_test_info() if self.board_number_auto_inc_action.isChecked(): if all([ x.test_desc.status == TEST_STATUS_PASSED for x in self._test_items if x.test_desc.suitable ]): self.board_number_spin_box.setValue( self.board_number_spin_box.value() + 1) self._settings_save() QtWidgets.QMessageBox.information( self, "Информация", "Все тесты пройдены, номер платы увеличен на 1") @pyqtSlot() def _reset_status_log_action_triggered(self): for item in self._test_items: test_desc = item.test_desc test_desc.status = TEST_STATUS_NOT_EXECUTED test_desc.log_text = None self._update_tests_status() self._update_current_test_info()
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")
class MainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.settings = QSettings() self.is_visible = True self.is_expanded = True self.is_move_action = False self.should_confirm_close = False self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_NoSystemBackground, True) self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_QuitOnClose) self.setWindowIcon(QIcon(join(config.ASSETS_DIR, "droptopus.png"))) self.setWindowTitle("Droptopus") self.miniwin = MiniWindow(self) self.frame = DropFrame(self) self.frame.show() self.readSettings() self.content = QStackedWidget() self.setCentralWidget(self.content) self.content.addWidget(self.frame) self.content.addWidget(self.miniwin) self.setAcceptDrops(True) self.setMouseTracking(True) self.collapse() def contextMenuEvent(self, event): menu = QMenu(self) label = ("Expand", "Collapse")[self.is_expanded] expand_action = menu.addAction(label) about_action = menu.addAction("About") menu.addSeparator() quit_action = menu.addAction("Quit") action = menu.exec_(self.mapToGlobal(event.pos())) if action == expand_action: if self.is_expanded: self.collapse() else: self.expand() elif action == about_action: self.showAbout() elif action == quit_action: self.should_confirm_close = True self.close() def expand(self): if self.is_expanded: return self.is_expanded = True self.setAcceptDrops(False) self.content.hide() expanded = self.frame.sizeHint() self.setMinimumSize(expanded) self.content.setCurrentWidget(self.frame) self.content.show() self.resize(expanded) # on OSX the window will not automatically stay inside the screen like on Linux # we have to do it manually screen_rect = QDesktopWidget().screenGeometry() window_rect = self.frameGeometry() intersection = window_rect & screen_rect dx = window_rect.width() - intersection.width() dy = window_rect.height() - intersection.height() unseen = window_rect & intersection if dx != 0 or dy != 0: if window_rect.left() > screen_rect.left(): dx = dx * -1 if window_rect.bottom() > screen_rect.bottom(): dy = dy * -1 self.move(window_rect.left() + dx, window_rect.top() + dy) def collapse(self): if not self.is_expanded: return self.is_expanded = False self.setAcceptDrops(True) self.content.hide() mini = self.miniwin.sizeHint() self.setMinimumSize(mini) self.move(self.anchor) self.content.setCurrentWidget(self.miniwin) self.content.show() self.resize(mini) def showAbout(self): about = AboutDialog(self) about.setModal(True) about.show() def mouseReleaseEvent(self, event): self.is_move_action = False def mousePressEvent(self, event): if not event.button() == Qt.LeftButton: return self.is_move_action = True self.offset = event.pos() def mouseMoveEvent(self, event): if not self.is_move_action: return x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x - x_w, y - y_w) if self.content.currentWidget() == self.miniwin: self.anchor = self.pos() def writeSettings(self): self.settings.beginGroup("MainWindow") self.settings.setValue("anchor", self.anchor) self.settings.endGroup() def readSettings(self): self.settings.beginGroup("MainWindow") saved_anchor = self.settings.value("anchor", None) if saved_anchor != None: self.anchor = saved_anchor else: rect = QDesktopWidget().screenGeometry() mini = self.miniwin.sizeHint() self.anchor = QPoint(rect.right() - mini.width(), rect.bottom() - mini.height()) self.settings.endGroup() def userReallyWantsToQuit(self): if not self.should_confirm_close: return True reply = QMessageBox.question( self, "Close Droptopus", "Are you sure you want to close the application?", QMessageBox.Yes, QMessageBox.No, ) return reply == QMessageBox.Yes def closeEvent(self, event): if self.userReallyWantsToQuit(): self.writeSettings() event.accept() else: event.ignore() # def dragMoveEvent(self, event): # super(MainWindow, self).dragMoveEvent(event) def dragEnterEvent(self, event): if not self.is_expanded: QTimer.singleShot(200, self.expand) else: super(MainWindow, self).dragEnterEvent(event) def mouseDoubleClickEvent(self, event): if self.is_expanded: self.collapse() else: self.expand() def event(self, evt): et = evt.type() if et == events.COLLAPSE_WINDOW: evt.accept() self.collapse() return True if evt.type() == events.RELOAD_WIDGETS: evt.accept() if self.is_expanded: self.resize(self.sizeHint()) if et == events.EXPAND_WINDOW: evt.accept() self.expand() return True elif et == events.CLOSE_WINDOW: evt.accept() self.should_confirm_close = True self.close() return True return super(MainWindow, self).event(evt)
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 MainWindow(QMainWindow): def __init__(self): super().__init__() self.setFont(QFont('Arial', 10)) self.resolution = QGuiApplication.primaryScreen().availableGeometry() self.reso_height = self.resolution.height() self.reso_width = self.resolution.width() self.settings = QSettings( os.path.join(os.path.abspath('.'), 'settings.ini'), QSettings.IniFormat) self.pixiv_var = None # Pixiv global vars self.pixiv_login = None # Pixiv login page self.pixiv_main = None # Pixiv main page self.pixiv_icon = QIcon(os.path.join(bundle_dir, 'icon', 'pixiv.png')) self.ehentai_var = None # Ehentai global vars self.ehentai_login = None # Ehentai login page self.ehentai_main = None # Ehentai main page self.ehentai_icon = QIcon( os.path.join(bundle_dir, 'icon', 'ehentai.png')) self.tab_widget = QTabWidget() # Main widget of main window self.setCentralWidget(self.tab_widget) self.misc_setting = misc.MiscSettingDialog() self.misc_setting.closed.connect(self.misc_setting_checker) self.rule_setting = misc.SaveRuleDialog() self.init_ui() def init_ui(self): menu_bar = self.menuBar() file_menu = menu_bar.addMenu('文件(&F)') act_clear_cookies = QAction('清除Cookie(&C)', self) act_clear_cookies.triggered.connect(self.clear_cookies) act_clear_db = QAction('清除数据库缓存(&D)', self) act_clear_db.triggered.connect(self.clear_db) act_exit = QAction('退出(&Q)', self) act_exit.triggered.connect(QCoreApplication.quit) file_menu.addAction(act_clear_cookies) file_menu.addAction(act_clear_db) file_menu.addSeparator() file_menu.addAction(act_exit) setting_menu = menu_bar.addMenu('设置(&S)') misc_setting = QAction('首选项(&P)', self) rule_setting = QAction('保存规则(&R)', self) setting_menu.addAction(misc_setting) setting_menu.addAction(rule_setting) misc_setting.triggered.connect( partial(self.setting_dialog, self.misc_setting)) rule_setting.triggered.connect( partial(self.setting_dialog, self.rule_setting)) self.tab_login('pixiv') self.tab_login('ehentai') self.tab_widget.setCurrentIndex(0) self.frameGeometry().moveCenter( self.resolution.center()) # Open at middle of screen self.setWindowTitle('PETSpider') self.show() def init_var(self): """ Construct global instances for every class. Return: A globj.Global class instance, should be passed to construct func of every modal. """ session = requests.Session() retries = Retry(total=3, backoff_factor=0.2) adp = HTTPAdapter(max_retries=retries) session.mount('http://', adp) session.mount('https://', adp) self.settings.beginGroup('MiscSetting') proxy = self.settings.value('proxy', {}) self.settings.endGroup() return misc.Global(session, proxy, bundle_dir) def tab_logout(self, tab: str, info=None): """Switch tab widget to main page.""" if tab == 'pixiv': # Recreate main page instance because new main page needs user's name/id self.pixiv_main = pixiv.gui.MainWidget(self.pixiv_var, info) self.pixiv_main.logout_sig.connect(self.tab_login) self.tab_widget.removeTab(0) self.tab_widget.insertTab(0, self.pixiv_main, self.pixiv_icon, 'Pixiv') self.tab_widget.setCurrentIndex(0) if tab == 'ehentai': self.ehentai_main = ehentai.gui.MainWidget(self.ehentai_var, info) self.ehentai_main.logout_sig.connect(self.tab_login) self.tab_widget.removeTab(1) self.tab_widget.insertTab(1, self.ehentai_main, self.ehentai_icon, 'Ehentai') self.tab_widget.setCurrentIndex(1) def tab_login(self, tab: str): """Switch tab widget to login page.""" if tab == 'pixiv': # Recreate glovar instance bacause old session contains old cookies self.pixiv_var = self.init_var() self.pixiv_login = pixiv.gui.LoginWidget(self.pixiv_var) self.pixiv_login.login_success.connect(self.tab_logout) self.tab_widget.removeTab(0) self.tab_widget.insertTab(0, self.pixiv_login, self.pixiv_icon, 'Pixiv') self.tab_widget.setCurrentIndex(0) if tab == 'ehentai': self.ehentai_var = self.init_var() self.ehentai_login = ehentai.gui.LoginWidget(self.ehentai_var) self.ehentai_login.login_success.connect(self.tab_logout) self.tab_widget.removeTab(1) self.tab_widget.insertTab(1, self.ehentai_login, self.ehentai_icon, 'Ehentai') self.tab_widget.setCurrentIndex(1) def clear_cookies(self): self.settings.beginGroup('Cookies') self.settings.setValue('pixiv', '') self.settings.setValue('ehentai', '') self.settings.sync() self.settings.endGroup() self.pixiv_login.clear_cookies() misc.show_msgbox(self, QMessageBox.Information, '清除完成', '成功清除登陆信息!') def clear_db(self): pixiv.core.cleaner() misc.show_msgbox(self, QMessageBox.Information, '清除完成', '成功清除数据库缓存!') def setting_dialog(self, setting): setting.move( round(self.x() + (self.width() - self.misc_setting.sizeHint().width()) / 2), round(self.y() + (self.height() - self.misc_setting.sizeHint().height()) / 2)) setting.show() def misc_setting_checker(self): """Make the proxy and thumbnail setting active immediately.""" self.settings.beginGroup('MiscSetting') if int(self.settings.value('pixiv_proxy', False)): self.pixiv_var.proxy = self.settings.value('proxy', {}) else: self.pixiv_var.proxy = {} if int(self.settings.value('ehentai_proxy', False)): self.ehentai_var.proxy = self.settings.value('proxy', {}) else: self.ehentai_var.proxy = {} setting_thumbnail = int(self.settings.value('thumbnail', True)) if self.pixiv_main: # Change thumbnail behavior self.pixiv_main.change_thumb_state(setting_thumbnail) if self.ehentai_main: self.ehentai_main.change_thumb_state(setting_thumbnail) self.settings.endGroup() def closeEvent(self, event): """Do cleaning before closing.""" if self.pixiv_main: if self.pixiv_main.logout_fn(): event.accept() else: event.ignore() if self.ehentai_main: if self.ehentai_main.logout_fn(): event.accept() else: event.ignore()
def __init__(self, parent=None): QW.QWidget.__init__(self, parent) self.setWindowTitle('TagMaker') self.setMinimumSize(600, 300) # размеры окна self.workplace = QW.QWidget( self) #добавляем место для вкладок и файлов self.setCentralWidget(self.workplace) #основное пространство self.main_grid = QW.QGridLayout( ) #сетка для выравнивания, в ней будет три-четыре элемента в одну строку, плюс пустые столбцы #self.main_grid.setAlignment(QtCore.Qt.AlignTop)#со сплиттером лучше отключить выравнивание self.workplace.setLayout(self.main_grid) #создание панели вкладок, класс в другом модуле tab_widget = QW.QWidget() self.tag_tab = tag_table.TagTabs(self) self.tag_tab.setMinimumWidth(250) #250 - пока оптимальный размер tab_layout = QW.QVBoxLayout() tab_buttons_layout = QW.QHBoxLayout() #создание управляющих кнопок self.addTab_button = QW.QPushButton() self.delete_tag_button = QW.QPushButton() self.appTag_button = QW.QPushButton() self.findMode_button = QW.QPushButton( ) #кнопка для переключения режимов поиска и назначения self.find_button = QW.QPushButton() self.addTab_button.setFixedSize(QtCore.QSize(30, 30)) self.delete_tag_button.setFixedSize(QtCore.QSize(30, 30)) self.appTag_button.setFixedSize(QtCore.QSize(30, 30)) self.findMode_button.setFixedSize(QtCore.QSize(30, 30)) self.find_button.setFixedSize(QtCore.QSize(30, 30)) self.addTab_button.clicked.connect(self.tag_tab.add_tab_dialog) self.delete_tag_button.clicked.connect(self.tag_tab.change_checkbox) self.appTag_button.clicked.connect(self.app_tags) self.findMode_button.clicked.connect(self.find_tag_mode) self.findMode_button.setCheckable(True) self.find_button.setDisabled(True) self.find_button.clicked.connect(self.find_by_tags) #иконки к кнопкам self.addTab_button.setIcon( QtGui.QIcon('styles/images/add_tab_btn.png')) self.addTab_button.setIconSize(QtCore.QSize(25, 25)) self.addTab_button.setToolTip('Add a new tab') self.delete_tag_button.setIcon( QtGui.QIcon('styles/images/del_tag_btn.png')) self.delete_tag_button.setIconSize(QtCore.QSize(20, 20)) self.delete_tag_button.setToolTip('Delete selected tags') self.appTag_button.setIcon( QtGui.QIcon('styles/images/tag_to_folder.png')) self.appTag_button.setIconSize(QtCore.QSize(20, 20)) self.appTag_button.setToolTip('App checked tags to folder') self.find_button.setIcon(QtGui.QIcon('styles/images/search.png')) self.find_button.setIconSize(QtCore.QSize(20, 20)) self.find_button.setToolTip('Find by checked tags') self.findMode_button.setIcon( QtGui.QIcon('styles/images/search_mode.png')) self.findMode_button.setIconSize(QtCore.QSize(20, 20)) self.findMode_button.setToolTip('Find mode on/off') tab_buttons_layout.addWidget(self.addTab_button) tab_buttons_layout.addWidget(self.delete_tag_button) tab_buttons_layout.addWidget(self.appTag_button) tab_buttons_layout.addWidget(self.findMode_button) tab_buttons_layout.addWidget(self.find_button) #tab_buttons_layout.addWidget(ch) tab_buttons_layout.addStretch(0) tab_layout.addLayout(tab_buttons_layout) tab_layout.addWidget(self.tag_tab) tab_widget.setLayout(tab_layout) #self.tag_tab.resize(50,0) #список добавленных объектов item_widget = QW.QWidget() self.item_list = item_list.Item_List(self) self.item_list.setMinimumWidth(150) #кнопки к нему item_layout = QW.QVBoxLayout() item_buttons_layout = QW.QHBoxLayout() delete_item_button = QW.QPushButton() delete_item_button.setFixedSize(QtCore.QSize(30, 30)) delete_item_button.clicked.connect(self.item_list.delete_item) open_button = QW.QPushButton() open_button.setFixedSize(QtCore.QSize(30, 30)) open_button.clicked.connect(self.open_item) in_tree_button = QW.QPushButton() in_tree_button.setFixedSize(QtCore.QSize(30, 30)) in_tree_button.clicked.connect(self.find_in_tree) #строка поиска объектов find_line = self.item_list.find_line #иконки delete_item_button.setIcon( QtGui.QIcon('styles/images/del_folder_btn.png')) delete_item_button.setIconSize(QtCore.QSize(20, 20)) delete_item_button.setToolTip('Delete folder from list') open_button.setIcon(QtGui.QIcon('styles/images/open_folder.png')) open_button.setIconSize(QtCore.QSize(20, 20)) open_button.setToolTip('Open folder in explorer') in_tree_button.setIcon(QtGui.QIcon('styles/images/in_tree.png')) in_tree_button.setIconSize(QtCore.QSize(20, 20)) in_tree_button.setToolTip('View in tree') item_buttons_layout.addWidget(delete_item_button) item_buttons_layout.addWidget(open_button) item_buttons_layout.addWidget(in_tree_button) item_buttons_layout.addWidget(find_line) item_layout.addLayout(item_buttons_layout) item_layout.addWidget(self.item_list) #item_buttons_layout.addStretch(0) item_widget.setLayout(item_layout) #дерево каталогов self.folder_list = tree_view.Tree_View(self) #разделитель для изменения ширины self.tag_splitter = QW.QSplitter(QtCore.Qt.Horizontal, self) self.tag_splitter.setChildrenCollapsible(False) self.tag_splitter.addWidget(tab_widget) #добавили флажки self.tag_splitter.addWidget(item_widget) #список с элементами self.tag_splitter.addWidget(self.folder_list) #дерево каталогов self.main_grid.addWidget(self.tag_splitter, 0, 0) #self.add_toolbar()#добавить тулбар, это метод #перешли к другому расположению кнопок #этот сигнал приходит из списка каталогов, когда в этом списке выбран каталог. нужен для синхронизации тегов и вкладок self.item_list.selectionModel().selectionChanged.connect( self.choose_checkbox) #разобраться с сохранением данных settings = QSettings('settings.ini', QSettings.IniFormat) settings.beginGroup('JUJ') settings.setValue('x', [100, 200]) settings.endGroup()
class Preferences(QWidget): def __init__(self): QWidget.__init__(self) self.settings = QSettings("settings/preferences.ini", QSettings.IniFormat) self.defaults = QSettings("settings/default.ini", QSettings.IniFormat) self.setWindowTitle("Preferences") self.setFixedSize(300, 230) layout = QFormLayout() desktop = QApplication.desktop() x = (desktop.width() - self.width()) / 2 y = (desktop.height() - self.height()) / 3 self.move(x, y) self.lineProtocol = QLineEdit() self.lineProtocol.textChanged.connect(self.on_field_modify) self.lineAddress = QLineEdit() self.lineAddress.textChanged.connect(self.on_field_modify) self.linePort = QLineEdit() self.linePort.textChanged.connect(self.on_field_modify) self.linePostfixGet = QLineEdit() self.linePostfixGet.textChanged.connect(self.on_field_modify) self.linePostfixPost = QLineEdit() self.linePostfixPost.textChanged.connect(self.on_field_modify) self.buttonDefaults = QPushButton("Default") self.buttonDefaults.setFixedHeight(30) self.buttonDefaults.clicked.connect(self.on_default) self.buttonDefaults.setDisabled(True) self.buttonSave = QPushButton("Save") self.buttonSave.setFixedHeight(30) self.buttonSave.clicked.connect(self.on_save_click) self.buttonSave.setDisabled(True) self.butbox = QHBoxLayout() self.butbox.addWidget(self.buttonDefaults) self.butbox.addWidget(self.buttonSave) layout.addRow("Protocol:", self.lineProtocol) layout.addRow("Address:", self.lineAddress) layout.addRow("Port:", self.linePort) layout.addRow("Postfix GET:", self.linePostfixGet) layout.addRow("Postfix POST:", self.linePostfixPost) layout.addRow(" ", None) layout.addRow(self.butbox) self.setLayout(layout) self.on_load() def on_load(self): self.lineProtocol.setText( self.settings.value("server_connection/protocol")) self.lineAddress.setText( self.settings.value("server_connection/address")) self.linePort.setText(self.settings.value("server_connection/port")) self.linePostfixGet.setText( self.settings.value("server_connection/postfix_get")) self.linePostfixPost.setText( self.settings.value("server_connection/postfix_post")) def on_default(self): self.lineProtocol.setText( self.defaults.value("server_connection/protocol", defaultValue="http")) self.lineAddress.setText( self.defaults.value("server_connection/address", defaultValue="192.168.0.14")) self.linePort.setText( self.defaults.value("server_connection/port", defaultValue="5003")) self.linePostfixGet.setText( self.defaults.value("server_connection/postfix_get", defaultValue="getbyid?card=")) self.linePostfixPost.setText( self.defaults.value("server_connection/postfix_post", defaultValue="edit")) self.buttonDefaults.setDisabled(True) if not self.buttonSave.isEnabled(): self.buttonSave.setDisabled(False) def on_save_click(self): self.settings.beginGroup("server_connection") self.settings.setValue("protocol", self.lineProtocol.text()) self.settings.setValue("address", self.lineAddress.text()) self.settings.setValue("port", self.linePort.text()) self.settings.setValue("postfix_get", self.linePostfixGet.text()) self.settings.setValue("postfix_post", self.linePostfixPost.text()) self.settings.endGroup() self.buttonSave.setDisabled(True) print("Saved") def on_field_modify(self): if self.lineProtocol.isModified() or self.lineAddress.isModified() or self.linePort.isModified() \ or self.linePostfixGet.isModified() or self.linePostfixPost.isModified(): self.buttonSave.setDisabled(False) self.buttonDefaults.setDisabled(False)
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self._version = __version__ self.setWindowIcon(QIcon(":/logo.png")) self.setWindowTitle("Tasmota Device Manager {}".format(self._version)) self.unknown = [] self.env = TasmotaEnvironment() self.device = None self.topics = [] self.mqtt_queue = [] self.fulltopic_queue = [] # ensure TDM directory exists in the user directory if not os.path.isdir("{}/TDM".format(QDir.homePath())): os.mkdir("{}/TDM".format(QDir.homePath())) self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()), QSettings.IniFormat) self.devices = QSettings("{}/TDM/devices.cfg".format(QDir.homePath()), QSettings.IniFormat) self.setMinimumSize(QSize(1000, 600)) # configure logging logging.basicConfig(filename="{}/TDM/tdm.log".format(QDir.homePath()), level=self.settings.value("loglevel", "INFO"), datefmt="%Y-%m-%d %H:%M:%S", format='%(asctime)s [%(levelname)s] %(message)s') logging.info("### TDM START ###") # load devices from the devices file, create TasmotaDevices and add the to the envvironment for mac in self.devices.childGroups(): self.devices.beginGroup(mac) device = TasmotaDevice(self.devices.value("topic"), self.devices.value("full_topic"), self.devices.value("friendly_name")) device.debug = self.devices.value("debug", False, bool) device.p['Mac'] = mac.replace("-", ":") device.env = self.env self.env.devices.append(device) # load device command history self.devices.beginGroup("history") for k in self.devices.childKeys(): device.history.append(self.devices.value(k)) self.devices.endGroup() self.devices.endGroup() self.device_model = TasmotaDevicesModel(self.env) self.setup_mqtt() self.setup_main_layout() self.add_devices_tab() self.build_mainmenu() # self.build_toolbars() self.setStatusBar(QStatusBar()) pbSubs = QPushButton("Show subscriptions") pbSubs.setFlat(True) pbSubs.clicked.connect(self.showSubs) self.statusBar().addPermanentWidget(pbSubs) self.queue_timer = QTimer() self.queue_timer.timeout.connect(self.mqtt_publish_queue) self.queue_timer.start(250) self.auto_timer = QTimer() self.auto_timer.timeout.connect(self.auto_telemetry) self.load_window_state() if self.settings.value("connect_on_startup", False, bool): self.actToggleConnect.trigger() self.tele_docks = {} self.consoles = [] def setup_main_layout(self): self.mdi = QMdiArea() self.mdi.setActivationOrder(QMdiArea.ActivationHistoryOrder) self.mdi.setTabsClosable(True) self.setCentralWidget(self.mdi) def setup_mqtt(self): self.mqtt = MqttClient() self.mqtt.connecting.connect(self.mqtt_connecting) self.mqtt.connected.connect(self.mqtt_connected) self.mqtt.disconnected.connect(self.mqtt_disconnected) self.mqtt.connectError.connect(self.mqtt_connectError) self.mqtt.messageSignal.connect(self.mqtt_message) def add_devices_tab(self): self.devices_list = ListWidget(self) sub = self.mdi.addSubWindow(self.devices_list) sub.setWindowState(Qt.WindowMaximized) self.devices_list.deviceSelected.connect(self.selectDevice) self.devices_list.openConsole.connect(self.openConsole) self.devices_list.openRulesEditor.connect(self.openRulesEditor) self.devices_list.openTelemetry.connect(self.openTelemetry) self.devices_list.openWebUI.connect(self.openWebUI) def load_window_state(self): wndGeometry = self.settings.value('window_geometry') if wndGeometry: self.restoreGeometry(wndGeometry) def build_mainmenu(self): mMQTT = self.menuBar().addMenu("MQTT") self.actToggleConnect = QAction(QIcon(":/disconnect.png"), "Connect") self.actToggleConnect.setCheckable(True) self.actToggleConnect.toggled.connect(self.toggle_connect) mMQTT.addAction(self.actToggleConnect) mMQTT.addAction(QIcon(), "Broker", self.setup_broker) mMQTT.addAction(QIcon(), "Autodiscovery patterns", self.patterns) mMQTT.addSeparator() mMQTT.addAction(QIcon(), "Clear obsolete retained LWTs", self.clear_LWT) mMQTT.addSeparator() mMQTT.addAction(QIcon(), "Auto telemetry period", self.auto_telemetry_period) self.actToggleAutoUpdate = QAction(QIcon(":/auto_telemetry.png"), "Auto telemetry") self.actToggleAutoUpdate.setCheckable(True) self.actToggleAutoUpdate.toggled.connect(self.toggle_autoupdate) mMQTT.addAction(self.actToggleAutoUpdate) mSettings = self.menuBar().addMenu("Settings") mSettings.addAction(QIcon(), "BSSId aliases", self.bssid) mSettings.addSeparator() mSettings.addAction(QIcon(), "Preferences", self.prefs) # mExport = self.menuBar().addMenu("Export") # mExport.addAction(QIcon(), "OpenHAB", self.openhab) def build_toolbars(self): main_toolbar = Toolbar(orientation=Qt.Horizontal, iconsize=24, label_position=Qt.ToolButtonTextBesideIcon) main_toolbar.setObjectName("main_toolbar") def initial_query(self, device, queued=False): for c in initial_commands(): cmd, payload = c cmd = device.cmnd_topic(cmd) if queued: self.mqtt_queue.append([cmd, payload]) else: self.mqtt.publish(cmd, payload, 1) def setup_broker(self): brokers_dlg = BrokerDialog() if brokers_dlg.exec_() == QDialog.Accepted and self.mqtt.state == self.mqtt.Connected: self.mqtt.disconnect() def toggle_autoupdate(self, state): if state == True: if self.mqtt.state == self.mqtt.Connected: for d in self.env.devices: self.mqtt.publish(d.cmnd_topic('STATUS'), payload=8) self.auto_timer.setInterval(self.settings.value("autotelemetry", 5000, int)) self.auto_timer.start() else: self.auto_timer.stop() def toggle_connect(self, state): if state and self.mqtt.state == self.mqtt.Disconnected: self.broker_hostname = self.settings.value('hostname', 'localhost') self.broker_port = self.settings.value('port', 1883, int) self.broker_username = self.settings.value('username') self.broker_password = self.settings.value('password') self.mqtt.hostname = self.broker_hostname self.mqtt.port = self.broker_port if self.broker_username: self.mqtt.setAuth(self.broker_username, self.broker_password) self.mqtt.connectToHost() elif not state and self.mqtt.state == self.mqtt.Connected: self.mqtt_disconnect() def auto_telemetry(self): if self.mqtt.state == self.mqtt.Connected: for d in self.env.devices: self.mqtt.publish(d.cmnd_topic('STATUS'), payload=8) def mqtt_connect(self): self.broker_hostname = self.settings.value('hostname', 'localhost') self.broker_port = self.settings.value('port', 1883, int) self.broker_username = self.settings.value('username') self.broker_password = self.settings.value('password') self.mqtt.hostname = self.broker_hostname self.mqtt.port = self.broker_port if self.broker_username: self.mqtt.setAuth(self.broker_username, self.broker_password) if self.mqtt.state == self.mqtt.Disconnected: self.mqtt.connectToHost() def mqtt_disconnect(self): self.mqtt.disconnectFromHost() def mqtt_connecting(self): self.statusBar().showMessage("Connecting to broker") def mqtt_connected(self): self.actToggleConnect.setIcon(QIcon(":/connect.png")) self.actToggleConnect.setText("Disconnect") self.statusBar().showMessage("Connected to {}:{} as {}".format(self.broker_hostname, self.broker_port, self.broker_username if self.broker_username else '[anonymous]')) self.mqtt_subscribe() def mqtt_subscribe(self): # clear old topics self.topics.clear() custom_patterns.clear() # load custom autodiscovery patterns self.settings.beginGroup("Patterns") for k in self.settings.childKeys(): custom_patterns.append(self.settings.value(k)) self.settings.endGroup() # expand fulltopic patterns to subscribable topics for pat in default_patterns: # tasmota default and SO19 self.topics += expand_fulltopic(pat) # check if custom patterns can be matched by default patterns for pat in custom_patterns: if pat.startswith("%prefix%") or pat.split('/')[1] == "%prefix%": continue # do nothing, default subcriptions will match this topic else: self.topics += expand_fulltopic(pat) for d in self.env.devices: # if device has a non-standard pattern, check if the pattern is found in the custom patterns if not d.is_default() and d.p['FullTopic'] not in custom_patterns: # if pattern is not found then add the device topics to subscription list. # if the pattern is found, it will be matched without implicit subscription self.topics += expand_fulltopic(d.p['FullTopic']) # passing a list of tuples as recommended by paho self.mqtt.subscribe([(topic, 0) for topic in self.topics]) @pyqtSlot(str, str) def mqtt_publish(self, t, p): self.mqtt.publish(t, p) def mqtt_publish_queue(self): for q in self.mqtt_queue: t, p = q self.mqtt.publish(t, p) self.mqtt_queue.pop(self.mqtt_queue.index(q)) def mqtt_disconnected(self): self.actToggleConnect.setIcon(QIcon(":/disconnect.png")) self.actToggleConnect.setText("Connect") self.statusBar().showMessage("Disconnected") def mqtt_connectError(self, rc): reason = { 1: "Incorrect protocol version", 2: "Invalid client identifier", 3: "Server unavailable", 4: "Bad username or password", 5: "Not authorized", } self.statusBar().showMessage("Connection error: {}".format(reason[rc])) self.actToggleConnect.setChecked(False) def mqtt_message(self, topic, msg): # try to find a device by matching known FullTopics against the MQTT topic of the message device = self.env.find_device(topic) if device: if topic.endswith("LWT"): if not msg: msg = "Offline" device.update_property("LWT", msg) if msg == 'Online': # known device came online, query initial state self.initial_query(device, True) else: # forward the message for processing device.parse_message(topic, msg) if device.debug: logging.debug("MQTT: %s %s", topic, msg) else: # unknown device, start autodiscovery process if topic.endswith("LWT"): self.env.lwts.append(topic) logging.info("DISCOVERY: LWT from an unknown device %s", topic) # STAGE 1 # load default and user-provided FullTopic patterns and for all the patterns, # try matching the LWT topic (it follows the device's FullTopic syntax for p in default_patterns + custom_patterns: match = re.fullmatch(p.replace("%topic%", "(?P<topic>.*?)").replace("%prefix%", "(?P<prefix>.*?)") + ".*$", topic) if match: # assume that the matched topic is the one configured in device settings possible_topic = match.groupdict().get('topic') if possible_topic not in ('tele', 'stat'): # if the assumed topic is different from tele or stat, there is a chance that it's a valid topic # query the assumed device for its FullTopic. False positives won't reply. possible_topic_cmnd = p.replace("%prefix%", "cmnd").replace("%topic%", possible_topic) + "FullTopic" logging.debug("DISCOVERY: Asking an unknown device for FullTopic at %s", possible_topic_cmnd) self.mqtt_queue.append([possible_topic_cmnd, ""]) elif topic.endswith("RESULT") or topic.endswith("FULLTOPIC"): # reply from an unknown device # STAGE 2 full_topic = loads(msg).get('FullTopic') if full_topic: # the device replies with its FullTopic # here the Topic is extracted using the returned FullTopic, identifying the device parsed = parse_topic(full_topic, topic) if parsed: # got a match, we query the device's MAC address in case it's a known device that had its topic changed logging.debug("DISCOVERY: topic %s is matched by fulltopic %s", topic, full_topic) d = self.env.find_device(topic=parsed['topic']) if d: d.update_property("FullTopic", full_topic) else: logging.info("DISCOVERY: Discovered topic=%s with fulltopic=%s", parsed['topic'], full_topic) d = TasmotaDevice(parsed['topic'], full_topic) self.env.devices.append(d) self.device_model.addDevice(d) logging.debug("DISCOVERY: Sending initial query to topic %s", parsed['topic']) self.initial_query(d, True) self.env.lwts.remove(d.tele_topic("LWT")) d.update_property("LWT", "Online") def export(self): fname, _ = QFileDialog.getSaveFileName(self, "Export device list as...", directory=QDir.homePath(), filter="CSV files (*.csv)") if fname: if not fname.endswith(".csv"): fname += ".csv" with open(fname, "w", encoding='utf8') as f: column_titles = ['mac', 'topic', 'friendly_name', 'full_topic', 'cmnd_topic', 'stat_topic', 'tele_topic', 'module', 'module_id', 'firmware', 'core'] c = csv.writer(f) c.writerow(column_titles) for r in range(self.device_model.rowCount()): d = self.device_model.index(r,0) c.writerow([ self.device_model.mac(d), self.device_model.topic(d), self.device_model.friendly_name(d), self.device_model.fullTopic(d), self.device_model.commandTopic(d), self.device_model.statTopic(d), self.device_model.teleTopic(d), # modules.get(self.device_model.module(d)), self.device_model.module(d), self.device_model.firmware(d), self.device_model.core(d) ]) def bssid(self): BSSIdDialog().exec_() def patterns(self): PatternsDialog().exec_() # def openhab(self): # OpenHABDialog(self.env).exec_() def showSubs(self): QMessageBox.information(self, "Subscriptions", "\n".join(sorted(self.topics))) def clear_LWT(self): dlg = ClearLWTDialog(self.env) if dlg.exec_() == ClearLWTDialog.Accepted: for row in range(dlg.lw.count()): itm = dlg.lw.item(row) if itm.checkState() == Qt.Checked: topic = itm.text() self.mqtt.publish(topic, retain=True) self.env.lwts.remove(topic) logging.info("MQTT: Cleared %s", topic) def prefs(self): dlg = PrefsDialog() if dlg.exec_() == QDialog.Accepted: update_devices = False devices_short_version = self.settings.value("devices_short_version", True, bool) if devices_short_version != dlg.cbDevShortVersion.isChecked(): update_devices = True self.settings.setValue("devices_short_version", dlg.cbDevShortVersion.isChecked()) update_consoles = False console_font_size = self.settings.value("console_font_size", 9) if console_font_size != dlg.sbConsFontSize.value(): update_consoles = True self.settings.setValue("console_font_size", dlg.sbConsFontSize.value()) console_word_wrap = self.settings.value("console_word_wrap", True, bool) if console_word_wrap != dlg.cbConsWW.isChecked(): update_consoles = True self.settings.setValue("console_word_wrap", dlg.cbConsWW.isChecked()) if update_consoles: for c in self.consoles: c.console.setWordWrapMode(dlg.cbConsWW.isChecked()) new_font = QFont(c.console.font()) new_font.setPointSize(dlg.sbConsFontSize.value()) c.console.setFont(new_font) self.settings.sync() def auto_telemetry_period(self): curr_val = self.settings.value("autotelemetry", 5000, int) period, ok = QInputDialog.getInt(self, "Set AutoTelemetry period", "Values under 5000ms may cause increased ESP LoadAvg", curr_val, 1000) if ok: self.settings.setValue("autotelemetry", period) self.settings.sync() @pyqtSlot(TasmotaDevice) def selectDevice(self, d): self.device = d @pyqtSlot() def openTelemetry(self): if self.device: tele_widget = TelemetryWidget(self.device) self.addDockWidget(Qt.RightDockWidgetArea, tele_widget) self.mqtt_publish(self.device.cmnd_topic('STATUS'), "8") @pyqtSlot() def openConsole(self): if self.device: console_widget = ConsoleWidget(self.device) self.mqtt.messageSignal.connect(console_widget.consoleAppend) console_widget.sendCommand.connect(self.mqtt.publish) self.addDockWidget(Qt.BottomDockWidgetArea, console_widget) console_widget.command.setFocus() self.consoles.append(console_widget) @pyqtSlot() def openRulesEditor(self): if self.device: rules = RulesWidget(self.device) self.mqtt.messageSignal.connect(rules.parseMessage) rules.sendCommand.connect(self.mqtt_publish) self.mdi.setViewMode(QMdiArea.TabbedView) self.mdi.addSubWindow(rules) rules.setWindowState(Qt.WindowMaximized) rules.destroyed.connect(self.updateMDI) self.mqtt_queue.append((self.device.cmnd_topic("ruletimer"), "")) self.mqtt_queue.append((self.device.cmnd_topic("rule1"), "")) self.mqtt_queue.append((self.device.cmnd_topic("Var"), "")) self.mqtt_queue.append((self.device.cmnd_topic("Mem"), "")) @pyqtSlot() def openWebUI(self): if self.device and self.device.p.get('IPAddress'): url = QUrl("http://{}".format(self.device.p['IPAddress'])) try: webui = QWebEngineView() webui.load(url) frm_webui = QFrame() frm_webui.setWindowTitle("WebUI [{}]".format(self.device.p['FriendlyName1'])) frm_webui.setFrameShape(QFrame.StyledPanel) frm_webui.setLayout(VLayout(0)) frm_webui.layout().addWidget(webui) frm_webui.destroyed.connect(self.updateMDI) self.mdi.addSubWindow(frm_webui) self.mdi.setViewMode(QMdiArea.TabbedView) frm_webui.setWindowState(Qt.WindowMaximized) except NameError: QDesktopServices.openUrl(QUrl("http://{}".format(self.device.p['IPAddress']))) def updateMDI(self): if len(self.mdi.subWindowList()) == 1: self.mdi.setViewMode(QMdiArea.SubWindowView) self.devices_list.setWindowState(Qt.WindowMaximized) def closeEvent(self, e): self.settings.setValue("version", self._version) self.settings.setValue("window_geometry", self.saveGeometry()) self.settings.setValue("views_order", ";".join(self.devices_list.views.keys())) self.settings.beginGroup("Views") for view, items in self.devices_list.views.items(): self.settings.setValue(view, ";".join(items[1:])) self.settings.endGroup() self.settings.sync() for d in self.env.devices: mac = d.p.get('Mac') topic = d.p['Topic'] full_topic = d.p['FullTopic'] friendly_name = d.p['FriendlyName1'] if mac: self.devices.beginGroup(mac.replace(":", "-")) self.devices.setValue("topic", topic) self.devices.setValue("full_topic", full_topic) self.devices.setValue("friendly_name", friendly_name) for i, h in enumerate(d.history): self.devices.setValue("history/{}".format(i), h) self.devices.endGroup() self.devices.sync() e.accept()
class Config(QObject): "Configuration provider for the whole program, wapper for QSettings" row_height_changed = pyqtSignal(int) def __init__(self, log=None): super().__init__() if log: self.log = log.getChild('Conf') self.log.setLevel(30) else: self.log = logging.getLogger() self.log.setLevel(99) self.log.debug('Initializing') self.qsettings = QSettings() self.qsettings.setIniCodec('UTF-8') self.options = None self.option_spec = self.load_option_spec() self.options = self.load_options() self.full_name = "{} {}".format(QCoreApplication.applicationName(), QCoreApplication.applicationVersion()) # options that need fast access are also definded as attributes, which # are updated by calling update_attributes() # (on paper it's 4 times faster, but i don't think it matters in my case) self.logger_table_font = None self.logger_table_font_size = None self.loop_event_delay = None self.benchmark_interval = None self.update_attributes() def __getitem__(self, name): # self.log.debug('Getting "{}"'.format(name)) value = self.options.get(name, None) if value is None: raise Exception('No option with name "{}"'.format(name)) # self.log.debug('Returning "{}"'.format(value)) return value def __setitem__(self, name, value): # self.log.debug('Setting "{}"'.format(name)) if name not in self.options: raise Exception('No option with name "{}"'.format(name)) self.options[name] = value def set_option(self, name, value): self[name] = value @staticmethod def get_resource_path(name, directory='ui'): data_dir = resource_filename('cutelog', directory) path = os.path.join(data_dir, name) if not os.path.exists(path): raise FileNotFoundError( 'Resource file not found in this path: "{}"'.format(path)) return path def get_ui_qfile(self, name): file = QFile(':/ui/{}'.format(name)) if not file.exists(): raise FileNotFoundError( 'ui file not found: ":/ui/{}"'.format(name)) file.open(QFile.ReadOnly) return file @property def listen_address(self): host = self.options.get('listen_host', None) port = self.options.get('listen_port', None) if host is None or port is None: raise Exception( 'Listen host or port not in options: "{}:{}"'.format( host, port)) return (host, port) def load_option_spec(self): option_spec = [] for spec in OPTION_SPEC: option = Option(*spec) option_spec.append(option) return option_spec def load_options(self): self.log.debug('Loading options') options = {} self.qsettings.beginGroup('Configuration') for option in self.option_spec: value = self.qsettings.value(option.name, option.default) if option.type == bool: value = str(value).lower( ) # needed because QSettings stores bools as strings value = True if value == "true" or value is True else False else: value = option.type(value) options[option.name] = value self.qsettings.endGroup() return options def update_options(self, new_options, save=True): self.options.update(new_options) if save: self.save_options() self.update_attributes(new_options) def update_attributes(self, options=None): "Updates fast attributes and everything else outside of self.options" if options: # here will be things that only need to be updated when they actually changed new_row_height = options.get('logger_row_height', self.options['logger_row_height']) if new_row_height != self.options['logger_row_height']: self.row_height_changed.emit(new_row_height) else: options = self.options self.loop_event_delay = options.get('loop_event_delay', self.loop_event_delay) self.benchmark_interval = options.get('benchmark_interval', self.benchmark_interval) self.logger_table_font = options.get('logger_table_font', self.logger_table_font) self.logger_table_font_size = options.get('logger_table_font_size', self.logger_table_font_size) self.set_logging_level( options.get('console_logging_level', ROOT_LOG.level)) def save_options(self): self.log.debug('Saving options') self.qsettings.beginGroup('Configuration') for option in self.option_spec: self.qsettings.setValue(option.name, self.options[option.name]) self.qsettings.endGroup() self.sync() def sync(self): self.log.debug('Syncing QSettings') self.qsettings.sync() def set_settings_value(self, name, value): self.qsettings.beginGroup('Configuration') self.qsettings.setValue(name, value) self.qsettings.endGroup() def set_logging_level(self, level): global ROOT_LOG ROOT_LOG.setLevel(level) self.log.setLevel(level) # def save_levels_preset(self, levels, preset_name): # pass def get_header_presets(self): self.qsettings.beginGroup('Header_Presets') result = self.qsettings.childGroups() self.qsettings.endGroup() return result def save_header_preset(self, name, columns): self.log.debug('Saving header preset "{}"'.format(name)) s = self.qsettings s.beginGroup('Header_Presets') s.beginWriteArray(name, len(columns)) for i, col in enumerate(columns): s.setArrayIndex(i) s.setValue('column', col.dump_to_string()) s.endArray() s.endGroup() def load_header_preset(self, name): from .logger_table_header import Column self.log.debug('Loading header preset "{}"'.format(name)) s = self.qsettings result = [] if name not in self.get_header_presets(): return None s.beginGroup('Header_Presets') size = s.beginReadArray(name) for i in range(size): s.setArrayIndex(i) new_column = Column(load=s.value('column')) result.append(new_column) s.endArray() s.endGroup() return result def save_geometry(self, geometry): s = self.qsettings s.beginGroup('Geometry') s.setValue('Main_Window_Geometry', geometry) s.endGroup() self.sync() def load_geometry(self): s = self.qsettings s.beginGroup('Geometry') geometry = s.value('Main_Window_Geometry') s.endGroup() return geometry
class DocumentWindow(QMainWindow, ui_mainwindow.Ui_MainWindow): """DocumentWindow subclasses QMainWindow and Ui_MainWindow. It performs some initialization operations that must be done in code rather than using Qt Creator. Attributes: controller (DocumentController): """ def __init__(self, parent=None, doc_ctrlr=None): super(DocumentWindow, self).__init__(parent) self.controller = doc_ctrlr doc = doc_ctrlr.document() self.setupUi(self) self.settings = QSettings() # 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._initSliceview(doc) self._initGridview(doc) 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._restoreGeometryandState() self._finishInit() doc.setViewNames(['slice', 'path', 'inspector']) # end def def document(self): return self.controller.document() def destroyWin(self): self.settings.beginGroup("MainWindow") self.settings.setValue("state", self.saveState()) self.settings.endGroup() for mgr in self.tool_managers: mgr.destroy() self.controller = None ### ACCESSORS ### def undoStack(self): return self.controller.undoStack() def selectedInstance(self): return self.controller.document().selectedInstance() def activateSelection(self, isActive): self.path_graphics_view.activateSelection(isActive) self.slice_graphics_view.activateSelection(isActive) self.grid_graphics_view.activateSelection(isActive) ### EVENT HANDLERS ### def focusInEvent(self): """Handle an OS focus change into cadnano.""" app().undoGroup.setActiveStack(self.controller.undoStack()) def moveEvent(self, event): """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): """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): QWidget.changeEvent(self, event) # end def ### DRAWING RELATED ### ### PRIVATE HELPER METHODS ### def _restoreGeometryandState(self): self.settings.beginGroup("MainWindow") geometry = self.settings.value("geometry") state = self.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(self.settings.value("size", QSize(1100, 800))) self.move(self.settings.value("pos", QPoint(200, 200))) state = self.settings.value("state") if state is not None: result = self.restoreState(state) if result is False: print("MainWindow.restoreState() failed.") self.settings.endGroup() # end def def _initGridview(self, doc): """Initializes Grid View. Args: doc (cadnano.document.Document): The Document corresponding to the design Returns: None """ self.grid_scene = QGraphicsScene(parent=self.grid_graphics_view) self.grid_root = GridRootItem(rect=self.grid_scene.sceneRect(), parent=None, window=self, document=doc) self.grid_root.setFlag(QGraphicsItem.ItemHasNoContents) self.grid_scene.addItem(self.grid_root) self.grid_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert self.grid_root.scene() == self.grid_scene self.grid_graphics_view.setScene(self.grid_scene) self.grid_graphics_view.scene_root_item = self.grid_root self.grid_graphics_view.setName("GridView") self.grid_tool_manager = GridToolManager(self, self.grid_root) self.grid_dock_widget.hide() # end def def _initPathview(self, doc): """Initializes Path View. Args: doc (cadnano.document.Document): The Document corresponding to the design Returns: None """ self.path_scene = QGraphicsScene(parent=self.path_graphics_view) self.path_root = PathRootItem(rect=self.path_scene.sceneRect(), parent=None, window=self, document=doc) self.path_root.setFlag(QGraphicsItem.ItemHasNoContents) self.path_scene.addItem(self.path_root) self.path_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert self.path_root.scene() == self.path_scene self.path_graphics_view.setScene(self.path_scene) self.path_graphics_view.scene_root_item = self.path_root self.path_graphics_view.setScaleFitFactor(0.7) self.path_graphics_view.setName("PathView") # end def def _initPathviewToolbar(self): """Initializes Path View Toolbar. Returns: None """ self.path_color_panel = ColorPanel() self.path_graphics_view.toolbar = self.path_color_panel # HACK for customqgraphicsview self.path_scene.addItem(self.path_color_panel) self.path_tool_manager = PathToolManager(self, self.path_root) 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): """Initializes Slice View. Args: doc (cadnano.document.Document): The Document corresponding to the design Returns: None """ self.slice_scene = QGraphicsScene(parent=self.slice_graphics_view) self.slice_root = SliceRootItem(rect=self.slice_scene.sceneRect(), parent=None, window=self, document=doc) self.slice_root.setFlag(QGraphicsItem.ItemHasNoContents) self.slice_scene.addItem(self.slice_root) self.slice_scene.setItemIndexMethod(QGraphicsScene.NoIndex) assert self.slice_root.scene() == self.slice_scene self.slice_graphics_view.setScene(self.slice_scene) self.slice_graphics_view.scene_root_item = self.slice_root self.slice_graphics_view.setName("SliceView") self.slice_graphics_view.setScaleFitFactor(0.7) self.slice_tool_manager = SliceToolManager(self, self.slice_root) # end def def _initEditMenu(self): """Initializes the Edit menu Returns: None """ self.actionUndo = self.controller.undoStack().createUndoAction(self) self.actionRedo = self.controller.undoStack().createRedoAction(self) self.actionUndo.setText( QApplication.translate("MainWindow", "Undo", None)) self.actionUndo.setShortcut( QApplication.translate("MainWindow", "Ctrl+Z", None)) self.actionRedo.setText( QApplication.translate("MainWindow", "Redo", None)) self.actionRedo.setShortcut( QApplication.translate("MainWindow", "Ctrl+Shift+Z", None)) self.sep = QAction(self) self.sep.setSeparator(True) self.menu_edit.insertAction(self.sep, self.actionRedo) self.menu_edit.insertAction(self.actionRedo, self.actionUndo) # 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. """ # grid_visible = self.slice_dock_widget.isVisible() # self.action_grid.setChecked(console_visible) inspector_visible = self.inspector_dock_widget.widget().isVisibleTo( self) self.action_inspector.setChecked(inspector_visible) path_visible = self.slice_dock_widget.widget().isVisibleTo(self) self.action_path.setChecked(path_visible) slice_visible = self.slice_dock_widget.isVisibleTo(self) self.action_slice.setChecked(slice_visible)
def storeSettings(self): config = QSettings() config.beginGroup("WindowMain") config.setValue("size", self.size()) config.setValue("pos", self.pos()) config.endGroup()
class TestSettingsHandler(TestCase): def setUp(self): self.settings = QSettings("KhaleelKhan", "SettingsApp-unittest") self.settings.clear() self.form = SettingsHandler(settings=self.settings) self.form.show() def tearDown(self): self.form.close() del self.form def test_defaults(self): self.assertEqual(self.form.le_latex_tempate.text(), os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.assertEqual(self.form.le_text_template.text(), os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.assertEqual(self.form.le_latex_out_dir.text(), os.path.abspath('Latex/Output')) self.assertEqual(self.form.le_text_out_dir.text(), os.path.abspath('Text/Output')) self.assertTrue(self.form.cb_open_pdf_after.isChecked()) self.assertTrue(self.form.cb_open_text_after.isChecked()) self.assertTrue(self.form.cb_keep_tex.isChecked()) self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.assertEqual(self.form.le_custom_latex.text(), '') self.assertFalse(self.form.le_custom_latex.isEnabled()) self.assertTrue(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertTrue(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) self.assertTrue(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertTrue(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) def test_latex_compiler_changed(self): # reset value of combo box self.form.combo_latex_compiler.setCurrentText('xelatex') self.form.combo_latex_compiler.setCurrentText('custom') self.assertTrue(self.form.le_custom_latex.isEnabled()) self.form.combo_latex_compiler.setCurrentText('xelatex') self.assertFalse(self.form.le_custom_latex.isEnabled()) self.form.combo_latex_compiler.setCurrentText('pdflatex') self.assertFalse(self.form.le_custom_latex.isEnabled()) def test_write_settings_to_config(self): # Write dummy values to variables self.form.latex_template = "latex_template_test_write_settings" self.form.text_template = "text_template_test_write_settings" self.form.latex_dir = "latex_out_dir_test_write_settings" self.form.text_dir = "text_out_dir_test_write_settings" self.form.open_pdf = True self.form.open_text = True self.form.keep_tex = True self.form.latex_compiler = "xelatex" self.form.latex_custom_command = "custom_command_test_write_settings" self.form.write_settings_to_config() # Read from config to verify self.settings.beginGroup("Templates") self.assertEqual(self.settings.value("latex"), "latex_template_test_write_settings") self.assertEqual(self.settings.value("text"), "text_template_test_write_settings") self.settings.endGroup() self.settings.beginGroup("Outputs") self.assertEqual(self.settings.value("latex"), "latex_out_dir_test_write_settings") self.assertEqual(self.settings.value("text"), "text_out_dir_test_write_settings") self.settings.endGroup() self.settings.beginGroup("Misc") self.assertEqual(self.settings.value("open_pdf"), 'True') self.assertEqual(self.settings.value("open_text"), 'True') self.assertEqual(self.settings.value("keep_tex"), 'True') self.assertEqual(self.settings.value("latex_compiler"), "xelatex") self.assertEqual(self.settings.value("latex_custom_command"), "custom_command_test_write_settings") self.settings.endGroup() # Test for False self.form.open_pdf = False self.form.open_text = False self.form.keep_tex = False self.form.write_settings_to_config() self.settings.beginGroup("Misc") self.assertEqual(self.settings.value("open_pdf"), 'False') self.assertEqual(self.settings.value("open_text"), 'False') self.assertEqual(self.settings.value("keep_tex"), 'False') self.settings.endGroup() def test_read_settings_from_config(self): # Write dummy settings to config self.settings.beginGroup("Templates") self.settings.setValue("latex", "latex_template_test_read_settings") self.settings.setValue("text", "text_template_test_read_settings") self.settings.endGroup() self.settings.beginGroup("Outputs") self.settings.setValue("latex", "latex_dir_test_read_settings") self.settings.setValue("text", "text_dir_test_read_settings") self.settings.endGroup() self.settings.beginGroup("Misc") self.settings.setValue("open_pdf", "True") self.settings.setValue("open_text", "True") self.settings.setValue("keep_tex", "True") self.settings.setValue("latex_compiler", "xelatex") self.settings.setValue("latex_custom_command", "custom_command_test_read_settings") self.settings.endGroup() # Save to config self.settings.sync() self.form.read_settings_from_config() self.assertEqual(self.form.latex_template, "latex_template_test_read_settings") self.assertEqual(self.form.text_template, "text_template_test_read_settings") self.assertEqual(self.form.latex_dir, "latex_dir_test_read_settings") self.assertEqual(self.form.text_dir, "text_dir_test_read_settings") self.assertTrue(self.form.open_pdf) self.assertTrue(self.form.open_text) self.assertTrue(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'xelatex') self.assertEqual(self.form.latex_custom_command, "custom_command_test_read_settings") # Test for False self.settings.beginGroup("Misc") self.settings.setValue("open_pdf", "False") self.settings.setValue("open_text", "False") self.settings.setValue("keep_tex", "False") self.settings.setValue("latex_compiler", "custom") self.settings.setValue("latex_custom_command", "custom_command_test_read_settings") self.settings.endGroup() # Save to config self.settings.sync() self.form.read_settings_from_config() self.assertFalse(self.form.open_pdf) self.assertFalse(self.form.open_text) self.assertFalse(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'custom') self.assertEqual(self.form.latex_custom_command, "custom_command_test_read_settings") def test_get_latex_compiler(self): self.form.latex_compiler = 'custom' self.form.latex_custom_command = "test string" self.assertEqual(self.form.get_latex_compiler(), "test string") self.form.latex_compiler = 'xelatex' self.assertEqual(self.form.get_latex_compiler(), "xelatex") self.form.latex_compiler = 'pdflatex' self.assertEqual(self.form.get_latex_compiler(), "pdflatex") def test_invalid_combobox_setting(self): self.form.combo_latex_compiler.setCurrentText('xelatex') self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.form.combo_latex_compiler.setCurrentText('invalid') # Should still be same self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') def test_click_okay_button(self): self.reset_all_variables() self.reset_all_ui_elements() # Set ui elements self.form.le_latex_tempate.setText(os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.form.le_text_template.setText(os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.form.le_latex_out_dir.setText(os.path.abspath('Latex/Output')) self.form.le_text_out_dir.setText(os.path.abspath('Text/Output')) self.form.cb_open_pdf_after.setChecked(True) self.form.cb_open_text_after.setChecked(True) self.form.cb_keep_tex.setChecked(True) self.form.combo_latex_compiler.setCurrentText('custom') self.form.le_custom_latex.setText("test string") # Emulate click on OK button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Ok) QTest.mouseClick(button, QtCore.Qt.LeftButton) self.assertEqual(self.form.latex_template, os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.assertEqual(self.form.text_template, os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.assertEqual(self.form.latex_dir, os.path.abspath('Latex/Output')) self.assertEqual(self.form.text_dir, os.path.abspath('Text/Output')) self.assertTrue(self.form.open_pdf) self.assertTrue(self.form.open_text) self.assertTrue(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'custom') self.assertEqual(self.form.latex_custom_command, "test string") # Test for False self.form.cb_open_pdf_after.setChecked(False) self.form.cb_open_text_after.setChecked(False) self.form.cb_keep_tex.setChecked(False) self.form.combo_latex_compiler.setCurrentText('xelatex') # Emulate click on OK button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Ok) QTest.mouseClick(button, QtCore.Qt.LeftButton) self.assertFalse(self.form.open_pdf) self.assertFalse(self.form.open_text) self.assertFalse(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'xelatex') def test_click_apply_button(self): self.reset_all_variables() self.reset_all_ui_elements() # Set ui elements self.form.le_latex_tempate.setText(os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.form.le_text_template.setText(os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.form.le_latex_out_dir.setText(os.path.abspath('Latex/Output')) self.form.le_text_out_dir.setText(os.path.abspath('Text/Output')) self.form.cb_open_pdf_after.setChecked(True) self.form.cb_open_text_after.setChecked(True) self.form.cb_keep_tex.setChecked(True) self.form.combo_latex_compiler.setCurrentText('custom') self.form.le_custom_latex.setText("test string") # Emulate click on Apply button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Apply) QTest.mouseClick(button, QtCore.Qt.LeftButton) self.assertEqual(self.form.latex_template, os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.assertEqual(self.form.text_template, os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.assertEqual(self.form.latex_dir, os.path.abspath('Latex/Output')) self.assertEqual(self.form.text_dir, os.path.abspath('Text/Output')) self.assertTrue(self.form.open_pdf) self.assertTrue(self.form.open_text) self.assertTrue(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'custom') self.assertEqual(self.form.latex_custom_command, "test string") # Test for False self.form.cb_open_pdf_after.setChecked(False) self.form.cb_open_text_after.setChecked(False) self.form.cb_keep_tex.setChecked(False) self.form.combo_latex_compiler.setCurrentText('xelatex') # Emulate click on Apply button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Apply) QTest.mouseClick(button, QtCore.Qt.LeftButton) self.assertFalse(self.form.open_pdf) self.assertFalse(self.form.open_text) self.assertFalse(self.form.keep_tex) self.assertEqual(self.form.latex_compiler, 'xelatex') def test_click_cancel_button(self): self.reset_all_ui_elements() # Emulate click on Cancel button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Cancel) QTest.mouseClick(button, QtCore.Qt.LeftButton) # Test if everything is still default self.assertEqual(self.form.le_latex_tempate.text(), os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.assertEqual(self.form.le_text_template.text(), os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.assertEqual(self.form.le_latex_out_dir.text(), os.path.abspath('Latex/Output')) self.assertEqual(self.form.le_text_out_dir.text(), os.path.abspath('Text/Output')) self.assertTrue(self.form.cb_open_pdf_after.isChecked()) self.assertTrue(self.form.cb_open_text_after.isChecked()) self.assertTrue(self.form.cb_keep_tex.isChecked()) self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.assertEqual(self.form.le_custom_latex.text(), '') # Test for False self.form.cb_open_pdf_after.setChecked(False) self.form.cb_open_text_after.setChecked(False) self.form.cb_keep_tex.setChecked(False) self.form.combo_latex_compiler.setCurrentText('xelatex') # Emulate click on Cancel button button = self.form.latex_buttonBox.button(self.form.latex_buttonBox.Cancel) QTest.mouseClick(button, QtCore.Qt.LeftButton) # still nothing should be changed self.assertTrue(self.form.cb_open_pdf_after.isChecked()) self.assertTrue(self.form.cb_open_text_after.isChecked()) self.assertTrue(self.form.cb_keep_tex.isChecked()) self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.assertEqual(self.form.le_custom_latex.text(), '') def test_setting_window_closed(self): self.reset_all_ui_elements() # Emulate click on close button self.form.close() # Test if everything is still default self.assertEqual(self.form.le_latex_tempate.text(), os.path.abspath('Latex/Templates/Awesome-CV/Latex_template.tex')) self.assertEqual(self.form.le_text_template.text(), os.path.abspath('Text/Templates/Simple/Text_template.txt')) self.assertEqual(self.form.le_latex_out_dir.text(), os.path.abspath('Latex/Output')) self.assertEqual(self.form.le_text_out_dir.text(), os.path.abspath('Text/Output')) self.assertTrue(self.form.cb_open_pdf_after.isChecked()) self.assertTrue(self.form.cb_open_text_after.isChecked()) self.assertTrue(self.form.cb_keep_tex.isChecked()) self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.assertEqual(self.form.le_custom_latex.text(), '') # Test for False self.form.cb_open_pdf_after.setChecked(False) self.form.cb_open_text_after.setChecked(False) self.form.cb_keep_tex.setChecked(False) self.form.combo_latex_compiler.setCurrentText('xelatex') # Emulate click on close button self.form.close() # still nothing should be changed self.assertTrue(self.form.cb_open_pdf_after.isChecked()) self.assertTrue(self.form.cb_open_text_after.isChecked()) self.assertTrue(self.form.cb_keep_tex.isChecked()) self.assertEqual(self.form.combo_latex_compiler.currentText(), 'xelatex') self.assertEqual(self.form.le_custom_latex.text(), '') @mock.patch('CoverletterCreator.SettingsHandler.os.path') def test_verify_path_field(self, mock_path): mock_path.exists.return_value = True self.form.verify_path_field('test_path_true', self.form.icon_latex_template) mock_path.exists.assert_called_with('test_path_true') self.assertTrue(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertTrue(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertTrue(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) self.assertTrue(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) mock_path.exists.return_value = False self.form.verify_path_field('test_path_false', self.form.icon_latex_template) mock_path.exists.assert_called_with('test_path_false') self.assertFalse(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertFalse(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Ok).isEnabled()) self.assertFalse(self.form.latex_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) self.assertFalse(self.form.text_buttonBox.button(QtWidgets.QDialogButtonBox.Apply).isEnabled()) @mock.patch('CoverletterCreator.SettingsHandler.QFileDialog', autospec=True) def test_open_latex_template(self, mock_opendialog): mock_opendialog.getOpenFileName.return_value = 'test_open_latex_template_filename', 'filetype' self.form.open_latex_template() self.assertTrue(mock_opendialog.getOpenFileName.called) self.assertEqual(os.path.abspath('test_open_latex_template_filename'), self.form.le_latex_tempate.text()) # Simulate open dialog closed (canceled) mock_opendialog.getOpenFileName.return_value = '', '' self.form.open_latex_template() self.assertTrue(mock_opendialog.getOpenFileName.called) # no change self.assertEqual(os.path.abspath('test_open_latex_template_filename'), self.form.le_latex_tempate.text()) @mock.patch('CoverletterCreator.SettingsHandler.QFileDialog', autospec=True) def test_open_latex_dir(self, mock_opendialog): mock_opendialog.getExistingDirectory.return_value = 'test_open_latex_dir_folder' self.form.open_latex_dir() self.assertTrue(mock_opendialog.getExistingDirectory.called) self.assertEqual(os.path.abspath('test_open_latex_dir_folder'), self.form.le_latex_out_dir.text()) # Simulate open dialog closed (canceled) mock_opendialog.getExistingDirectory.return_value = '' self.form.open_latex_dir() self.assertTrue(mock_opendialog.getExistingDirectory.called) # no change self.assertEqual(os.path.abspath('test_open_latex_dir_folder'), self.form.le_latex_out_dir.text()) @mock.patch('CoverletterCreator.SettingsHandler.QFileDialog', autospec=True) def test_open_text_template(self, mock_opendialog): mock_opendialog.getOpenFileName.return_value = 'test_open_text_template_filename', 'filetype' self.form.open_text_template() self.assertTrue(mock_opendialog.getOpenFileName.called) self.assertEqual(os.path.abspath('test_open_text_template_filename'), self.form.le_text_template.text()) # Simulate open dialog closed (canceled) mock_opendialog.getOpenFileName.return_value = '', '' self.form.open_text_template() self.assertTrue(mock_opendialog.getOpenFileName.called) # no change self.assertEqual(os.path.abspath('test_open_text_template_filename'), self.form.le_text_template.text()) @mock.patch('CoverletterCreator.SettingsHandler.QFileDialog', autospec=True) def test_open_text_dir(self, mock_opendialog): mock_opendialog.getExistingDirectory.return_value = 'test_open_text_dir_folder' self.form.open_text_dir() self.assertTrue(mock_opendialog.getExistingDirectory.called) self.assertEqual(os.path.abspath('test_open_text_dir_folder'), self.form.le_text_out_dir.text()) # Simulate open dialog closed (canceled) mock_opendialog.getExistingDirectory.return_value = '' self.form.open_text_dir() self.assertTrue(mock_opendialog.getExistingDirectory.called) # no change self.assertEqual(os.path.abspath('test_open_text_dir_folder'), self.form.le_text_out_dir.text()) def reset_all_variables(self): # Reset all variables self.form.latex_template = "" self.form.text_template = "" self.form.latex_dir = "" self.form.text_dir = "" self.form.open_pdf = False self.form.open_text = False self.form.keep_tex = False self.form.latex_compiler = "" self.form.latex_custom_command = "" def reset_all_ui_elements(self): self.form.le_latex_tempate.setText("") self.form.le_text_template.setText("") self.form.le_latex_out_dir.setText("") self.form.le_text_out_dir.setText("") self.form.cb_open_pdf_after.setChecked(True) self.form.cb_open_text_after.setChecked(True) self.form.cb_keep_tex.setChecked(True) self.form.combo_latex_compiler.setCurrentText("") self.form.le_custom_latex.setText("")
download_path = persepolis_setting.value('download_path') folder_list = [download_path_temp, download_path] # add subfolders to folder_list if user checked subfolders check box in setting window. if persepolis_setting.value('subfolder') == 'yes': for folder in ['Audios', 'Videos', 'Others', 'Documents', 'Compressed']: folder_list.append(os.path.join(download_path, folder)) # create folders in folder_list for folder in folder_list: osCommands.makeDirs(folder) persepolis_setting.endGroup() # Browser integration for Firefox and chromium and google chrome for browser in ['chrome', 'chromium', 'opera', 'vivaldi', 'firefox']: browserIntegration(browser) # get locale and set ui direction locale = str(persepolis_setting.value('settings/locale')) # right to left languages rtl_locale_list = ['fa_IR'] # left to right languages ltr_locale_list = ['en_US', 'zh_CN', 'fr_FR']
download_path = persepolis_setting.value('download_path') folder_list = [download_path_temp, download_path] # add subfolders to folder_list if user checked subfolders check box in setting window. if persepolis_setting.value('subfolder') == 'yes': for folder in ['Audios', 'Videos', 'Others', 'Documents', 'Compressed']: folder_list.append(os.path.join(download_path, folder)) # create folders in folder_list for folder in folder_list: osCommands.makeDirs(folder) persepolis_setting.endGroup() # Browser integration for Firefox and chromium and google chrome for browser in ['chrome', 'chromium', 'opera', 'vivaldi', 'firefox']: browserIntegration(browser) # compatibility persepolis_version = float(persepolis_setting.value('version/version', 2.5)) if persepolis_version < 2.6: from persepolis.scripts.compatibility import compatibility try: compatibility() except Exception as e: # create an object for PersepolisDB persepolis_db = PersepolisDB()
class GreaseMonkeyJsObject(QObject): """ Class implementing the Python side for GreaseMonkey scripts. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent object @type QObject """ super(GreaseMonkeyJsObject, self).__init__(parent) self.__settings = None def setSettingsFile(self, name): """ Public method to set the settings file for the GreaseMonkey parameters. @param name name of the settings file @type str """ if self.__settings is not None: self.__settings.sync() self.__settings = None self.__settings = QSettings(name, QSettings.IniFormat) @pyqtSlot(str, str, str) def getValue(self, nspace, name, dValue): """ Public slot to get the value for the named variable for the identified script. @param nspace unique script id @type str @param name name of the variable @type str @param dValue default value @type str @return value for the named variable @rtype str """ vName = "GreaseMonkey-{0}/{1}".format(nspace, name) sValue = self.__settings.value(vName, dValue) if not sValue: return dValue return sValue @pyqtSlot(str, str, str) def setValue(self, nspace, name, value): """ Public slot to set the value for the named variable for the identified script. @param nspace unique script id @type str @param name name of the variable @type str @param value value to be set @type str @return flag indicating success @rtype bool """ vName = "GreaseMonkey-{0}/{1}".format(nspace, name) self.__settings.setValue(vName, value) self.__settings.sync() return True @pyqtSlot(str, str) def deleteValue(self, nspace, name): """ Public slot to set delete the named variable for the identified script. @param nspace unique script id @type str @param name name of the variable @type str @return flag indicating success @rtype bool """ vName = "GreaseMonkey-{0}/{1}".format(nspace, name) self.__settings.remove(vName) self.__settings.sync() return True @pyqtSlot(str) def listValues(self, nspace): """ Public slot to list the stored variables for the identified script. @param nspace unique script id @type str @return list of stored variables @rtype list of str """ nspaceName = "GreaseMonkey-{0}".format(nspace) self.__settings.beginGroup(nspaceName) keys = self.__settings.allKeys() self.__settings.endGroup() return keys @pyqtSlot(str) def setClipboard(self, text): """ Public slot to set some clipboard text. @param text text to be copied to the clipboard @type str """ QGuiApplication.clipboard().setText(text)
def restoreSettingsFromConfig(self): if self.oacmode == True: settings = self.settings else: settings = QSettings(QSettings.UserScope, "astrastudio", "OnAirScreen") # polulate text clock languages self.textClockLanguage.clear() self.textClockLanguage.addItems(self.textClockLanguages) # populate owm widget languages self.owmLanguage.clear() self.owmLanguage.addItems(ww.owm_languages.keys()) # populate owm units self.owmUnit.clear() self.owmUnit.addItems(ww.owm_units.keys()) settings.beginGroup("General") self.StationName.setText(settings.value('stationname', 'Radio Eriwan')) self.Slogan.setText( settings.value('slogan', 'Your question is our motivation')) self.setStationNameColor( self.getColorFromName(settings.value('stationcolor', '#FFAA00'))) self.setSloganColor( self.getColorFromName(settings.value('slogancolor', '#FFAA00'))) self.checkBox_UpdateCheck.setChecked( settings.value('updatecheck', False, type=bool)) self.updateKey.setEnabled( settings.value('updatecheck', False, type=bool)) self.label_28.setEnabled( settings.value('updatecheck', False, type=bool)) self.updateCheckNowButton.setEnabled( settings.value('updatecheck', False, type=bool)) self.checkBox_IncludeBetaVersions.setEnabled( settings.value('updatecheck', False, type=bool)) self.updateKey.setText(settings.value('updatekey', '')) self.checkBox_IncludeBetaVersions.setChecked( settings.value('updateincludebeta', False, type=bool)) settings.endGroup() settings.beginGroup("NTP") self.checkBox_NTPCheck.setChecked( settings.value('ntpcheck', True, type=bool)) self.NTPCheckServer.setText( settings.value('ntpcheckserver', 'pool.ntp.org')) settings.endGroup() settings.beginGroup("LEDS") self.setLEDInactiveBGColor( self.getColorFromName(settings.value('inactivebgcolor', '#222222'))) self.setLEDInactiveFGColor( self.getColorFromName( settings.value('inactivetextcolor', '#555555'))) settings.endGroup() settings.beginGroup("LED1") self.LED1.setChecked(settings.value('used', True, type=bool)) self.LED1Text.setText(settings.value('text', 'ON AIR')) self.LED1Demo.setText(settings.value('text', 'ON AIR')) self.setLED1BGColor( self.getColorFromName(settings.value('activebgcolor', '#FF0000'))) self.setLED1FGColor( self.getColorFromName(settings.value('activetextcolor', '#FFFFFF'))) self.LED1Autoflash.setChecked( settings.value('autoflash', False, type=bool)) self.LED1Timedflash.setChecked( settings.value('timedflash', False, type=bool)) settings.endGroup() settings.beginGroup("LED2") self.LED2.setChecked(settings.value('used', True, type=bool)) self.LED2Text.setText(settings.value('text', 'PHONE')) self.LED2Demo.setText(settings.value('text', 'PHONE')) self.setLED2BGColor( self.getColorFromName(settings.value('activebgcolor', '#DCDC00'))) self.setLED2FGColor( self.getColorFromName(settings.value('activetextcolor', '#FFFFFF'))) self.LED2Autoflash.setChecked( settings.value('autoflash', False, type=bool)) self.LED2Timedflash.setChecked( settings.value('timedflash', False, type=bool)) settings.endGroup() settings.beginGroup("LED3") self.LED3.setChecked(settings.value('used', True, type=bool)) self.LED3Text.setText(settings.value('text', 'DOORBELL')) self.LED3Demo.setText(settings.value('text', 'DOORBELL')) self.setLED3BGColor( self.getColorFromName(settings.value('activebgcolor', '#00C8C8'))) self.setLED3FGColor( self.getColorFromName(settings.value('activetextcolor', '#FFFFFF'))) self.LED3Autoflash.setChecked( settings.value('autoflash', False, type=bool)) self.LED3Timedflash.setChecked( settings.value('timedflash', False, type=bool)) settings.endGroup() settings.beginGroup("LED4") self.LED4.setChecked(settings.value('used', True, type=bool)) self.LED4Text.setText(settings.value('text', 'ARI')) self.LED4Demo.setText(settings.value('text', 'ARI')) self.setLED4BGColor( self.getColorFromName(settings.value('activebgcolor', '#FF00FF'))) self.setLED4FGColor( self.getColorFromName(settings.value('activetextcolor', '#FFFFFF'))) self.LED4Autoflash.setChecked( settings.value('autoflash', False, type=bool)) self.LED4Timedflash.setChecked( settings.value('timedflash', False, type=bool)) settings.endGroup() settings.beginGroup("Clock") self.clockDigital.setChecked(settings.value('digital', True, type=bool)) self.clockAnalog.setChecked( not settings.value('digital', True, type=bool)) self.showSeconds.setChecked( settings.value('showSeconds', False, type=bool)) self.staticColon.setChecked( settings.value('staticColon', False, type=bool)) self.useTextclock.setChecked( settings.value('useTextClock', True, type=bool)) self.setDigitalHourColor( self.getColorFromName(settings.value('digitalhourcolor', '#3232FF'))) self.setDigitalSecondColor( self.getColorFromName( settings.value('digitalsecondcolor', '#FF9900'))) self.setDigitalDigitColor( self.getColorFromName( settings.value('digitaldigitcolor', '#3232FF'))) self.logoPath.setText( settings.value( 'logopath', ':/astrastudio_logo/images/astrastudio_transparent.png')) settings.endGroup() settings.beginGroup("Network") self.udpport.setText(settings.value('udpport', '3310')) self.httpport.setText(settings.value('httpport', '8010')) settings.endGroup() settings.beginGroup("Formatting") self.dateFormat.setText( settings.value('dateFormat', 'dddd, dd. MMMM yyyy')) self.textClockLanguage.setCurrentIndex( self.textClockLanguage.findText( settings.value('textClockLanguage', 'English'))) self.time_am_pm.setChecked(settings.value('isAmPm', False, type=bool)) self.time_24h.setChecked( not settings.value('isAmPm', False, type=bool)) settings.endGroup() settings.beginGroup("WeatherWidget") self.owmWidgetEnabled.setChecked( settings.value('owmWidgetEnabled', False, type=bool)) self.owmAPIKey.setText(settings.value('owmAPIKey', "")) self.owmCityID.setText(settings.value('owmCityID', "2643743")) self.owmLanguage.setCurrentIndex( self.owmLanguage.findText(settings.value('owmLanguage', "English"))) self.owmUnit.setCurrentIndex( self.owmUnit.findText(settings.value('owmUnit', "Celsius"))) self.owmAPIKey.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) self.owmCityID.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) self.owmLanguage.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) self.owmUnit.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) self.owmTestAPI.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) self.owmTestOutput.setEnabled( settings.value('owmWidgetEnabled', False, type=bool)) settings.endGroup()
class DialogUpdater(QDialog): def __init__(self, parent=None): super(DialogUpdater, self).__init__(parent) # super(DialogUpdater, self).__init__(parent, Qt.CustomizeWindowHint | Qt.WindowTitleHint) self.ui = dialog_updater.Ui_DialogUpdater() self.ui.setupUi(self) self.ui.listWidget.setStyleSheet( "QListWidget { outline:0; background-color: transparent; }" "QListWidget::item {border-bottom: 1px solid #999;}" "QListWidget::item::selected { color: #fff; }") # self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint) self.worker = None self.cleaner = None self.setting = QSettings() self.updating = False self.domains = [] def process(self, data): # print(data) action, host, ip, secs, last = data if "end" == action and ip and host and secs and secs < 99999: if hasattr(self.parent(), "addParsedDomain"): self.parent().addParsedDomain(host, ip) if self.ui.listWidget.count() > 0: for index in range(self.ui.listWidget.count()): item = self.ui.listWidget.item(index) data = item.data(QtWidgets.QListWidgetItem.UserType) if data and data == host: # oldWidget = self.ui.listWidget.itemWidget(item) widget = self.makeDomainItemWidget(index, host, action, ip, secs) self.ui.listWidget.removeItemWidget(item) self.ui.listWidget.setItemWidget(item, widget) self.ui.listWidget.scrollToItem(item) # self.ui.listWidget.setCurrentItem(item) # itemIndex = self.ui.listWidget.indexFromItem(item) # self.ui.listWidget.update(itemIndex) if last: self.updating = False self.cleaner = CleanerThread() self.cleaner.signal.connect(self.close) self.cleaner.start() def makeDomainItemWidget(self, idx, domain, status=None, ip=None, ms=None): widget = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) no = QtWidgets.QLabel() # no.setStyleSheet('background-color:blue') no.setText(f"#{idx+1}") no.setAlignment(QtCore.Qt.AlignCenter) layout.addWidget(no) layout.setStretch(0, 1) name = QtWidgets.QLabel() name.setText(domain) # name.setStyleSheet('background-color:gray') layout.addWidget(name) layout.setStretch(1, 3) flag = QtWidgets.QPushButton() flag.setStyleSheet('background-color: transparent') flag.setFlat(True) if "end" == status: flag.setText('') if ip and ms < 99999: flag.setIcon(qta.icon('fa5.check-circle', color='green')) ms = "{:.3f}s".format(ms / 1000) else: flag.setIcon(qta.icon('fa5.times-circle', color='red')) ms = "" elif "begin" == status: flag.setText('') flag.setIcon( qta.icon('fa5s.sync', color='gray', animation=qta.Spin(self))) ip = "" ms = "" else: flag.setText('') ip = "" ms = "" layout.addWidget(flag) layout.setStretch(2, 1) ipLabel = QtWidgets.QLabel() # ipLabel.setStyleSheet('background-color:green') ipLabel.setText(ip) layout.addWidget(ipLabel) layout.setStretch(3, 2) timer = QtWidgets.QLabel() # timer.setStyleSheet('background-color:blue') timer.setText(ms) layout.addWidget(timer) layout.setStretch(4, 1) widget.setLayout(layout) return widget def showEvent(self, event: QtGui.QShowEvent) -> None: if "domains" in self.setting.childGroups(): self.setting.beginGroup("domains") self.domains = self.setting.childKeys() self.setting.endGroup() if not self.domains: self.domains = configurations.get("domains") if not self.updating and self.domains: self.updating = True self.ui.listWidget.clear() if hasattr(self.parent(), "resetParsedDomains"): self.parent().resetParsedDomains() itemSize = QtCore.QSize( self.ui.listWidget.width() - self.ui.listWidget.autoScrollMargin() * 2, 50) no = 0 for domain in self.domains: item = QtWidgets.QListWidgetItem() item.setSizeHint(itemSize) item.setData(QtWidgets.QListWidgetItem.UserType, domain) widget = self.makeDomainItemWidget(no, domain) self.ui.listWidget.addItem(item) self.ui.listWidget.setItemWidget(item, widget) no += 1 self.worker = WorkerThread(self.domains) self.worker.signal.connect(self.process) self.worker.start()
class Preferences(object): """Connect UI elements to the backend.""" def __init__(self): self.qs = QSettings("cadnano.org", "cadnano2.5") self.ui_prefs = Ui_Preferences() self.widget = QWidget() self.ui_prefs.setupUi(self.widget) self.readPreferences() self.widget.addAction(self.ui_prefs.actionClose) self.connectSignals() self.document = None # end def ### SUPPORT METHODS ### def connectSignals(self): ui_prefs = self.ui_prefs ui_prefs.actionClose.triggered.connect(self.hideDialog) ui_prefs.button_box.clicked.connect(self.handleButtonClick) ui_prefs.enabled_orthoview_combo_box.currentIndexChanged.connect( self.orthoviewChangedSlot) ui_prefs.gridview_style_combo_box.currentIndexChanged.connect( self.gridviewStyleChangedSlot) ui_prefs.show_icon_labels.clicked.connect(self.setShowIconLabels) ui_prefs.zoom_speed_slider.valueChanged.connect(self.setZoomSpeed) # end def def readPreferences(self): """Read the preferences from self.qs (a QSettings object) and set the preferences accordingly in the UI popup. """ qs = self.qs qs.beginGroup(PREFS_GROUP_NAME) self.gridview_style_idx = qs.value(GRIDVIEW_STYLE_KEY, GRIDVIEW_STYLE_DEFAULT) self.orthoview_style_idx = qs.value(ORTHOVIEW_KEY, ORTHOVIEW_DEFAULT) self.zoom_speed = qs.value(ZOOM_SPEED_KEY, ZOOM_SPEED_DEFAULT) self.show_icon_labels = qs.value(SHOW_ICON_LABELS_KEY, SHOW_ICON_LABELS_DEFAULT) qs.endGroup() ui_prefs = self.ui_prefs ui_prefs.gridview_style_combo_box.setCurrentIndex( self.gridview_style_idx) # print(self.orthoview_style_idx, ORTHOVIEW_DEFAULT, ORTHOVIEW_DEFAULT) ui_prefs.enabled_orthoview_combo_box.setCurrentIndex( self.orthoview_style_idx) ui_prefs.show_icon_labels.setChecked(self.show_icon_labels) ui_prefs.zoom_speed_slider.setProperty("value", self.zoom_speed) # end def ### SLOTS ### def hideDialog(self): self.widget.hide() # end def def showDialog(self): self.readPreferences() self.widget.show() # launch prefs in mode-less dialog # end def def gridviewStyleChangedSlot(self, index): """Update the grid when the type of grid is changed. Calls setGridviewStyleIdx and updates each part of the document accordingly. """ gridview_style = GRIDVIEW_STYLES[self.gridview_style_idx] for part in self.document.getParts(): part.partDocumentSettingChangedSignal.emit(part, 'grid', gridview_style) # end def def handleButtonClick(self, button): """Used only to restore defaults. Other buttons are ignored because connections are already set up in qt designer. """ if self.ui_prefs.button_box.buttonRole( button) == QDialogButtonBox.ResetRole: self.restoreDefaults() # end def def restoreDefaults(self): """Restore the default settings.""" gridview_style_idx = GRIDVIEW_STYLE_DEFAULT orthoview_idx = ORTHOVIEW_DEFAULT ui_prefs = self.ui_prefs ui_prefs.gridview_style_combo_box.setCurrentIndex(gridview_style_idx) ui_prefs.enabled_orthoview_combo_box.setCurrentIndex(orthoview_idx) ui_prefs.zoom_speed_slider.setProperty("value", ZOOM_SPEED_DEFAULT) ui_prefs.show_icon_labels.setChecked(SHOW_ICON_LABELS_DEFAULT) # end def def setGridviewStyleIdx(self, value): """Select the type of grid that should be displayed.""" self.gridview_style_idx = value self.qs.beginGroup(PREFS_GROUP_NAME) self.qs.setValue(GRIDVIEW_STYLE_KEY, value) self.qs.endGroup() # end def def orthoviewChangedSlot(self, view_idx: EnumType): """Handles index changes to enabled_orthoview_combo_box. Saves the setting and notifies the doc controller to toggle visibilty of appropriate 2D orthographic view (sliceview or gridview). """ assert isinstance(view_idx, EnumType) new_orthoview_style_idx = view_idx assert new_orthoview_style_idx in (OrthoViewEnum.GRID, OrthoViewEnum.SLICE) self.orthoview_style_idx = new_orthoview_style_idx self.qs.beginGroup(PREFS_GROUP_NAME) self.qs.setValue(ORTHOVIEW_KEY, new_orthoview_style_idx) self.qs.endGroup() controller = self.document.controller() controller.setSliceOrGridViewVisible(self.orthoview_style_idx) # end def def setShowIconLabels(self, value): self.show_icon_labels = value self.qs.beginGroup(PREFS_GROUP_NAME) self.qs.setValue(SHOW_ICON_LABELS_KEY, value) self.qs.endGroup() # end def def setZoomSpeed(self, value): self.zoom_speed = value self.qs.beginGroup(PREFS_GROUP_NAME) self.qs.setValue(ZOOM_SPEED_KEY, value) self.qs.endGroup()
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.actionExportStaplesSlot) 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) self.win.action_renumber.triggered.connect(self.actionRenumberSlot) ### 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.""" from ui.dialogs.ui_about import Ui_About 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 actionExportStaplesSlot(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() def actionAutostapleSlot(self): part = self.activePart() if part: self.win.path_graphics_view.setViewportUpdateOn(False) part.autoStaple() self.win.path_graphics_view.setViewportUpdateOn(True) 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): """docstring for actionAddHoneycombPartSlot""" part = self._document.addHoneycombPart() self.setActivePart(part) def actionAddSquarePartSlot(self): """docstring for actionAddSquarePartSlot""" part = self._document.addSquarePart() self.setActivePart(part) def actionRenumberSlot(self): coordList = self.win.pathroot.getSelectedPartOrderedVHList() part = self.activePart() part.renumber(coordList) # end def ### ACCESSORS ### def document(self): return self._document def window(self): return self.win def setDocument(self, doc): """ Sets the controller's document, and informs the document that this is its controller. """ self._document = doc doc.setController(self) def activePart(self): if self._active_part == None: self._active_part = self._document.selectedPart() return self._active_part def setActivePart(self, part): self._active_part = part def undoStack(self): return self._document.undoStack() ### 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() + '[*]') 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()) 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 os.path.isdir(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 = None app().document_controllers.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: assert(not self._has_no_associated_file) 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")
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 self._document.setController(self) self._file_open_path = None # will be set in _readSettings self._has_no_associated_file = True 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""" # print("new window", app().qApp.allWindows()) self.win = win = DocumentWindow(doc_ctrlr=self) app().documentWindowWasCreatedSignal.emit(self._document, win) self._connectWindowSignalsToSelf() win.show() app().active_document = self # Connect outliner with property editor o = win.outliner_widget p_e = win.property_widget o.itemSelectionChanged.connect(p_e.outlinerItemSelectionChanged) # self.actionFilterVirtualHelixSlot() self.actionFilterEndpointSlot() self.actionFilterXoverSlot() # setup tool exclusivity self.actiongroup = ag = QActionGroup(win) action_group_list = [ 'action_global_select', 'action_global_pencil', 'action_path_nick', 'action_path_paint', 'action_path_insertion', 'action_path_skip', 'action_path_add_seq', 'action_path_mods' ] for action_name in action_group_list: ag.addAction(getattr(win, action_name)) win.action_global_select.trigger() # end def def destroyDC(self): self.disconnectSignalsToSelf() if self.win is not None: self.win.destroyWin() self.win = None # end def def disconnectSignalsToSelf(self): win = self.win if win is not None: o = win.outliner_widget p_e = win.property_widget o.itemSelectionChanged.disconnect(p_e.outlinerItemSelectionChanged) for signal_obj, slot_method in self.self_signals: # print("dSS", slot_method.__name__) signal_obj.disconnect(slot_method) self.self_signals = [] # end def def _connectWindowSignalsToSelf(self): """This method serves to group all the signal & slot connections made by DocumentController""" win = self.win win.closeEvent = self.windowCloseEventHandler self.self_signals = [ (win.action_new.triggered, self.actionNewSlot), (win.action_open.triggered, self.actionOpenSlot), (win.action_close.triggered, self.actionCloseSlot), (win.action_save.triggered, self.actionSaveSlot), (win.action_save_as.triggered, self.actionSaveAsSlot), (win.action_SVG.triggered, self.actionSVGSlot), (win.action_export_staples.triggered, self.actionExportSequencesSlot), (win.action_preferences.triggered, self.actionPrefsSlot), (win.action_outliner.triggered, self.actionToggleOutlinerSlot), (win.action_new_dnapart.triggered, self.actionCreateNucleicAcidPart), (win.action_new_dnapart.triggered, lambda: win.action_global_pencil.trigger()), (win.action_about.triggered, self.actionAboutSlot), (win.action_cadnano_website.triggered, self.actionCadnanoWebsiteSlot), (win.action_feedback.triggered, self.actionFeedbackSlot), # make it so select tool in slice view activation turns on vh filter (win.action_filter_handle.triggered, self.actionFilterVirtualHelixSlot), (win.action_global_select.triggered, self.actionSelectForkSlot), (win.action_global_pencil.triggered, self.actionCreateForkSlot), (win.action_filter_endpoint.triggered, self.actionFilterEndpointSlot), (win.action_filter_strand.triggered, self.actionFilterStrandSlot), (win.action_filter_xover.triggered, self.actionFilterXoverSlot), (win.action_filter_fwd.triggered, self.actionFilterFwdSlot), (win.action_filter_rev.triggered, self.actionFilterRevSlot), (win.action_path_add_seq.triggered, self.actionPathAddSeqSlot), (win.action_vhelix_snap.triggered, self.actionVhelixSnapSlot) ] for signal_obj, slot_method in self.self_signals: signal_obj.connect(slot_method) # end def ### SLOTS ### def actionSelectForkSlot(self): win = self.win win.action_path_select.trigger() win.action_vhelix_select.trigger() # end def def actionCreateForkSlot(self): win = self.win win.action_vhelix_create.trigger() win.action_path_pencil.trigger() # end def def actionVhelixSnapSlot(self, state): for item in self.win.sliceroot.instance_items.values(): item.griditem.allow_snap = state # end def def undoStackCleanChangedSlot(self): """The title changes to include [*] on modification. Use this when clearing out undostack to set the modified status of the document """ self.win.setWindowModified(not self.undoStack().isClean()) self.win.setWindowTitle(self.documentTitle()) # end def 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"] """list: String names of enabled filter types.""" def actionFilterVirtualHelixSlot(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) types = ["virtual_helix"] self._document.setFilterSet(types) # end def 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) fS.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 actionFilterFwdSlot(self): """Remains checked if no other strand-type filter is active.""" f_fwd = self.win.action_filter_fwd f_rev = self.win.action_filter_rev if not f_fwd.isChecked() and not f_rev.isChecked(): f_fwd.setChecked(True) self._strandFilterUpdate() def actionFilterRevSlot(self): """Remains checked if no other strand-type filter is active.""" f_fwd = self.win.action_filter_fwd f_rev = self.win.action_filter_rev if not f_fwd.isChecked() and not f_rev.isChecked(): f_rev.setChecked(True) self._strandFilterUpdate() # end def def _strandFilterUpdate(self): win = self.win filter_list = [] add_oligo = False if win.action_filter_endpoint.isChecked(): filter_list.append("endpoint") # filter_list.append("strand") add_oligo = True # if win.action_filter_strand.isChecked(): # filter_list.append("strand") # add_oligo = True if win.action_filter_xover.isChecked(): filter_list.append("xover") add_oligo = True if win.action_filter_fwd.isChecked(): filter_list.append("forward") add_oligo = True if win.action_filter_rev.isChecked(): filter_list.append("reverse") add_oligo = True if add_oligo: filter_list.append("oligo") self._document.setFilterSet(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.children()) == 0: return # no parts if self.maybeSave() is False: return # user canceled in maybe save else: # user did not cancel if self.filesavedialog is not 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. 3. Downstream, the file is selected in openAfterMaybeSave, and the selected file is actually opened in openAfterMaybeSaveCallback. """ if self.maybeSave() is False: return # user canceled in maybe save else: # user did not cancel if hasattr(self, "filesavedialog"): # user did save if self.filesavedialog is not None: self.filesavedialog.finished.connect( self.openAfterMaybeSave) else: self.openAfterMaybeSave() # windows else: # user did not save self.openAfterMaybeSave() # finalize new def actionCloseSlot(self): """Called when DocumentWindow is closed""" # if util.isWindows(): # traceback.print_stack() the_app = app() self.destroyDC() if the_app.document_controllers: # check we haven't done this already # print("App Closing") the_app.destroyApp() # print("App closed") #end def 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 is 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 is not 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._document.activePart() if part is None: return loop_olgs = part.getLoopOligos() if loop_olgs: from cadnano.gui.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 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 loop_olgs: o.applyColor(styles.stapColors[0]) dialog.exec_() return # Proceed with staple export. fname = self.fileName() if fname is 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, "(*.txt)") self.saveStaplesDialog = None self.exportStaplesCallback(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "%s - Export As" % QApplication.applicationName(), directory, "(*.txt)") 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 actionPathAddSeqSlot(self): print("triggered seqslot") ap = self._document.activePart() if ap is not None: self._document.activePart().setAbstractSequences(emit_signals=True) # end def def actionPrefsSlot(self): app().prefsClicked() # end def def actionModifySlot(self): """ Notifies that part root items that parts should respond to modifier selection signals. """ pass def actionCreateNucleicAcidPart(self): if ONLY_ONE: self.newDocument() # only allow one part for now doc = self._document part = doc.createNucleicAcidPart() active_part = doc.activePart() if active_part is not None: active_part.setActive(False) doc.deactivateActivePart() part.setActive(True) doc.setActivePart(part) return part # end def def actionToggleOutlinerSlot(self): outliner = self.win.outliner_property_splitter if outliner.isVisible(): outliner.hide() else: outliner.show() # 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 undoStack(self): return self._document.undoStack() # end def ### PRIVATE SUPPORT METHODS ### def newDocument(self, fname=None): """Creates a new Document, reusing the DocumentController. Tells all of the views to reset and removes all items from them """ if fname is not None and self.fileName() == fname: setReopen(True) self._document.makeNew() self._has_no_associated_file = fname is None self.win.setWindowTitle(self.documentTitle() + '[*]') # end def def saveFileDialog(self): fname = self.fileName() if fname is 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 is not 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. Args: selected (Tuple, List or str): if a List or Tuple, the filename should be the first element """ if isinstance(selected, (list, tuple)): fname = selected[0] else: fname = selected # Return if fname is '', None, or a directory path if not fname or fname is None or os.path.isdir(fname): return False if not fname.lower().endswith(".txt"): fname += ".txt" if self.saveStaplesDialog is not None: self.saveStaplesDialog.filesSelected.disconnect( self.exportStaplesCallback) # manual garbage collection to prevent hang (in osx) del self.saveStaplesDialog self.saveStaplesDialog = None # write the file ap = self._document.activePart() if ap is not None: output = ap.getSequences() 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 is not 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) # NC commented out single document stuff if ONLY_ONE: self.newDocument(fname=fname) self._document.readFile(fname) 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 is not None: self.fileopendialog.filesSelected.disconnect( self.openAfterMaybeSaveCallback) # manual garbage collection to prevent hang (in osx) del self.fileopendialog self.fileopendialog = None self.setFileName(fname) 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 is not 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() else: event.ignore() self.actionCloseSlot() # if self.win is not None: # QMainWindow.closeEvent(self.win, event) # end def ### 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._document.fileName() def setFileName(self, proposed_fname): if self.fileName() == proposed_fname: return True self._document.setFileName(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 *.c25)") self.filesavedialog = None self.openAfterMaybeSaveCallback(fname) else: # access through non-blocking callback fdialog = QFileDialog( self.win, "Open Document", path, "cadnano1 / cadnano2 Files (*.nno *.json *.c25)") 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 is None or filename == '': if self._has_no_associated_file: return False filename = self.fileName() try: self._document.writeToFile(filename) except: 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() raise 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")