示例#1
0
文件: preferences.py 项目: jni/cecog
    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()
示例#2
0
 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()
示例#4
0
    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()))
示例#5
0
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()
示例#6
0
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}
示例#7
0
 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()
示例#8
0
 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()
示例#9
0
    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()
示例#10
0
    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()
示例#11
0
 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()
示例#12
0
文件: config.py 项目: rhoef/afw
    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()
示例#13
0
 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()
示例#14
0
 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()
示例#15
0
文件: config.py 项目: manerotoni/afw
    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()
示例#16
0
 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()
示例#17
0
    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()
示例#18
0
    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()
示例#19
0
文件: config.py 项目: rhoef/afw
    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()
示例#20
0
    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)
示例#22
0
    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()
示例#23
0
 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()
示例#24
0
    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()
示例#25
0
文件: config.py 项目: manerotoni/afw
    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()
示例#26
0
 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)
示例#27
0
    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()
示例#28
0
 def saveSettings(self) -> None:
     self.incrementTip()
     settings = QSettings()
     settings.beginGroup("DidYouKnowWindow")
     settings.setValue("windowSize", self.size())
     settings.endGroup()
示例#29
0
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()
示例#30
0
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)
示例#31
0
    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()
示例#32
0
    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()
示例#33
0
    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()
示例#34
0
文件: models.py 项目: peterwup/tdm
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)
示例#35
0
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)
示例#36
0
    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)
示例#37
0
文件: Devices.py 项目: xninjax/tdm
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
示例#39
0
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)
示例#40
0
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
示例#41
0
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()
示例#42
0
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")
示例#43
0
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)
示例#44
0
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())
示例#45
0
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()
示例#46
0
    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()
示例#47
0
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)
示例#48
0
文件: tdmgr.py 项目: awwolf/tdm
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()
示例#49
0
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
示例#50
0
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)
示例#51
0
 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("")
示例#53
0
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']
示例#54
0
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()
示例#55
0
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()
示例#57
0
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()
示例#58
0
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()
示例#59
0
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")
示例#60
0
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")