Esempio n. 1
0
    def addShowActions(self):
        """Adds a submenu giving access to the (other)
        opened viewer documents"""
        mds = self._actionCollection.viewer_document_select
        docs = mds.viewdocs()
        document_actions = {}
        multi_docs = len(docs) > 1
        if self._panel.widget().currentViewdoc():
            current_doc_filename = self._panel.widget().currentViewdoc().filename()

        m = self._menu
        sm = QMenu(m)
        sm.setTitle(_("Show..."))
        sm.setEnabled(multi_docs)
        ag = QActionGroup(m)
        ag.triggered.connect(self._panel.slotShowViewdoc)

        for d in docs:
            action = QAction(sm)
            action.setText(d.name())
            action._document_filename = d.filename()
            # TODO: Tooltips aren't shown by Qt (it seems)
            action.setToolTip(d.filename())
            action.setCheckable(True)
            action.setChecked(d.filename() == current_doc_filename)

            ag.addAction(action)
            sm.addAction(action)

        m.addSeparator()
        m.addMenu(sm)
def setup_menu():
    u"""
    Add a submenu to a view menu.

    Add a submenu that lists the available extra classes to the view
    menu, creating that menu when neccessary
    """
    if extra_classes_list:
        try:
            mw.addon_view_menu
        except AttributeError:
            mw.addon_view_menu = QMenu(_(u"&View"), mw)
            mw.form.menubar.insertMenu(
                mw.form.menuTools.menuAction(), mw.addon_view_menu)
        mw.extra_class_submenu = QMenu(u"Mode (e&xtra class)", mw)
        mw.addon_view_menu.addMenu(mw.extra_class_submenu)
        action_group = QActionGroup(mw, exclusive=True)
        no_class_action = action_group.addAction(
            QAction('(none/standard)', mw, checkable=True))
        no_class_action.setChecked(True)
        mw.extra_class_submenu.addAction(no_class_action)
        mw.connect(no_class_action, SIGNAL("triggered()"),
                   lambda: set_extra_class(None))
        for ecd in extra_classes_list:
            nn_class_action = action_group.addAction(
                QAction(ecd['display'], mw, checkable=True))
            mw.extra_class_submenu.addAction(nn_class_action)
            mw.connect(nn_class_action, SIGNAL("triggered()"),
                       lambda ec=ecd['class']: set_extra_class(ec))
Esempio n. 3
0
    def generateViewMenu(self):

        values = [
            (self.tr("Nothing"), "Nothing"),
            (self.tr("POV"), "POV"),
            (self.tr("Label"), "Label"),
            (self.tr("Progress"), "Progress"),
            (self.tr("Compile"), "Compile"),
        ]

        menus = [
            (self.tr("Tree"), "Tree"),
            (self.tr("Index cards"), "Cork"),
            (self.tr("Outline"), "Outline")
        ]

        submenus = {
            "Tree": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
            "Cork": [
                (self.tr("Icon"), "Icon"),
                (self.tr("Text"), "Text"),
                (self.tr("Background"), "Background"),
                (self.tr("Border"), "Border"),
                (self.tr("Corner"), "Corner"),
            ],
            "Outline": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
        }

        self.menuView.clear()
        self.menuView.addMenu(self.menuMode)
        self.menuView.addSeparator()

        # print("Generating menus with", settings.viewSettings)

        for mnu, mnud in menus:
            m = QMenu(mnu, self.menuView)
            for s, sd in submenus[mnud]:
                m2 = QMenu(s, m)
                agp = QActionGroup(m2)
                for v, vd in values:
                    a = QAction(v, m)
                    a.setCheckable(True)
                    a.setData("{},{},{}".format(mnud, sd, vd))
                    if settings.viewSettings[mnud][sd] == vd:
                        a.setChecked(True)
                    a.triggered.connect(self.setViewSettingsAction, AUC)
                    agp.addAction(a)
                    m2.addAction(a)
                m.addMenu(m2)
            self.menuView.addMenu(m)
Esempio n. 4
0
 def _create_actions(self):
     group = QActionGroup(self)
     self.grid_action = QAction(
         '&Grid', self, shortcut='ctrl+G', triggered=self.show_grid,
         checkable=True, icon=load_icon(':/icons/show_grid.png')
     )
     self.grid_action.setChecked(True)
     group.addAction(self.grid_action)
     self.expanded_action = QAction(
         '&Expanded', self, shortcut='ctrl+E', triggered=self.show_expanded,
         checkable=True, icon=load_icon(':/icons/show_expanded.png')
     )
     group.addAction(self.expanded_action)
Esempio n. 5
0
def on_main_window_start(main_window):
    main_window.theme_menu = main_window.menuBar().addMenu(
        main_window.tr('Themes'))
    themes_directory = QFileInfo('themes')
    if themes_directory.exists():
        active_theme = ThemeManager.get_active_theme()
        path = themes_directory.absoluteFilePath()
        group_action = QActionGroup(main_window)
        group_action.setExclusive(True)
        for theme in os.listdir(path):
            action = QAction(theme, main_window)
            action.setData(theme)
            action.setCheckable(True)
            if theme == active_theme:
                action.setChecked(True)
            action.changed.connect(ThemeManager.wrapper(main_window))
            group_action.addAction(action)
            group_action.addAction(action)
            main_window.theme_menu.addAction(action)
Esempio n. 6
0
 def _create_actions(self):
     group = QActionGroup(self)
     self.grid_action = QAction(
         "&Grid",
         self,
         shortcut="ctrl+G",
         triggered=self.show_grid,
         checkable=True,
         icon=load_icon(":/icons/show_grid.png"),
     )
     self.grid_action.setChecked(True)
     group.addAction(self.grid_action)
     self.expanded_action = QAction(
         "&Expanded",
         self,
         shortcut="ctrl+E",
         triggered=self.show_expanded,
         checkable=True,
         icon=load_icon(":/icons/show_expanded.png"),
     )
     group.addAction(self.expanded_action)
Esempio n. 7
0
    def __init__(self, win):
        super(PathToolManager, self).__init__()
        self.window = win
        self._active_tool = None
        self._active_part = None
        self.select_tool = SelectTool(self)
        self.pencil_tool = PencilTool(self)
        self.break_tool = BreakTool(self)
        self.erase_tool = EraseTool(self)
        self.insertion_tool = InsertionTool(self)
        self.skip_tool = SkipTool(self)
        self.paint_tool = PaintTool(self) # (self, win.path_graphics_view.toolbar)
        self.add_seq_tool = AddSeqTool(self)
        self.mods_tool = ModsTool(self)

        def installTool(tool_name, window):
            l_tool_name = tool_name.lower()
            tool_widget = getattr(window, 'action_path_' + l_tool_name)
            tool = getattr(self, l_tool_name + '_tool')
            tool.action_name = 'action_path_' + tool_name

            def clickHandler(self):
                tool_widget.setChecked(True)
                self.setActiveTool(tool)
                if hasattr(tool, 'widgetClicked'):
                    tool.widgetClicked()
            # end def

            select_tool_method_name = 'choose' + tool_name + 'Tool'
            setattr(self.__class__, select_tool_method_name, clickHandler)
            handler = getattr(self, select_tool_method_name)
            tool_widget.triggered.connect(handler)
            return tool_widget
        # end def
        tools = ('Select', 'Pencil', 'Break', 'Erase', 'Insertion', 'Skip', 'Paint', 'Add_Seq', 'Mods')
        ag = QActionGroup(win)
        # Call installTool on every tool
        list(map((lambda tool_name: ag.addAction(installTool(tool_name, win))), tools))
        ag.setExclusive(True)
        # Select the preferred Startup tool
        startup_tool_name = app().prefs.getStartupToolName()
        getattr(self, 'choose' + startup_tool_name + 'Tool')()
Esempio n. 8
0
class SearchOptionsButton(QPushButton):
	def __init__(self, **kwargs):
		super(SearchOptionsButton, self).__init__(**kwargs)

		self.setText(self.tr('Options'))

		menu = QMenu()
		self.actionCi = menu.addAction(self.tr('Case insensitive'))

		menu.addSeparator()
		self.actionFormat = QActionGroup(self)
		self.actionPlain = menu.addAction(self.tr('Plain text'))
		self.actionPlain.setEnabled(False)
		self.actionRe = menu.addAction(self.tr('Regular expression'))
		self.actionGlob = menu.addAction(self.tr('Glob pattern'))
		self.actionGlob.setEnabled(False)
		self.actionFormat.addAction(self.actionPlain)
		self.actionFormat.addAction(self.actionRe)
		self.actionFormat.addAction(self.actionGlob)

		self.actionRoot = menu.addAction(self.tr('Search in best root dir'))

		for act in [self.actionCi, self.actionRe, self.actionPlain, self.actionGlob, self.actionRoot]:
			act.setCheckable(True)
		self.actionRe.setChecked(True)

		self.setMenu(menu)

	def shouldFindRoot(self):
		return self.actionRoot.isChecked()

	def caseSensitive(self):
		return not self.actionCi.isChecked()

	def reFormat(self):
		if self.actionPlain.isChecked():
			return QRegExp.FixedString
		elif self.actionRe.isChecked():
			return QRegExp.RegExp
		elif self.actionGlob.isChecked():
			return QRegExp.WildcardUnix
Esempio n. 9
0
class revisions(QWidget, Ui_revisions):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.setupUi(self)
        self.splitter.setStretchFactor(0, 5)
        self.splitter.setStretchFactor(1, 70)

        self.listDelegate = listCompleterDelegate(self)
        self.list.setItemDelegate(self.listDelegate)
        self.list.itemClicked.connect(self.showDiff)
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(self.popupMenu)
        self.btnDelete.setEnabled(False)
        self.btnDelete.clicked.connect(self.delete)
        self.btnRestore.clicked.connect(self.restore)
        self.btnRestore.setEnabled(False)

        # self.list.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.updateTimer = QTimer()
        self.updateTimer.setSingleShot(True)
        self.updateTimer.setInterval(500)
        self.updateTimer.timeout.connect(self.update)
        self.updateTimer.stop()

        self.menu = QMenu(self)
        self.actGroup = QActionGroup(self)

        self.actShowDiff = QAction(self.tr("Show modifications"), self.menu)
        self.actShowDiff.setCheckable(True)
        self.actShowDiff.setChecked(True)
        self.actShowDiff.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowDiff)
        self.actGroup.addAction(self.actShowDiff)

        self.actShowVersion = QAction(self.tr("Show ancient version"), self.menu)
        self.actShowVersion.setCheckable(True)
        self.actShowVersion.setChecked(False)
        self.actShowVersion.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowVersion)
        self.actGroup.addAction(self.actShowVersion)

        self.menu.addSeparator()
        self.actShowSpaces = QAction(self.tr("Show spaces"), self.menu)
        self.actShowSpaces.setCheckable(True)
        self.actShowSpaces.setChecked(False)
        self.actShowSpaces.triggered.connect(self.showDiff)
        self.menu.addAction(self.actShowSpaces)

        self.actDiffOnly = QAction(self.tr("Show modifications only"), self.menu)
        self.actDiffOnly.setCheckable(True)
        self.actDiffOnly.setChecked(True)
        self.actDiffOnly.triggered.connect(self.showDiff)
        self.menu.addAction(self.actDiffOnly)
        self.btnOptions.setMenu(self.menu)

        self._model = None
        self._index = None

    def setModel(self, model):
        self._model = model
        self._model.dataChanged.connect(self.updateMaybe)

    def setCurrentModelIndex(self, index):
        self._index = index
        self.view.setText("")
        self.update()

    def updateMaybe(self, topLeft, bottomRight):
        if self._index and \
                                topLeft.column() <= Outline.revisions <= bottomRight.column() and \
                                topLeft.row() <= self._index.row() <= bottomRight.row():
            # self.update()
            self.updateTimer.start()

    def update(self):
        self.list.clear()
        item = self._index.internalPointer()
        rev = item.revisions()
        # Sort revisions
        rev = sorted(rev, key=lambda x: x[0], reverse=True)
        for r in rev:
            timestamp = datetime.datetime.fromtimestamp(r[0]).strftime('%Y-%m-%d %H:%M:%S')
            readable = self.readableDelta(r[0])
            i = QListWidgetItem(readable)
            i.setData(Qt.UserRole, r[0])
            i.setData(Qt.UserRole + 1, timestamp)
            self.list.addItem(i)

    def readableDelta(self, timestamp):
        now = datetime.datetime.now()
        delta = now - datetime.datetime.fromtimestamp(timestamp)
        if delta.days > 365:
            return self.tr("{} years ago").format(str(int(delta.days / 365)))
        elif delta.days > 30:
            return self.tr("{} months ago").format(str(int(delta.days / 30.5)))
        elif delta.days > 0:
            return self.tr("{} days ago").format(str(delta.days))
        if delta.days == 1:
            return self.tr("1 day ago")
        elif delta.seconds > 60 * 60:
            return self.tr("{} hours ago").format(str(int(delta.seconds / 60 / 60)))
        elif delta.seconds > 60:
            return self.tr("{} minutes ago").format(str(int(delta.seconds / 60)))
        else:
            return self.tr("{} seconds ago").format(str(delta.seconds))

    def showDiff(self):
        # UI stuff
        self.actShowSpaces.setEnabled(self.actShowDiff.isChecked())
        self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())

        # FIXME: Errors in line number
        i = self.list.currentItem()

        if not i:
            self.btnDelete.setEnabled(False)
            self.btnRestore.setEnabled(False)
            return

        self.btnDelete.setEnabled(True)
        self.btnRestore.setEnabled(True)

        ts = i.data(Qt.UserRole)
        item = self._index.internalPointer()

        textNow = item.text()
        textBefore = [r[1] for r in item.revisions() if r[0] == ts][0]

        if self.actShowVersion.isChecked():
            self.view.setText(textBefore)
            return

        textNow = textNow.splitlines()
        textBefore = textBefore.splitlines()

        d = difflib.Differ()
        diff = list(d.compare(textBefore, textNow))

        if self.actShowSpaces.isChecked():
            _format = lambda x: x.replace(" ", "␣ ")
        else:
            _format = lambda x: x

        extra = "<br>"
        diff = [d for d in diff if d and not d[:2] == "? "]
        mydiff = ""
        skip = False
        for n, l in enumerate(diff):
            l = diff[n]
            op = l[:2]
            txt = l[2:]
            op2 = diff[n + 1][:2] if n + 1 < len(diff) else None
            txt2 = diff[n + 1][2:] if n + 1 < len(diff) else None

            if skip:
                skip = False
                continue

            # Same line
            if op == "  " and not self.actDiffOnly.isChecked():
                mydiff += "{}{}".format(txt, extra)

            elif op == "- " and op2 == "+ ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br><span style='color: blue;'>{}</span><br>".format(
                            self.tr("Line {}:").format(str(n)))
                s = difflib.SequenceMatcher(None, txt, txt2, autojunk=True)
                newline = ""
                for tag, i1, i2, j1, j2 in s.get_opcodes():
                    if tag == "equal":
                        newline += txt[i1:i2]
                    elif tag == "delete":
                        newline += "<span style='color:red; background:yellow;'>{}</span>".format(_format(txt[i1:i2]))
                    elif tag == "insert":
                        newline += "<span style='color:green; background:yellow;'>{}</span>".format(
                            _format(txt2[j1:j2]))
                    elif tag == "replace":
                        newline += "<span style='color:red; background:yellow;'>{}</span>".format(_format(txt[i1:i2]))
                        newline += "<span style='color:green; background:yellow;'>{}</span>".format(
                            _format(txt2[j1:j2]))

                # Few ugly tweaks for html diffs
                newline = re.sub(r"(<span style='color.*?><span.*?>)</span>(.*)<span style='color:.*?>(</span></span>)",
                                 "\\1\\2\\3", newline)
                newline = re.sub(
                    r"<p align=\"<span style='color:red; background:yellow;'>cen</span><span style='color:green; background:yellow;'>righ</span>t<span style='color:red; background:yellow;'>er</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*?)</p>",
                    "<p align=\"right\"><span style='color:green; background:yellow;'>\\1</span></p>", newline)
                newline = re.sub(
                    r"<p align=\"<span style='color:green; background:yellow;'>cente</span>r<span style='color:red; background:yellow;'>ight</span>\" style=\" -qt-block-indent:0; -qt-user-state:0; \">(.*)</p>",
                    "<p align=\"center\"><span style='color:green; background:yellow;'>\\1</span></p>", newline)
                newline = re.sub(r"<p(<span.*?>)(.*?)(</span>)(.*?)>(.*?)</p>",
                                 "<p\\2\\4>\\1\\5\\3</p>", newline)

                mydiff += newline + extra
                skip = True
            elif op == "- ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br>{}:<br>".format(str(n))
                mydiff += "<span style='color:red;'>{}</span>{}".format(txt, extra)
            elif op == "+ ":
                if self.actDiffOnly.isChecked():
                    mydiff += "<br>{}:<br>".format(str(n))
                mydiff += "<span style='color:green;'>{}</span>{}".format(txt, extra)

        self.view.setText(mydiff)

    def restore(self):
        i = self.list.currentItem()
        if not i:
            return
        ts = i.data(Qt.UserRole)
        item = self._index.internalPointer()
        textBefore = [r[1] for r in item.revisions() if r[0] == ts][0]
        index = self._index.sibling(self._index.row(), Outline.text)
        self._index.model().setData(index, textBefore)
        # item.setData(Outline.text, textBefore)

    def delete(self):
        i = self.list.currentItem()
        if not i:
            return
        ts = i.data(Qt.UserRole)
        self._index.internalPointer().deleteRevision(ts)

    def clearAll(self):
        self._index.internalPointer().clearAllRevisions()

    def saveState(self):
        return [
            self.actShowDiff.isChecked(),
            self.actShowVersion.isChecked(),
            self.actShowSpaces.isChecked(),
            self.actDiffOnly.isChecked(),
        ]

    def popupMenu(self, pos):
        i = self.list.itemAt(pos)
        m = QMenu(self)
        if i:
            m.addAction(self.tr("Restore")).triggered.connect(self.restore)
            m.addAction(self.tr("Delete")).triggered.connect(self.delete)
            m.addSeparator()
        if self.list.count():
            m.addAction(self.tr("Clear all")).triggered.connect(self.clearAll)

        m.popup(self.list.mapToGlobal(pos))

    def restoreState(self, state):
        self.actShowDiff.setChecked(state[0])
        self.actShowVersion.setChecked(state[1])
        self.actShowSpaces.setChecked(state[2])
        self.actDiffOnly.setChecked(state[3])
        self.actShowSpaces.setEnabled(self.actShowDiff.isChecked())
        self.actDiffOnly.setEnabled(self.actShowDiff.isChecked())
Esempio n. 10
0
    def __init__(self):
        super(MainWindow, self).__init__()

        self.currentPath = ''

        self.view = SvgView()

        fileMenu = QMenu("&File", self)
        openAction = fileMenu.addAction("&Open...")
        openAction.setShortcut("Ctrl+O")
        quitAction = fileMenu.addAction("E&xit")
        quitAction.setShortcut("Ctrl+Q")

        self.menuBar().addMenu(fileMenu)

        viewMenu = QMenu("&View", self)
        self.backgroundAction = viewMenu.addAction("&Background")
        self.backgroundAction.setEnabled(False)
        self.backgroundAction.setCheckable(True)
        self.backgroundAction.setChecked(False)
        self.backgroundAction.toggled.connect(self.view.setViewBackground)

        self.outlineAction = viewMenu.addAction("&Outline")
        self.outlineAction.setEnabled(False)
        self.outlineAction.setCheckable(True)
        self.outlineAction.setChecked(True)
        self.outlineAction.toggled.connect(self.view.setViewOutline)

        self.menuBar().addMenu(viewMenu)

        rendererMenu = QMenu("&Renderer", self)
        self.nativeAction = rendererMenu.addAction("&Native")
        self.nativeAction.setCheckable(True)
        self.nativeAction.setChecked(True)

        if QGLFormat.hasOpenGL():
            self.glAction = rendererMenu.addAction("&OpenGL")
            self.glAction.setCheckable(True)

        self.imageAction = rendererMenu.addAction("&Image")
        self.imageAction.setCheckable(True)

        if QGLFormat.hasOpenGL():
            rendererMenu.addSeparator()
            self.highQualityAntialiasingAction = rendererMenu.addAction("&High Quality Antialiasing")
            self.highQualityAntialiasingAction.setEnabled(False)
            self.highQualityAntialiasingAction.setCheckable(True)
            self.highQualityAntialiasingAction.setChecked(False)
            self.highQualityAntialiasingAction.toggled.connect(self.view.setHighQualityAntialiasing)

        rendererGroup = QActionGroup(self)
        rendererGroup.addAction(self.nativeAction)

        if QGLFormat.hasOpenGL():
            rendererGroup.addAction(self.glAction)

        rendererGroup.addAction(self.imageAction)

        self.menuBar().addMenu(rendererMenu)

        openAction.triggered.connect(self.openFile)
        quitAction.triggered.connect(QApplication.instance().quit)
        rendererGroup.triggered.connect(self.setRenderer)

        self.setCentralWidget(self.view)
        self.setWindowTitle("SVG Viewer")
Esempio n. 11
0
	def __init__(self, supported_exts, parent=None):
		super().__init__(parent)

		self._diasshowRunning = False
		# a dummy widget to center actions
		spacer1 = QWidget()
		spacer1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
		self.addWidget(spacer1)

		self.supportedExts = supported_exts
		self._fromFile = self.addAction(QIcon("icons/image-outline.svg"), "", self.chooseFile) # load images from file
		self._fromFile.setToolTip("Load image")
		self._fromFolder = self.addAction(QIcon("icons/folder-open.svg"), "", self.chooseFolder) # load images from folder
		self._fromFolder.setToolTip("Load from directory")

		# View in native size, fit width, fit height or fit image
		self._imageMode = QToolButton(self)
		self._imageMode.setIcon(QIcon("icons/eye-outline.svg"))
		self._imageMode.setToolTip("Image view mode")
		self._imageMode.setPopupMode(QToolButton.InstantPopup)
		self.addWidget(self._imageMode)
		
		# imageMode menu
		imageModeMenu = QMenu(self)
		imageModeActions = QActionGroup(imageModeMenu)
		imModeAct1 = imageModeActions.addAction("Native size")
		imModeAct1.setCheckable(True)
		imModeAct1.triggered.connect(lambda: self.imageModeChanged.emit(0))
		imModeAct2 = imageModeActions.addAction("Fit in view")
		imModeAct2.setCheckable(True)
		imModeAct2.triggered.connect(lambda: self.imageModeChanged.emit(1))
		imModeAct3 = imageModeActions.addAction("Fit width")
		imModeAct3.setCheckable(True)
		imModeAct3.triggered.connect(lambda: self.imageModeChanged.emit(2))
		imModeAct4 = imageModeActions.addAction("Fit height")
		imModeAct4.setCheckable(True)
		imModeAct4.triggered.connect(lambda: self.imageModeChanged.emit(3))
		imageModeActions.setExclusive(True)
		imageModeMenu.addActions(imageModeActions.actions())
		self._imageMode.setMenu(imageModeMenu)

		
		self._imgDirection = self.addAction(QIcon("icons/arrow-move-outline.svg"), "", self.imageDirectionChanged.emit) # Horizontal or Vertical
		self._imgDirection.setToolTip("Toggle image direction")

		# start or stop diasshow
		self._playDias = self.addAction(QIcon("icons/media-play-outline.svg"), "", self.diasshowState)
		self._playDias.setToolTip("Start/stop diasshow")

		#diasshow menu
		self._diasMenu = QMenu(self)
		self._diasMenu.addAction("5 seconds", lambda: self.diasshowState(5))
		self._diasMenu.addAction("10 seconds", lambda: self.diasshowState(10))
		self._diasMenu.addAction("30 seconds", lambda: self.diasshowState(30))
		self._diasMenu.addAction("5 minutes", lambda: self.diasshowState(60*5))
		self._diasMenu.addAction("10 minutes", lambda: self.diasshowState(600))
		self._playDias.setMenu(self._diasMenu)


		self._zoomIn = self.addAction(QIcon("icons/zoom-in-outline.svg"), "", lambda: self.zoomChanged.emit(True))
		self._zoomOut = self.addAction(QIcon("icons/zoom-out-outline.svg"), "", lambda: self.zoomChanged.emit(False))
		self._rotateCW = self.addAction(QIcon("icons/rotate-cw-outline.svg"), "", self.rotateChanged.emit) # Clockwise
		self._rotateCW.setToolTip("Rotate Clockwise")
		#self._rotateCCW = self.addAction("Rotate Left") # Counter clockwise

		# a dummy widget to center actions
		spacer2 = QWidget()
		spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
		self.addWidget(spacer2)
Esempio n. 12
0
class MainUI(QtWidgets.QMainWindow, main_window_class):
    connectionLostSignal = pyqtSignal(str, str)
    connectionInitiatedSignal = pyqtSignal(str)
    batteryUpdatedSignal = pyqtSignal(int, object, object)
    connectionDoneSignal = pyqtSignal(str)
    connectionFailedSignal = pyqtSignal(str, str)
    disconnectedSignal = pyqtSignal(str)
    linkQualitySignal = pyqtSignal(int)

    _input_device_error_signal = pyqtSignal(str)
    _input_discovery_signal = pyqtSignal(object)
    _log_error_signal = pyqtSignal(object, str)

    def __init__(self, *args):
        super(MainUI, self).__init__(*args)
        self.setupUi(self)

        # Restore window size if present in the config file
        try:
            size = Config().get("window_size")
            self.resize(size[0], size[1])
        except KeyError:
            pass

        ######################################################
        # By lxrocks
        # 'Skinny Progress Bar' tweak for Yosemite
        # Tweak progress bar - artistic I am not - so pick your own colors !!!
        # Only apply to Yosemite
        ######################################################
        import platform

        if platform.system() == 'Darwin':

            (Version, junk, machine) = platform.mac_ver()
            logger.info("This is a MAC - checking if we can apply Progress "
                        "Bar Stylesheet for Yosemite Skinny Bars ")
            yosemite = (10, 10, 0)
            tVersion = tuple(map(int, (Version.split("."))))

            if tVersion >= yosemite:
                logger.info("Found Yosemite - applying stylesheet")

                tcss = """
                    QProgressBar {
                        border: 1px solid grey;
                        border-radius: 5px;
                        text-align: center;
                    }
                    QProgressBar::chunk {
                        background-color: """ + COLOR_BLUE + """;
                    }
                 """
                self.setStyleSheet(tcss)

            else:
                logger.info("Pre-Yosemite - skinny bar stylesheet not applied")

        ######################################################

        self.cf = Crazyflie(ro_cache=None,
                            rw_cache=cfclient.config_path + "/cache")

        cflib.crtp.init_drivers(
            enable_debug_driver=Config().get("enable_debug_driver"))

        zmq_params = ZMQParamAccess(self.cf)
        zmq_params.start()

        zmq_leds = ZMQLEDDriver(self.cf)
        zmq_leds.start()

        self.scanner = ScannerThread()
        self.scanner.interfaceFoundSignal.connect(self.foundInterfaces)
        self.scanner.start()

        # Create and start the Input Reader
        self._statusbar_label = QLabel("No input-device found, insert one to"
                                       " fly.")
        self.statusBar().addWidget(self._statusbar_label)

        self.joystickReader = JoystickReader()
        self._active_device = ""
        # self.configGroup = QActionGroup(self._menu_mappings, exclusive=True)

        self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True)

        # TODO: Need to reload configs
        # ConfigManager().conf_needs_reload.add_callback(self._reload_configs)

        self.cf.connection_failed.add_callback(
            self.connectionFailedSignal.emit)
        self.connectionFailedSignal.connect(self._connection_failed)

        self._input_device_error_signal.connect(
            self._display_input_device_error)
        self.joystickReader.device_error.add_callback(
            self._input_device_error_signal.emit)
        self._input_discovery_signal.connect(self.device_discovery)
        self.joystickReader.device_discovery.add_callback(
            self._input_discovery_signal.emit)

        # Hide the 'File' menu on OS X, since its only item, 'Exit', gets
        # merged into the application menu.
        if sys.platform == 'darwin':
            self.menuFile.menuAction().setVisible(False)

        # Connect UI signals
        self.logConfigAction.triggered.connect(self._show_connect_dialog)
        self.interfaceCombo.currentIndexChanged['QString'].connect(
            self.interfaceChanged)
        self.connectButton.clicked.connect(self._connect)
        self.scanButton.clicked.connect(self._scan)
        self.menuItemConnect.triggered.connect(self._connect)
        self.menuItemConfInputDevice.triggered.connect(
            self._show_input_device_config_dialog)
        self.menuItemExit.triggered.connect(self.closeAppRequest)
        self.batteryUpdatedSignal.connect(self._update_battery)
        self._menuitem_rescandevices.triggered.connect(self._rescan_devices)
        self._menuItem_openconfigfolder.triggered.connect(
            self._open_config_folder)

        self.address.setValue(0xE7E7E7E7E7)

        self._auto_reconnect_enabled = Config().get("auto_reconnect")
        self.autoReconnectCheckBox.toggled.connect(
            self._auto_reconnect_changed)
        self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect"))

        self.joystickReader.input_updated.add_callback(
            self.cf.commander.send_setpoint)

        self.joystickReader.assisted_input_updated.add_callback(
            self.cf.commander.send_velocity_world_setpoint)

        # Connection callbacks and signal wrappers for UI protection
        self.cf.connected.add_callback(self.connectionDoneSignal.emit)
        self.connectionDoneSignal.connect(self._connected)
        self.cf.disconnected.add_callback(self.disconnectedSignal.emit)
        self.disconnectedSignal.connect(self._disconnected)
        self.cf.connection_lost.add_callback(self.connectionLostSignal.emit)
        self.connectionLostSignal.connect(self._connection_lost)
        self.cf.connection_requested.add_callback(
            self.connectionInitiatedSignal.emit)
        self.connectionInitiatedSignal.connect(self._connection_initiated)
        self._log_error_signal.connect(self._logging_error)

        self.batteryBar.setTextVisible(False)
        self.batteryBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))

        self.linkQualityBar.setTextVisible(False)
        self.linkQualityBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))

        # Connect link quality feedback
        self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit)
        self.linkQualitySignal.connect(
            lambda percentage: self.linkQualityBar.setValue(percentage))

        self._selected_interface = None
        self._initial_scan = True
        self._scan()

        # Parse the log configuration files
        self.logConfigReader = LogConfigReader(self.cf)

        self._current_input_config = None
        self._active_config = None
        self._active_config = None

        self.inputConfig = None

        # Add things to helper so tabs can access it
        cfclient.ui.pluginhelper.cf = self.cf
        cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader
        cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader

        self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper)
        self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper)
        self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper)
        self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper)
        self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show)
        self._about_dialog = AboutDialog(cfclient.ui.pluginhelper)
        self.menuItemAbout.triggered.connect(self._about_dialog.show)
        self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show)
        self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show)

        # Load and connect tabs
        self.tabsMenuItem = QMenu("Tabs", self.menuView, enabled=True)
        self.menuView.addMenu(self.tabsMenuItem)

        # self.tabsMenuItem.setMenu(QtWidgets.QMenu())
        tabItems = {}
        self.loadedTabs = []
        for tabClass in cfclient.ui.tabs.available:
            tab = tabClass(self.tabs, cfclient.ui.pluginhelper)
            item = QtWidgets.QAction(tab.getMenuName(), self, checkable=True)
            item.toggled.connect(tab.toggleVisibility)
            self.tabsMenuItem.addAction(item)
            tabItems[tab.getTabName()] = item
            self.loadedTabs.append(tab)
            if not tab.enabled:
                item.setEnabled(False)

        # First instantiate all tabs and then open them in the correct order
        try:
            for tName in Config().get("open_tabs").split(","):
                t = tabItems[tName]
                if (t is not None and t.isEnabled()):
                    # Toggle though menu so it's also marked as open there
                    t.toggle()
        except Exception as e:
            logger.warning("Exception while opening tabs [{}]".format(e))

        # Loading toolboxes (A bit of magic for a lot of automatic)
        self.toolboxesMenuItem = QMenu("Toolboxes",
                                       self.menuView,
                                       enabled=True)
        self.menuView.addMenu(self.toolboxesMenuItem)

        self.toolboxes = []
        for t_class in cfclient.ui.toolboxes.toolboxes:
            toolbox = t_class(cfclient.ui.pluginhelper)
            dockToolbox = MyDockWidget(toolbox.getName())
            dockToolbox.setWidget(toolbox)
            self.toolboxes += [
                dockToolbox,
            ]

            # Add menu item for the toolbox
            item = QtWidgets.QAction(toolbox.getName(), self)
            item.setCheckable(True)
            item.triggered.connect(self.toggleToolbox)
            self.toolboxesMenuItem.addAction(item)

            dockToolbox.closed.connect(lambda: self.toggleToolbox(False))

            # Setup some introspection
            item.dockToolbox = dockToolbox
            item.menuItem = item
            dockToolbox.dockToolbox = dockToolbox
            dockToolbox.menuItem = item

        # References to all the device sub-menus in the "Input device" menu
        self._all_role_menus = ()
        # Used to filter what new devices to add default mapping to
        self._available_devices = ()
        # Keep track of mux nodes so we can enable according to how many
        # devices we have
        self._all_mux_nodes = ()

        # Check which Input muxes are available
        self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True)
        for m in self.joystickReader.available_mux():
            node = QAction(m.name,
                           self._menu_inputdevice,
                           checkable=True,
                           enabled=False)
            node.toggled.connect(self._mux_selected)
            self._mux_group.addAction(node)
            self._menu_inputdevice.addAction(node)
            self._all_mux_nodes += (node, )
            mux_subnodes = ()
            for name in m.supported_roles():
                sub_node = QMenu("    {}".format(name),
                                 self._menu_inputdevice,
                                 enabled=False)
                self._menu_inputdevice.addMenu(sub_node)
                mux_subnodes += (sub_node, )
                self._all_role_menus += ({
                    "muxmenu": node,
                    "rolemenu": sub_node
                }, )
            node.setData((m, mux_subnodes))

        self._mapping_support = True

    def interfaceChanged(self, interface):
        if interface == INTERFACE_PROMPT_TEXT:
            self._selected_interface = None
        else:
            self._selected_interface = interface
        self._update_ui_state()

    def foundInterfaces(self, interfaces):
        selected_interface = self._selected_interface

        self.interfaceCombo.clear()
        self.interfaceCombo.addItem(INTERFACE_PROMPT_TEXT)

        formatted_interfaces = []
        for i in interfaces:
            if len(i[1]) > 0:
                interface = "%s - %s" % (i[0], i[1])
            else:
                interface = i[0]
            formatted_interfaces.append(interface)
        self.interfaceCombo.addItems(formatted_interfaces)

        if self._initial_scan:
            self._initial_scan = False

            try:
                if len(Config().get("link_uri")) > 0:
                    formatted_interfaces.index(Config().get("link_uri"))
                    selected_interface = Config().get("link_uri")
            except KeyError:
                #  The configuration for link_uri was not found
                pass
            except ValueError:
                #  The saved URI was not found while scanning
                pass

        if len(interfaces) == 1 and selected_interface is None:
            selected_interface = interfaces[0][0]

        newIndex = 0
        if selected_interface is not None:
            try:
                newIndex = formatted_interfaces.index(selected_interface) + 1
            except ValueError:
                pass

        self.interfaceCombo.setCurrentIndex(newIndex)

        self.uiState = UIState.DISCONNECTED
        self._update_ui_state()

    def _update_ui_state(self):
        if self.uiState == UIState.DISCONNECTED:
            self.setWindowTitle("Not connected")
            canConnect = self._selected_interface is not None
            self.menuItemConnect.setText("Connect to Crazyflie")
            self.menuItemConnect.setEnabled(canConnect)
            self.connectButton.setText("Connect")
            self.connectButton.setToolTip(
                "Connect to the Crazyflie on the selected interface")
            self.connectButton.setEnabled(canConnect)
            self.scanButton.setText("Scan")
            self.scanButton.setEnabled(True)
            self.address.setEnabled(True)
            self.batteryBar.setValue(3000)
            self._menu_cf2_config.setEnabled(False)
            self._menu_cf1_config.setEnabled(True)
            self.linkQualityBar.setValue(0)
            self.menuItemBootloader.setEnabled(True)
            self.logConfigAction.setEnabled(False)
            self.interfaceCombo.setEnabled(True)
        elif self.uiState == UIState.CONNECTED:
            s = "Connected on %s" % self._selected_interface
            self.setWindowTitle(s)
            self.menuItemConnect.setText("Disconnect")
            self.menuItemConnect.setEnabled(True)
            self.connectButton.setText("Disconnect")
            self.connectButton.setToolTip("Disconnect from the Crazyflie")
            self.scanButton.setEnabled(False)
            self.logConfigAction.setEnabled(True)
            # Find out if there's an I2C EEPROM, otherwise don't show the
            # dialog.
            if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0:
                self._menu_cf2_config.setEnabled(True)
            self._menu_cf1_config.setEnabled(False)
        elif self.uiState == UIState.CONNECTING:
            s = "Connecting to {} ...".format(self._selected_interface)
            self.setWindowTitle(s)
            self.menuItemConnect.setText("Cancel")
            self.menuItemConnect.setEnabled(True)
            self.connectButton.setText("Cancel")
            self.connectButton.setToolTip("Cancel connecting to the Crazyflie")
            self.scanButton.setEnabled(False)
            self.address.setEnabled(False)
            self.menuItemBootloader.setEnabled(False)
            self.interfaceCombo.setEnabled(False)
        elif self.uiState == UIState.SCANNING:
            self.setWindowTitle("Scanning ...")
            self.connectButton.setText("Connect")
            self.menuItemConnect.setEnabled(False)
            self.connectButton.setText("Connect")
            self.connectButton.setEnabled(False)
            self.scanButton.setText("Scanning...")
            self.scanButton.setEnabled(False)
            self.address.setEnabled(False)
            self.menuItemBootloader.setEnabled(False)
            self.interfaceCombo.setEnabled(False)

    @pyqtSlot(bool)
    def toggleToolbox(self, display):
        menuItem = self.sender().menuItem
        dockToolbox = self.sender().dockToolbox

        if display and not dockToolbox.isVisible():
            dockToolbox.widget().enable()
            self.addDockWidget(dockToolbox.widget().preferedDockArea(),
                               dockToolbox)
            dockToolbox.show()
        elif not display:
            dockToolbox.widget().disable()
            self.removeDockWidget(dockToolbox)
            dockToolbox.hide()
            menuItem.setChecked(False)

    def _rescan_devices(self):
        self._statusbar_label.setText("No inputdevice connected!")
        self._menu_devices.clear()
        self._active_device = ""
        self.joystickReader.stop_input()

        # for c in self._menu_mappings.actions():
        #    c.setEnabled(False)
        # devs = self.joystickReader.available_devices()
        # if (len(devs) > 0):
        #    self.device_discovery(devs)

    def _show_input_device_config_dialog(self):
        self.inputConfig = InputConfigDialogue(self.joystickReader)
        self.inputConfig.show()

    def _auto_reconnect_changed(self, checked):
        self._auto_reconnect_enabled = checked
        Config().set("auto_reconnect", checked)
        logger.info("Auto reconnect enabled: {}".format(checked))

    def _show_connect_dialog(self):
        self.logConfigDialogue.show()

    def _update_battery(self, timestamp, data, logconf):
        self.batteryBar.setValue(int(data["pm.vbat"] * 1000))

        color = COLOR_BLUE
        # TODO firmware reports fully-charged state as 'Battery',
        # rather than 'Charged'
        if data["pm.state"] in [BatteryStates.CHARGING, BatteryStates.CHARGED]:
            color = COLOR_GREEN
        elif data["pm.state"] == BatteryStates.LOW_POWER:
            color = COLOR_RED

        self.batteryBar.setStyleSheet(progressbar_stylesheet(color))

    def _connected(self):
        self.uiState = UIState.CONNECTED
        self._update_ui_state()

        Config().set("link_uri", str(self._selected_interface))

        lg = LogConfig("Battery", 1000)
        lg.add_variable("pm.vbat", "float")
        lg.add_variable("pm.state", "int8_t")
        try:
            self.cf.log.add_config(lg)
            lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit)
            lg.error_cb.add_callback(self._log_error_signal.emit)
            lg.start()
        except KeyError as e:
            logger.warning(str(e))

        mems = self.cf.mem.get_mems(MemoryElement.TYPE_DRIVER_LED)
        if len(mems) > 0:
            mems[0].write_data(self._led_write_done)

    def _disconnected(self):
        self.uiState = UIState.DISCONNECTED
        self._update_ui_state()

    def _connection_initiated(self):
        self.uiState = UIState.CONNECTING
        self._update_ui_state()

    def _led_write_done(self, mem, addr):
        logger.info("LED write done callback")

    def _logging_error(self, log_conf, msg):
        QMessageBox.about(
            self, "Log error", "Error when starting log config"
            " [{}]: {}".format(log_conf.name, msg))

    def _connection_lost(self, linkURI, msg):
        if not self._auto_reconnect_enabled:
            if self.isActiveWindow():
                warningCaption = "Communication failure"
                error = "Connection lost to {}: {}".format(linkURI, msg)
                QMessageBox.critical(self, warningCaption, error)
                self.uiState = UIState.DISCONNECTED
                self._update_ui_state()
        else:
            self._connect()

    def _connection_failed(self, linkURI, error):
        if not self._auto_reconnect_enabled:
            msg = "Failed to connect on {}: {}".format(linkURI, error)
            warningCaption = "Communication failure"
            QMessageBox.critical(self, warningCaption, msg)
            self.uiState = UIState.DISCONNECTED
            self._update_ui_state()
        else:
            self._connect()

    def closeEvent(self, event):
        self.hide()
        self.cf.close_link()
        Config().save_file()

    def resizeEvent(self, event):
        Config().set(
            "window_size",
            [event.size().width(), event.size().height()])

    def _connect(self):
        if self.uiState == UIState.CONNECTED:
            self.cf.close_link()
        elif self.uiState == UIState.CONNECTING:
            self.cf.close_link()
            self.uiState = UIState.DISCONNECTED
            self._update_ui_state()
        else:
            self.cf.open_link(self._selected_interface)

    def _scan(self):
        self.uiState = UIState.SCANNING
        self._update_ui_state()
        self.scanner.scanSignal.emit(self.address.value())

    def _display_input_device_error(self, error):
        self.cf.close_link()
        QMessageBox.critical(self, "Input device error", error)

    def _mux_selected(self, checked):
        """Called when a new mux is selected. The menu item contains a
        reference to the raw mux object as well as to the associated device
        sub-nodes"""
        if not checked:
            (mux, sub_nodes) = self.sender().data()
            for s in sub_nodes:
                s.setEnabled(False)
        else:
            (mux, sub_nodes) = self.sender().data()
            for s in sub_nodes:
                s.setEnabled(True)
            self.joystickReader.set_mux(mux=mux)

            # Go though the tree and select devices/mapping that was
            # selected before it was disabled.
            for role_node in sub_nodes:
                for dev_node in role_node.children():
                    if type(dev_node) is QAction and dev_node.isChecked():
                        dev_node.toggled.emit(True)

            self._update_input_device_footer()

    def _get_dev_status(self, device):
        msg = "{}".format(device.name)
        if device.supports_mapping:
            map_name = "N/A"
            if device.input_map:
                map_name = device.input_map_name
            msg += " ({})".format(map_name)
        return msg

    def _update_input_device_footer(self):
        """Update the footer in the bottom of the UI with status for the
        input device and its mapping"""

        msg = ""

        if len(self.joystickReader.available_devices()) > 0:
            mux = self.joystickReader._selected_mux
            msg = "Using {} mux with ".format(mux.name)
            for key in list(mux._devs.keys())[:-1]:
                if mux._devs[key]:
                    msg += "{}, ".format(self._get_dev_status(mux._devs[key]))
                else:
                    msg += "N/A, "
            # Last item
            key = list(mux._devs.keys())[-1]
            if mux._devs[key]:
                msg += "{}".format(self._get_dev_status(mux._devs[key]))
            else:
                msg += "N/A"
        else:
            msg = "No input device found"
        self._statusbar_label.setText(msg)

    def _inputdevice_selected(self, checked):
        """Called when a new input device has been selected from the menu. The
        data in the menu object is the associated map menu (directly under the
        item in the menu) and the raw device"""
        (map_menu, device, mux_menu) = self.sender().data()
        if not checked:
            if map_menu:
                map_menu.setEnabled(False)
                # Do not close the device, since we don't know exactly
                # how many devices the mux can have open. When selecting a
                # new mux the old one will take care of this.
        else:
            if map_menu:
                map_menu.setEnabled(True)

            (mux, sub_nodes) = mux_menu.data()
            for role_node in sub_nodes:
                for dev_node in role_node.children():
                    if type(dev_node) is QAction and dev_node.isChecked():
                        if device.id == dev_node.data()[1].id \
                                and dev_node is not self.sender():
                            dev_node.setChecked(False)

            role_in_mux = str(self.sender().parent().title()).strip()
            logger.info("Role of {} is {}".format(device.name, role_in_mux))

            Config().set("input_device", str(device.name))

            self._mapping_support = self.joystickReader.start_input(
                device.name, role_in_mux)
        self._update_input_device_footer()

    def _inputconfig_selected(self, checked):
        """Called when a new configuration has been selected from the menu. The
        data in the menu object is a referance to the device QAction in parent
        menu. This contains a referance to the raw device."""
        if not checked:
            return

        selected_mapping = str(self.sender().text())
        device = self.sender().data().data()[1]
        self.joystickReader.set_input_map(device.name, selected_mapping)
        self._update_input_device_footer()

    def device_discovery(self, devs):
        """Called when new devices have been added"""
        for menu in self._all_role_menus:
            role_menu = menu["rolemenu"]
            mux_menu = menu["muxmenu"]
            dev_group = QActionGroup(role_menu, exclusive=True)
            for d in devs:
                dev_node = QAction(d.name,
                                   role_menu,
                                   checkable=True,
                                   enabled=True)
                role_menu.addAction(dev_node)
                dev_group.addAction(dev_node)
                dev_node.toggled.connect(self._inputdevice_selected)

                map_node = None
                if d.supports_mapping:
                    map_node = QMenu("    Input map", role_menu, enabled=False)
                    map_group = QActionGroup(role_menu, exclusive=True)
                    # Connect device node to map node for easy
                    # enabling/disabling when selection changes and device
                    # to easily enable it
                    dev_node.setData((map_node, d))
                    for c in ConfigManager().get_list_of_configs():
                        node = QAction(c,
                                       map_node,
                                       checkable=True,
                                       enabled=True)
                        node.toggled.connect(self._inputconfig_selected)
                        map_node.addAction(node)
                        # Connect all the map nodes back to the device
                        # action node where we can access the raw device
                        node.setData(dev_node)
                        map_group.addAction(node)
                        # If this device hasn't been found before, then
                        # select the default mapping for it.
                        if d not in self._available_devices:
                            last_map = Config().get("device_config_mapping")
                            if d.name in last_map and last_map[d.name] == c:
                                node.setChecked(True)
                    role_menu.addMenu(map_node)
                dev_node.setData((map_node, d, mux_menu))

        # Update the list of what devices we found
        # to avoid selecting default mapping for all devices when
        # a new one is inserted
        self._available_devices = ()
        for d in devs:
            self._available_devices += (d, )

        # Only enable MUX nodes if we have enough devies to cover
        # the roles
        for mux_node in self._all_mux_nodes:
            (mux, sub_nodes) = mux_node.data()
            if len(mux.supported_roles()) <= len(self._available_devices):
                mux_node.setEnabled(True)

        # TODO: Currently only supports selecting default mux
        if self._all_mux_nodes[0].isEnabled():
            self._all_mux_nodes[0].setChecked(True)

        # If the previous length of the available devies was 0, then select
        # the default on. If that's not available then select the first
        # on in the list.
        # TODO: This will only work for the "Normal" mux so this will be
        #       selected by default
        if Config().get("input_device") in [d.name for d in devs]:
            for dev_menu in self._all_role_menus[0]["rolemenu"].actions():
                if dev_menu.text() == Config().get("input_device"):
                    dev_menu.setChecked(True)
        else:
            # Select the first device in the first mux (will always be "Normal"
            # mux)
            self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True)
            logger.info("Select first device")

        self._update_input_device_footer()

    def _open_config_folder(self):
        QDesktopServices.openUrl(
            QUrl("file:///" + QDir.toNativeSeparators(cfclient.config_path)))

    def closeAppRequest(self):
        self.close()
        sys.exit(0)
Esempio n. 13
0
class MainWindow(QMainWindow, Ui_MainWindow):
    """docstring for MainWindow."""
    def __init__(self, parent=None):
        super(MainWindow, self).__init__()
        self._csvFilePath = ""
        self.serialport = serial.Serial()
        self.receiver_thread = readerThread(self)
        self.receiver_thread.setPort(self.serialport)
        self._localEcho = None
        self._viewMode = None
        self._quickSendOptRow = 1

        self.setupUi(self)
        self.setCorner(Qt.TopLeftCorner, Qt.LeftDockWidgetArea)
        self.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea)
        font = QtGui.QFont()
        font.setFamily(EDITOR_FONT)
        font.setPointSize(9)
        self.txtEdtOutput.setFont(font)
        self.txtEdtInput.setFont(font)
        #self.quickSendTable.setFont(font)
        if UI_FONT is not None:
            font = QtGui.QFont()
            font.setFamily(UI_FONT)
            font.setPointSize(9)
            self.dockWidget_PortConfig.setFont(font)
            self.dockWidget_SendHex.setFont(font)
            self.dockWidget_QuickSend.setFont(font)
        self.setupMenu()
        self.setupFlatUi()
        self.onEnumPorts()

        icon = QtGui.QIcon(":/MyTerm.ico")
        self.setWindowIcon(icon)
        self.actionAbout.setIcon(icon)

        self.defaultStyleWidget = QWidget()
        self.defaultStyleWidget.setWindowIcon(icon)

        icon = QtGui.QIcon(":/qt_logo_16.ico")
        self.actionAbout_Qt.setIcon(icon)

        self._viewGroup = QActionGroup(self)
        self._viewGroup.addAction(self.actionAscii)
        self._viewGroup.addAction(self.actionHex_lowercase)
        self._viewGroup.addAction(self.actionHEX_UPPERCASE)
        self._viewGroup.setExclusive(True)

        # bind events
        self.actionOpen_Cmd_File.triggered.connect(self.openQuickSend)
        self.actionSave_Log.triggered.connect(self.onSaveLog)
        self.actionExit.triggered.connect(self.onExit)

        self.actionOpen.triggered.connect(self.openPort)
        self.actionClose.triggered.connect(self.closePort)

        self.actionPort_Config_Panel.triggered.connect(self.onTogglePrtCfgPnl)
        self.actionQuick_Send_Panel.triggered.connect(self.onToggleQckSndPnl)
        self.actionSend_Hex_Panel.triggered.connect(self.onToggleHexPnl)
        self.dockWidget_PortConfig.visibilityChanged.connect(self.onVisiblePrtCfgPnl)
        self.dockWidget_QuickSend.visibilityChanged.connect(self.onVisibleQckSndPnl)
        self.dockWidget_SendHex.visibilityChanged.connect(self.onVisibleHexPnl)
        self.actionLocal_Echo.triggered.connect(self.onLocalEcho)
        self.actionAlways_On_Top.triggered.connect(self.onAlwaysOnTop)

        self.actionAscii.triggered.connect(self.onViewChanged)
        self.actionHex_lowercase.triggered.connect(self.onViewChanged)
        self.actionHEX_UPPERCASE.triggered.connect(self.onViewChanged)

        self.actionAbout.triggered.connect(self.onAbout)
        self.actionAbout_Qt.triggered.connect(self.onAboutQt)

        self.btnOpen.clicked.connect(self.onOpen)
        self.btnClear.clicked.connect(self.onClear)
        self.btnSaveLog.clicked.connect(self.onSaveLog)
        self.btnEnumPorts.clicked.connect(self.onEnumPorts)
        self.btnSendHex.clicked.connect(self.onSend)

        self.receiver_thread.read.connect(self.onReceive)
        self.receiver_thread.exception.connect(self.onReaderExcept)
        self._signalMapQuickSendOpt = QSignalMapper(self)
        self._signalMapQuickSendOpt.mapped[int].connect(self.onQuickSendOptions)
        self._signalMapQuickSend = QSignalMapper(self)
        self._signalMapQuickSend.mapped[int].connect(self.onQuickSend)

        # initial action
        self.actionHEX_UPPERCASE.setChecked(True)
        self.receiver_thread.setViewMode(VIEWMODE_HEX_UPPERCASE)
        self.initQuickSend()
        self.restoreLayout()
        self.moveScreenCenter()
        self.syncMenu()
        
        if self.isMaximized():
            self.setMaximizeButton("restore")
        else:
            self.setMaximizeButton("maximize")
            
        self.loadSettings()

    def setupMenu(self):
        self.menuMenu = QtWidgets.QMenu()
        self.menuMenu.setTitle("&File")
        self.menuMenu.setObjectName("menuMenu")
        self.menuView = QtWidgets.QMenu(self.menuMenu)
        self.menuView.setTitle("&View")
        self.menuView.setObjectName("menuView")

        self.menuView.addAction(self.actionAscii)
        self.menuView.addAction(self.actionHex_lowercase)
        self.menuView.addAction(self.actionHEX_UPPERCASE)
        self.menuMenu.addAction(self.actionOpen_Cmd_File)
        self.menuMenu.addAction(self.actionSave_Log)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionPort_Config_Panel)
        self.menuMenu.addAction(self.actionQuick_Send_Panel)
        self.menuMenu.addAction(self.actionSend_Hex_Panel)
        self.menuMenu.addAction(self.menuView.menuAction())
        self.menuMenu.addAction(self.actionLocal_Echo)
        self.menuMenu.addAction(self.actionAlways_On_Top)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionAbout)
        self.menuMenu.addAction(self.actionAbout_Qt)
        self.menuMenu.addSeparator()
        self.menuMenu.addAction(self.actionExit)

        self.sendOptMenu = QtWidgets.QMenu()
        self.actionSend_Hex = QtWidgets.QAction(self)
        self.actionSend_Hex.setText("Send &Hex")
        self.actionSend_Hex.setStatusTip("Send Hex (e.g. 31 32 FF)")

        self.actionSend_Asc = QtWidgets.QAction(self)
        self.actionSend_Asc.setText("Send &Asc")
        self.actionSend_Asc.setStatusTip("Send Asc (e.g. abc123)")

        self.actionSend_TFH = QtWidgets.QAction(self)
        self.actionSend_TFH.setText("Send &Text file as hex")
        self.actionSend_TFH.setStatusTip('Send text file as hex (e.g. strings "31 32 FF" in the file)')

        self.actionSend_TFA = QtWidgets.QAction(self)
        self.actionSend_TFA.setText("Send t&Ext file as asc")
        self.actionSend_TFA.setStatusTip('Send text file as asc (e.g. strings "abc123" in the file)')

        self.actionSend_FB = QtWidgets.QAction(self)
        self.actionSend_FB.setText("Send &Bin/Hex file")
        self.actionSend_FB.setStatusTip("Send a bin file or a hex file")

        self.sendOptMenu.addAction(self.actionSend_Hex)
        self.sendOptMenu.addAction(self.actionSend_Asc)
        self.sendOptMenu.addAction(self.actionSend_TFH)
        self.sendOptMenu.addAction(self.actionSend_TFA)
        self.sendOptMenu.addAction(self.actionSend_FB)

        self.actionSend_Hex.triggered.connect(self.onSetSendHex)
        self.actionSend_Asc.triggered.connect(self.onSetSendAsc)
        self.actionSend_TFH.triggered.connect(self.onSetSendTFH)
        self.actionSend_TFA.triggered.connect(self.onSetSendTFA)
        self.actionSend_FB.triggered.connect(self.onSetSendFB)

    def setupFlatUi(self):
        self._dragPos = self.pos()
        self._isDragging = False
        self.setMouseTracking(True)
        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setStyleSheet("""
            QWidget {
                background-color: %(BackgroundColor)s;
                /*background-image: url(:/background.png);*/
                outline: none;
            }
            QLabel {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            
            QComboBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
            }
            QComboBox {
                border: none;
                padding: 1px 1px 1px 3px;
            }
            QComboBox:editable {
                background: white;
            }
            QComboBox:!editable, QComboBox::drop-down:editable {
                background: #62c7e0;
            }
            QComboBox:!editable:hover, QComboBox::drop-down:editable:hover {
                background: #c7eaf3;
            }
            QComboBox:!editable:pressed, QComboBox::drop-down:editable:pressed {
                background: #35b6d7;
            }
            QComboBox:on {
                padding-top: 3px;
                padding-left: 4px;
            }
            QComboBox::drop-down {
                subcontrol-origin: padding;
                subcontrol-position: top right;
                width: 16px;
                border: none;
            }
            QComboBox::down-arrow {
                image: url(:/downarrow.png);
            }
            QComboBox::down-arrow:on {
                image: url(:/uparrow.png);
            }
            
            QGroupBox {
                color:%(TextColor)s;
                font-size:12px;
                /*font-family:Century;*/
                border: 1px solid gray;
                margin-top: 15px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                subcontrol-position: top left;
                left:5px;
                top:3px;
            }
            
            QCheckBox {
                color:%(TextColor)s;
                spacing: 5px;
                font-size:12px;
                /*font-family:Century;*/
            }
            QCheckBox::indicator:unchecked {
                image: url(:/checkbox_unchecked.png);
            }

            QCheckBox::indicator:unchecked:hover {
                image: url(:/checkbox_unchecked_hover.png);
            }

            QCheckBox::indicator:unchecked:pressed {
                image: url(:/checkbox_unchecked_pressed.png);
            }

            QCheckBox::indicator:checked {
                image: url(:/checkbox_checked.png);
            }

            QCheckBox::indicator:checked:hover {
                image: url(:/checkbox_checked_hover.png);
            }

            QCheckBox::indicator:checked:pressed {
                image: url(:/checkbox_checked_pressed.png);
            }
            
            QScrollBar:horizontal {
                background-color:%(BackgroundColor)s;
                border: none;
                height: 15px;
                margin: 0px 20px 0 20px;
            }
            QScrollBar::handle:horizontal {
                background: %(ScrollBar_Handle)s;
                min-width: 20px;
            }
            QScrollBar::add-line:horizontal {
                image: url(:/rightarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: right;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line:horizontal {
                image: url(:/leftarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                width: 20px;
                subcontrol-position: left;
                subcontrol-origin: margin;
            }
            
            QScrollBar:vertical {
                background-color:%(BackgroundColor)s;
                border: none;
                width: 15px;
                margin: 20px 0px 20px 0px;
            }
            QScrollBar::handle::vertical {
                background: %(ScrollBar_Handle)s;
                min-height: 20px;
            }
            QScrollBar::add-line::vertical {
                image: url(:/downarrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: bottom;
                subcontrol-origin: margin;
            }
            QScrollBar::sub-line::vertical {
                image: url(:/uparrow.png);
                border: none;
                background: %(ScrollBar_Line)s;
                height: 20px;
                subcontrol-position: top;
                subcontrol-origin: margin;
            }
            
            QTableView {
                background-color: white;
                /*selection-background-color: #FF92BB;*/
                border: 1px solid %(TableView_Border)s;
                color: %(TextColor)s;
            }
            QTableView::focus {
                /*border: 1px solid #2a7fff;*/
            }
            QTableView QTableCornerButton::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                background-color: %(TableView_Corner)s;
            }
            QTableView QWidget {
                background-color: white;
            }
            QTableView::item:focus {
                border: 1px red;
                background-color: transparent;
                color: %(TextColor)s;
            }
            QHeaderView::section {
                border: none;
                border-right: 1px solid %(TableView_Border)s;
                border-bottom: 1px solid %(TableView_Border)s;
                padding-left: 2px;
                padding-right: 2px;
                color: #444444;
                background-color: %(TableView_Header)s;
            }
            QTextEdit {
                background-color:white;
                color:%(TextColor)s;
                border-top: none;
                border-bottom: none;
                border-left: 2px solid %(BackgroundColor)s;
                border-right: 2px solid %(BackgroundColor)s;
            }
            QTextEdit::focus {
            }
            
            QToolButton, QPushButton {
                background-color:#30a7b8;
                border:none;
                color:#ffffff;
                font-size:12px;
                /*font-family:Century;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#51c0d1;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#3a9ecc;
            }
            
            QMenuBar {
                color: %(TextColor)s;
                height: 24px;
            }
            QMenuBar::item {
                background-color: transparent;
                margin: 8px 0px 0px 0px;
                padding: 1px 8px 1px 8px;
                height: 15px;
            }
            QMenuBar::item:selected {
                background: #51c0d1;
            }
            QMenuBar::item:pressed {
                
            }
            /*
            QMenu {
                color: %(TextColor)s;
                background: #ffffff;
            }
            QMenu {
                margin: 2px;
            }
            QMenu::item {
                padding: 2px 25px 2px 21px;
                border: 1px solid transparent;
            }
            QMenu::item:selected {
                background: #51c0d1;
            }
            QMenu::icon {
                background: transparent;
                border: 2px inset transparent;
            }*/

            QDockWidget {
                font-size:12px;
                /*font-family:Century;*/
                color: %(TextColor)s;
                titlebar-close-icon: none;
                titlebar-normal-icon: none;
            }
            QDockWidget::title {
                margin: 0;
                padding: 2px;
                subcontrol-origin: content;
                subcontrol-position: right top;
                text-align: left;
                background: #67baed;
                
            }
            QDockWidget::float-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/restore_inactive.png);
            }
            QDockWidget::float-button:hover {
                background-color:#227582;
                image: url(:/restore_active.png);
            }
            QDockWidget::float-button:pressed {
                padding: 0;
                background-color:#14464e;
                image: url(:/restore_active.png);
            }
            QDockWidget::close-button {
                max-width: 12px;
                max-height: 12px;
                background-color:transparent;
                border:none;
                image: url(:/close_inactive.png);
            }
            QDockWidget::close-button:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QDockWidget::close-button:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
                padding: 0;
            }
            
        """ % dict(
            BackgroundColor =  '#99d9ea',
            TextColor =        '#202020',
            ScrollBar_Handle = '#61b9e1',
            ScrollBar_Line =   '#7ecfe4',
            TableView_Corner = '#8ae6d2',
            TableView_Header = '#8ae6d2',
            TableView_Border = '#eeeeee'
        ))
        self.dockWidgetContents.setStyleSheet("""
            QPushButton {
                min-height:23px;
            }
        """)
        self.dockWidget_QuickSend.setStyleSheet("""
            QToolButton, QPushButton {
                background-color:#27b798;
                /*font-family:Consolas;*/
                /*font-size:12px;*/
                /*min-width:46px;*/
            }
            QToolButton:hover, QPushButton:hover {
                background-color:#3bd5b4;
            }
            QToolButton:pressed, QPushButton:pressed {
                background-color:#1d8770;
            }
        """)
        self.dockWidgetContents_2.setStyleSheet("""
            QPushButton {
                min-height:23px;
                min-width:50px;
            }
        """)

        w = self.frameGeometry().width()
        self._minBtn = QPushButton(self)
        self._minBtn.setGeometry(w-103,0,28,24)
        self._minBtn.clicked.connect(self.onMinimize)
        self._minBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/minimize_inactive.png);
            }
            QPushButton:hover {
                background-color:#227582;
                image: url(:/minimize_active.png);
            }
            QPushButton:pressed {
                background-color:#14464e;
                image: url(:/minimize_active.png);
            }
        """)
        
        self._maxBtn = QPushButton(self)
        self._maxBtn.setGeometry(w-74,0,28,24)
        self._maxBtn.clicked.connect(self.onMaximize)
        self.setMaximizeButton("maximize")
        
        self._closeBtn = QPushButton(self)
        self._closeBtn.setGeometry(w-45,0,36,24)
        self._closeBtn.clicked.connect(self.onExit)
        self._closeBtn.setStyleSheet("""
            QPushButton {
                background-color:transparent;
                border:none;
                outline: none;
                image: url(:/close_inactive.png);
            }
            QPushButton:hover {
                background-color:#ea5e00;
                image: url(:/close_active.png);
            }
            QPushButton:pressed {
                background-color:#994005;
                image: url(:/close_active.png);
            }
        """)
        
        self.btnMenu = QtWidgets.QToolButton(self)
        self.btnMenu.setEnabled(True)
        self.btnMenu.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
        self.btnMenu.setIcon(QtGui.QIcon(':/MyTerm.ico'))
        self.btnMenu.setText('Myterm  ')
        self.btnMenu.setGeometry(3,3,80,18)
        self.btnMenu.setMenu(self.menuMenu)
        self.btnMenu.setPopupMode(QtWidgets.QToolButton.InstantPopup)
        
        self.btnRefresh = QtWidgets.QToolButton(self)
        self.btnRefresh.setEnabled(True)
        self.btnRefresh.setIcon(QtGui.QIcon(':/refresh.ico'))
        self.btnRefresh.setGeometry(110,3,18,18)
        self.btnRefresh.clicked.connect(self.onEnumPorts)
        
        self.verticalLayout_1.removeWidget(self.cmbPort)
        self.cmbPort.setParent(self)
        self.cmbPort.setGeometry(128,3,60,18)
        
        self.verticalLayout_1.removeWidget(self.btnOpen)
        self.btnOpen.setParent(self)
        self.btnOpen.setGeometry(210,3,60,18)
        
    def resizeEvent(self, event):
        w = event.size().width()
        self._minBtn.move(w-103,0)
        self._maxBtn.move(w-74,0)
        self._closeBtn.move(w-45,0)

    def onMinimize(self):
        self.showMinimized()
    
    def isMaximized(self):
        return ((self.windowState() == Qt.WindowMaximized))
    
    def onMaximize(self):
        if self.isMaximized():
            self.showNormal()
            self.setMaximizeButton("maximize")
        else:
            self.showMaximized()
            self.setMaximizeButton("restore")
    
    def setMaximizeButton(self, style):
        if "maximize" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/maximize_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/maximize_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/maximize_active.png);
                }
            """)
        elif "restore" == style:
            self._maxBtn.setStyleSheet("""
                QPushButton {
                    background-color:transparent;
                    border:none;
                    outline: none;
                    image: url(:/restore_inactive.png);
                }
                QPushButton:hover {
                    background-color:#227582;
                    image: url(:/restore_active.png);
                }
                QPushButton:pressed {
                    background-color:#14464e;
                    image: url(:/restore_active.png);
                }
            """)
    
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self._isDragging = True
            self._dragPos = event.globalPos() - self.pos()
        event.accept()
        
    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton and self._isDragging and not self.isMaximized():
            self.move(event.globalPos() - self._dragPos)
        event.accept()

    def mouseReleaseEvent(self, event):
        self._isDragging = False
        event.accept()

    def saveSettings(self):
        root = ET.Element("MyTerm")
        GUISettings = ET.SubElement(root, "GUISettings")

        PortCfg = ET.SubElement(GUISettings, "PortConfig")
        ET.SubElement(PortCfg, "port").text = self.cmbPort.currentText()
        ET.SubElement(PortCfg, "baudrate").text = self.cmbBaudRate.currentText()
        ET.SubElement(PortCfg, "databits").text = self.cmbDataBits.currentText()
        ET.SubElement(PortCfg, "parity").text = self.cmbParity.currentText()
        ET.SubElement(PortCfg, "stopbits").text = self.cmbStopBits.currentText()
        ET.SubElement(PortCfg, "rtscts").text = self.chkRTSCTS.isChecked() and "on" or "off"
        ET.SubElement(PortCfg, "xonxoff").text = self.chkXonXoff.isChecked() and "on" or "off"

        View = ET.SubElement(GUISettings, "View")
        ET.SubElement(View, "LocalEcho").text = self.actionLocal_Echo.isChecked() and "on" or "off"
        ET.SubElement(View, "ReceiveView").text = self._viewGroup.checkedAction().text()

        with open(get_config_path('MyTerm.xml'), 'w') as f:
            f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
            f.write(ET.tostring(root, encoding='utf-8', pretty_print=True).decode("utf-8"))

    def loadSettings(self):
        if os.path.isfile(get_config_path("MyTerm.xml")):
            with open(get_config_path("MyTerm.xml"), 'r') as f:
                tree = safeET.parse(f)

            port = tree.findtext('GUISettings/PortConfig/port', default='')
            if port != '':
                self.cmbPort.setCurrentText(port)

            baudrate = tree.findtext('GUISettings/PortConfig/baudrate', default='38400')
            if baudrate != '':
                self.cmbBaudRate.setCurrentText(baudrate)

            databits = tree.findtext('GUISettings/PortConfig/databits', default='8')
            id = self.cmbDataBits.findText(databits)
            if id >= 0:
                self.cmbDataBits.setCurrentIndex(id)

            parity = tree.findtext('GUISettings/PortConfig/parity', default='None')
            id = self.cmbParity.findText(parity)
            if id >= 0:
                self.cmbParity.setCurrentIndex(id)

            stopbits = tree.findtext('GUISettings/PortConfig/stopbits', default='1')
            id = self.cmbStopBits.findText(stopbits)
            if id >= 0:
                self.cmbStopBits.setCurrentIndex(id)

            rtscts = tree.findtext('GUISettings/PortConfig/rtscts', default='off')
            if 'on' == rtscts:
                self.chkRTSCTS.setChecked(True)
            else:
                self.chkRTSCTS.setChecked(False)

            xonxoff = tree.findtext('GUISettings/PortConfig/xonxoff', default='off')
            if 'on' == xonxoff:
                self.chkXonXoff.setChecked(True)
            else:
                self.chkXonXoff.setChecked(False)

            LocalEcho = tree.findtext('GUISettings/View/LocalEcho', default='off')
            if 'on' == LocalEcho:
                self.actionLocal_Echo.setChecked(True)
                self._localEcho = True
            else:
                self.actionLocal_Echo.setChecked(False)
                self._localEcho = False

            ReceiveView = tree.findtext('GUISettings/View/ReceiveView', default='HEX(UPPERCASE)')
            if 'Ascii' in ReceiveView:
                self.actionAscii.setChecked(True)
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in ReceiveView:
                self.actionHex_lowercase.setChecked(True)
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in ReceiveView:
                self.actionHEX_UPPERCASE.setChecked(True)
                self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.receiver_thread.setViewMode(self._viewMode)

    def closeEvent(self, event):
        self.saveLayout()
        self.saveQuickSend()
        self.saveSettings()
        event.accept()

    def initQuickSend(self):
        #self.quickSendTable.horizontalHeader().setDefaultSectionSize(40)
        #self.quickSendTable.horizontalHeader().setMinimumSectionSize(25)
        self.quickSendTable.setRowCount(50)
        self.quickSendTable.setColumnCount(3)
        self.quickSendTable.verticalHeader().setSectionsClickable(True)

        for row in range(50):
            self.initQuickSendButton(row)

        if os.path.isfile(get_config_path('QuickSend.csv')):
            self.loadQuickSend(get_config_path('QuickSend.csv'))

        self.quickSendTable.resizeColumnsToContents()

    def initQuickSendButton(self, row, cmd = 'cmd', opt = 'H', dat = ''):
        if self.quickSendTable.cellWidget(row, 0) is None:
            item = QToolButton(self)
            item.setText(cmd)
            item.clicked.connect(self._signalMapQuickSend.map)
            self._signalMapQuickSend.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 0, item)
        else:
            self.quickSendTable.cellWidget(row, 0).setText(cmd)

        if self.quickSendTable.cellWidget(row, 1) is None:
            item = QToolButton(self)
            item.setText(opt)
            #item.setMaximumSize(QtCore.QSize(16, 16))
            item.clicked.connect(self._signalMapQuickSendOpt.map)
            self._signalMapQuickSendOpt.setMapping(item, row)
            self.quickSendTable.setCellWidget(row, 1, item)
        else:
            self.quickSendTable.cellWidget(row, 1).setText(opt)

        if self.quickSendTable.item(row, 2) is None:
            self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))
        else:
            self.quickSendTable.item(row, 2).setText(dat)

        self.quickSendTable.setRowHeight(row, 16)

    def onSetSendHex(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('H')

    def onSetSendAsc(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('A')

    def onSetSendTFH(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FH')

    def onSetSendTFA(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FA')

    def onSetSendFB(self):
        item = self.quickSendTable.cellWidget(self._quickSendOptRow, 1)
        item.setText('FB')

    def onQuickSendOptions(self, row):
        self._quickSendOptRow = row
        item = self.quickSendTable.cellWidget(row, 1)
        self.sendOptMenu.popup(item.mapToGlobal(QPoint(item.size().width(), item.size().height())))

    def openQuickSend(self):
        fileName = QFileDialog.getOpenFileName(self, "Select a file",
            os.getcwd(), "CSV Files (*.csv)")[0]
        if fileName:
            self.loadQuickSend(fileName, notifyExcept = True)

    def saveQuickSend(self):
        # scan table
        rows = self.quickSendTable.rowCount()
        #cols = self.quickSendTable.columnCount()

        save_data = [[self.quickSendTable.cellWidget(row, 0).text(),
                      self.quickSendTable.cellWidget(row, 1).text(),
                      self.quickSendTable.item(row, 2) is not None
                      and self.quickSendTable.item(row, 2).text() or ''] for row in range(rows)]

        #import pprint
        #pprint.pprint(save_data, width=120, compact=True)

        # write to file
        with open(get_config_path('QuickSend.csv'), 'w') as csvfile:
            csvwriter = csv.writer(csvfile, delimiter=',', lineterminator='\n')
            csvwriter.writerows(save_data)

    def loadQuickSend(self, path, notifyExcept = False):
        data = []
        set_rows = 0
        set_cols = 0
        try:
            with open(path) as csvfile:
                csvData = csv.reader(csvfile)
                for row in csvData:
                    data.append(row)
                    set_rows = set_rows + 1
                    if len(row) > set_cols:
                        set_cols = len(row)
        except IOError as e:
            print("({})".format(e))
            if notifyExcept:
                QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                    str(e), QMessageBox.Close)
            return

        rows = self.quickSendTable.rowCount()
        cols = self.quickSendTable.columnCount()

        if rows < set_rows:
            rows = set_rows + 10
            self.quickSendTable.setRowCount(rows)

        for row, rowdat in enumerate(data):
            if len(rowdat) >= 3:
                cmd, opt, dat = rowdat[0:3]
                self.initQuickSendButton(row, cmd, opt, dat)
#                self.quickSendTable.cellWidget(row, 0).setText(cmd)
#                self.quickSendTable.cellWidget(row, 1).setText(opt)
#                self.quickSendTable.setItem(row, 2, QTableWidgetItem(dat))

        self.quickSendTable.resizeColumnsToContents()
        #self.quickSendTable.resizeRowsToContents()

    def onQuickSend(self, row):
        if self.quickSendTable.item(row, 2) is not None:
            tablestring = self.quickSendTable.item(row, 2).text()
            format = self.quickSendTable.cellWidget(row, 1).text()
            if 'H' == format:
                self.transmitHex(tablestring)
            elif 'A' == format:
                self.transmitAsc(tablestring)
            elif 'FB' == format:
                try:
                    with open(tablestring, 'rb') as f:
                        bytes = f.read()
                        self.transmitBytearray(bytes)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                        str(e), QMessageBox.Close)
            else:
                try:
                    with open(tablestring, 'rt') as f:
                        filestring = f.read()
                        if 'FH' == format:
                            self.transmitHex(filestring)
                        elif 'FA' == format:
                            self.transmitAsc(filestring)
                except IOError as e:
                    print("({})".format(e))
                    QMessageBox.critical(self.defaultStyleWidget, "Open failed",
                        str(e), QMessageBox.Close)

    def onSend(self):
        sendstring = self.txtEdtInput.toPlainText()
        self.transmitHex(sendstring)

    def transmitHex(self, hexstring):
        if len(hexstring) > 0:
            hexarray = []
            _hexstring = hexstring.replace(' ', '')
            _hexstring = _hexstring.replace('\r', '')
            _hexstring = _hexstring.replace('\n', '')
            for i in range(0, len(_hexstring), 2):
                word = _hexstring[i:i+2]
                if is_hex(word):
                    hexarray.append(int(word, 16))
                else:
                    QMessageBox.critical(self.defaultStyleWidget, "Error",
                        "'%s' is not hexadecimal." % (word), QMessageBox.Close)
                    return

            self.transmitBytearray(bytearray(hexarray))

    def transmitAsc(self, text):
        if len(text) > 0:
            byteArray = [ord(char) for char in text]
            self.transmitBytearray(bytearray(byteArray))

    def transmitBytearray(self, byteArray):
        if self.serialport.isOpen():
            try:
                self.serialport.write(byteArray)
            except Exception as e:
                QMessageBox.critical(self.defaultStyleWidget,
                    "Exception in transmit", str(e), QMessageBox.Close)
                print("Exception in transmitBytearray(%s)" % text)
            else:
                if self._viewMode == VIEWMODE_ASCII:
                    text = byteArray.decode('unicode_escape')
                elif self._viewMode == VIEWMODE_HEX_LOWERCASE:
                    text = ''.join('%02x ' % t for t in byteArray)
                elif self._viewMode == VIEWMODE_HEX_UPPERCASE:
                    text = ''.join('%02X ' % t for t in byteArray)
                self.appendOutputText("\n%s T->:%s" % (self.timestamp(), text), Qt.blue)

    def onReaderExcept(self, e):
        self.closePort()
        QMessageBox.critical(self.defaultStyleWidget, "Read failed", str(e), QMessageBox.Close)

    def timestamp(self):
        return datetime.datetime.now().time().isoformat()[:-3]

    def onReceive(self, data):
        self.appendOutputText("\n%s R<-:%s" % (self.timestamp(), data))

    def appendOutputText(self, data, color=Qt.black):
        # the qEditText's "append" methon will add a unnecessary newline.
        # self.txtEdtOutput.append(data.decode('utf-8'))

        tc=self.txtEdtOutput.textColor()
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(QtGui.QColor(color))
        self.txtEdtOutput.insertPlainText(data)
        self.txtEdtOutput.moveCursor(QtGui.QTextCursor.End)
        self.txtEdtOutput.setTextColor(tc)

    def getPort(self):
        return self.cmbPort.currentText()

    def getDataBits(self):
        return {'5':serial.FIVEBITS,
                '6':serial.SIXBITS,
                '7':serial.SEVENBITS, 
                '8':serial.EIGHTBITS}[self.cmbDataBits.currentText()]

    def getParity(self):
        return {'None' :serial.PARITY_NONE,
                'Even' :serial.PARITY_EVEN,
                'Odd'  :serial.PARITY_ODD,
                'Mark' :serial.PARITY_MARK,
                'Space':serial.PARITY_SPACE}[self.cmbParity.currentText()]

    def getStopBits(self):
        return {'1'  :serial.STOPBITS_ONE,
                '1.5':serial.STOPBITS_ONE_POINT_FIVE,
                '2'  :serial.STOPBITS_TWO}[self.cmbStopBits.currentText()]

    def openPort(self):
        if self.serialport.isOpen():
            return

        _port = self.getPort()
        if '' == _port:
            QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Port is empty.")
            return

        _baudrate = self.cmbBaudRate.currentText()
        if '' == _baudrate:
            QMessageBox.information(self.defaultStyleWidget, "Invalid parameters", "Baudrate is empty.")
            return

        self.serialport.port     = _port
        self.serialport.baudrate = _baudrate
        self.serialport.bytesize = self.getDataBits()
        self.serialport.stopbits = self.getStopBits()
        self.serialport.parity   = self.getParity()
        self.serialport.rtscts   = self.chkRTSCTS.isChecked()
        self.serialport.xonxoff  = self.chkXonXoff.isChecked()
        # self.serialport.timeout  = THREAD_TIMEOUT
        # self.serialport.writeTimeout = SERIAL_WRITE_TIMEOUT
        try:
            self.serialport.open()
        except Exception as e:
            QMessageBox.critical(self.defaultStyleWidget, 
                "Could not open serial port", str(e), QMessageBox.Close)
        else:
            self._start_reader()
            self.setWindowTitle("%s on %s [%s, %s%s%s%s%s]" % (
                appInfo.title,
                self.serialport.portstr,
                self.serialport.baudrate,
                self.serialport.bytesize,
                self.serialport.parity,
                self.serialport.stopbits,
                self.serialport.rtscts and ' RTS/CTS' or '',
                self.serialport.xonxoff and ' Xon/Xoff' or '',
                )
            )
            pal = self.btnOpen.palette()
            pal.setColor(QtGui.QPalette.Button, QtGui.QColor(0,0xff,0x7f))
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Close')
            self.btnOpen.update()

    def closePort(self):
        if self.serialport.isOpen():
            self._stop_reader()
            self.serialport.close()
            self.setWindowTitle(appInfo.title)
            pal = self.btnOpen.style().standardPalette()
            self.btnOpen.setAutoFillBackground(True)
            self.btnOpen.setPalette(pal)
            self.btnOpen.setText('Open')
            self.btnOpen.update()

    def _start_reader(self):
        """Start reader thread"""
        self.receiver_thread.start()

    def _stop_reader(self):
        """Stop reader thread only, wait for clean exit of thread"""
        self.receiver_thread.join()

    def onTogglePrtCfgPnl(self):
        if self.actionPort_Config_Panel.isChecked():
            self.dockWidget_PortConfig.show()
        else:
            self.dockWidget_PortConfig.hide()

    def onToggleQckSndPnl(self):
        if self.actionQuick_Send_Panel.isChecked():
            self.dockWidget_QuickSend.show()
        else:
            self.dockWidget_QuickSend.hide()

    def onToggleHexPnl(self):
        if self.actionSend_Hex_Panel.isChecked():
            self.dockWidget_SendHex.show()
        else:
            self.dockWidget_SendHex.hide()

    def onVisiblePrtCfgPnl(self, visible):
        self.actionPort_Config_Panel.setChecked(visible)

    def onVisibleQckSndPnl(self, visible):
        self.actionQuick_Send_Panel.setChecked(visible)

    def onVisibleHexPnl(self, visible):
        self.actionSend_Hex_Panel.setChecked(visible)

    def onLocalEcho(self):
        self._localEcho = self.actionLocal_Echo.isChecked()

    def onAlwaysOnTop(self):
        if self.actionAlways_On_Top.isChecked():
            style = self.windowFlags()
            self.setWindowFlags(style|Qt.WindowStaysOnTopHint)
            self.show()
        else:
            style = self.windowFlags()
            self.setWindowFlags(style & ~Qt.WindowStaysOnTopHint)
            self.show()

    def onOpen(self):
        if self.serialport.isOpen():
            self.closePort()
        else:
            self.openPort()

    def onClear(self):
        self.txtEdtOutput.clear()

    def onSaveLog(self):
        fileName = QFileDialog.getSaveFileName(self, "Save as", os.getcwd(),
            "Log files (*.log);;Text files (*.txt);;All files (*.*)")[0]
        if fileName:
            import codecs
            with codecs.open(fileName, 'w', 'utf-8') as f:
                f.write(self.txtEdtOutput.toPlainText())

    def moveScreenCenter(self):
        w = self.frameGeometry().width()
        h = self.frameGeometry().height()
        desktop = QDesktopWidget()
        screenW = desktop.screen().width()
        screenH = desktop.screen().height()
        self.setGeometry((screenW-w)/2, (screenH-h)/2, w, h)

    def onEnumPorts(self):
        self.cmbPort.clear()
        for p in enum_ports():
            self.cmbPort.addItem(p)

    def onAbout(self):
        QMessageBox.about(self.defaultStyleWidget, "About MyTerm", appInfo.aboutme)

    def onAboutQt(self):
        QMessageBox.aboutQt(self.defaultStyleWidget)

    def onExit(self):
        if self.serialport.isOpen():
            self.closePort()
        self.close()

    def restoreLayout(self):
        if os.path.isfile(get_config_path("UILayout.dat")):
            try:
                f=open(get_config_path("UILayout.dat"), 'rb')
                geometry, state=pickle.load(f)
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))
        else:
            try:
                f=QFile(':/default_layout_qt5.dat')
                f.open(QIODevice.ReadOnly)
                geometry, state=pickle.loads(f.readAll())
                self.restoreGeometry(geometry)
                self.restoreState(state)
            except Exception as e:
                print("Exception on restoreLayout, {}".format(e))

    def saveLayout(self):
        with open(get_config_path("UILayout.dat"), 'wb') as f:
            pickle.dump((self.saveGeometry(), self.saveState()), f)

    def syncMenu(self):
        self.actionPort_Config_Panel.setChecked(not self.dockWidget_PortConfig.isHidden())
        self.actionQuick_Send_Panel.setChecked(not self.dockWidget_QuickSend.isHidden())
        self.actionSend_Hex_Panel.setChecked(not self.dockWidget_SendHex.isHidden())

    def onViewChanged(self):
        checked = self._viewGroup.checkedAction()
        if checked is None:
            self._viewMode = VIEWMODE_HEX_UPPERCASE
            self.actionHEX_UPPERCASE.setChecked(True)
        else:
            if 'Ascii' in checked.text():
                self._viewMode = VIEWMODE_ASCII
            elif 'lowercase' in checked.text():
                self._viewMode = VIEWMODE_HEX_LOWERCASE
            elif 'UPPERCASE' in checked.text():
                self._viewMode = VIEWMODE_HEX_UPPERCASE

        self.receiver_thread.setViewMode(self._viewMode)
Esempio n. 14
0
class Actions(QObject):

    def __init__(self, controller: 'Controller') -> None:
        super().__init__()

        self.controller = controller

        self.make_actions()

    def make_actions(self) -> None:
        self.save_as = QAction(QIcon.fromTheme('save-as'), 'Save Filelist As', self)
        self.save_as.setShortcut('Ctrl+s')
        self.save_as.setStatusTip('Save the current file selection')
        self.save_as.triggered.connect(self.controller.save_as)

        def on_debug(enabled):
            if enabled:
                logging.getLogger().setLevel(logging.DEBUG)
            else:
                logging.getLogger().setLevel(logging.ERROR)

        self.debug = QAction(QIcon.fromTheme('media-record'), '&Debug', self, checkable=True)
        self.debug.setStatusTip('Debug application')
        self.debug.setChecked(logging.getLogger().getEffectiveLevel() == logging.DEBUG)
        self.debug.triggered.connect(lambda: on_debug(self.debug.isChecked()))

        self.exit = QAction(QIcon.fromTheme('window-close'), '&Exit', self)
        self.exit.setShortcut('Ctrl+W')
        self.exit.setStatusTip('Close Window')
        self.exit.triggered.connect(self.controller.close_window)

        self.home = QAction(QIcon.fromTheme('go-home'), '&Go to Home', self)
        self.home.setStatusTip('Go to the Home directory')
        self.home.triggered.connect(self.controller.go_home)

        self.undo = QAction(QIcon.fromTheme('undo'), '&Undo', self)
        self.undo.setShortcut('Ctrl+Z')
        self.undo.setStatusTip('Undo the last action')

        self.redo = QAction(QIcon.fromTheme('redo'), '&Redo', self)
        self.redo.setShortcut('Ctrl+Y')
        self.redo.setStatusTip('Redo the last action')

        self.edit_copy = QAction(QIcon.fromTheme('edit-copy'), '&Copy', self)
        self.edit_copy.setShortcut('Ctrl+C')
        self.edit_copy.setStatusTip('Copy Selected Files')
        self.edit_copy.triggered.connect(self.controller.on_edit_copy)

        self.edit_cut = QAction(QIcon.fromTheme('edit-cut'), 'Cu&t', self)
        self.edit_cut.setShortcut('Ctrl+X')
        self.edit_cut.setStatusTip('Cut Selected Files')
        self.edit_cut.triggered.connect(self.controller.on_edit_cut)

        self.edit_paste = QAction(QIcon.fromTheme('edit-paste'), '&Paste', self)
        self.edit_paste.setShortcut('Ctrl+V')
        self.edit_paste.setStatusTip('Paste Files')
        self.edit_paste.triggered.connect(self.controller.on_edit_paste)

        self.edit_delete = QAction(QIcon.fromTheme('edit-delete'), '&Delete', self)
        self.edit_delete.setStatusTip('Delete Selected Files')

        self.edit_select_all = QAction(QIcon.fromTheme('edit-select-all'), '&Select All', self)
        self.edit_select_all.setShortcut('Ctrl+A')
        self.edit_select_all.setStatusTip('Select All')
        self.edit_select_all.triggered.connect(self.controller.select_all)

        self.zoom_in = QAction(QIcon.fromTheme('zoom-in'), "Zoom &In", self)
        self.zoom_in.triggered.connect(self.controller.zoom_in)
        self.zoom_in.setShortcut('Ctrl+=')
        self.zoom_out = QAction(QIcon.fromTheme('zoom-out'), "Zoom &Out", self)
        self.zoom_out.triggered.connect(self.controller.zoom_out)
        self.zoom_out.setShortcut('Ctrl+-')

        self.lod_in = QAction(QIcon.fromTheme('zoom-in'), "Level of Detail &In", self)
        self.lod_in.triggered.connect(self.controller.more_details)
        self.lod_in.setShortcut('Alt+=')
        self.lod_out = QAction(QIcon.fromTheme('zoom-out'), "Level of Detail &Out", self)
        self.lod_out.triggered.connect(self.controller.less_details)
        self.lod_out.setShortcut('Alt+-')

        self.crop_thumbnails = QAction(QIcon.fromTheme('zoom-fit-best'), "Crop Thumbnails", self, checkable=True)
        self.crop_thumbnails.triggered.connect(
            lambda: self.controller.set_crop_thumbnails(self.crop_thumbnails.isChecked()))

        self.new_window = QAction(QIcon.fromTheme('window-new'), "New Window", self)
        self.new_window.triggered.connect(lambda x: self.controller.new_controller(clone=True))
        self.new_window.setShortcut('Ctrl+N')

        self.parent_directory = QAction(self.controller.app.qapp.style().standardIcon(QStyle.SP_FileDialogToParent),
                                        "Parent Directory")
        self.parent_directory.triggered.connect(self.controller.parent_directory)

        self.back = QAction(QIcon.fromTheme('back'), 'Go &back', self)
        self.back.setShortcut('Alt+Left')
        self.back.setStatusTip('Go back in history')
        self.back.setEnabled(False)
        self.back.triggered.connect(self.controller.go_back)

        self.forward = QAction(QIcon.fromTheme('forward'), 'Go &forward', self)
        self.forward.setShortcut('Alt+Right')
        self.forward.setStatusTip('Go forward in history')
        self.forward.setEnabled(False)
        self.forward.triggered.connect(self.controller.go_forward)

        self.search = QAction(QIcon.fromTheme('system-search'), 'Search', self)
        self.search.setShortcut('F3')
        self.search.setStatusTip('Search for files')
        self.search.triggered.connect(self.controller.show_search)

        self.reload = QAction(QIcon.fromTheme('reload'), 'Reload', self)
        self.reload.setShortcut('F5')
        self.reload.setStatusTip('Reload the View')
        self.reload.triggered.connect(self.controller.reload)

        self.rename = QAction(QIcon.fromTheme('rename'), 'Rename', self)
        self.rename.setShortcut('F2')
        self.rename.setStatusTip('Rename the current file')
        self.rename.triggered.connect(lambda checked: self.controller.show_rename_dialog())

        self.reload_thumbnails = QAction(QIcon.fromTheme('edit-delete'), 'Reload Thumbnails', self)
        self.reload_thumbnails.setStatusTip('Reload Thumbnails')
        self.reload_thumbnails.triggered.connect(self.controller.reload_thumbnails)

        self.reload_metadata = QAction(QIcon.fromTheme('edit-delete'), 'Reload MetaData', self)
        self.reload_metadata.setStatusTip('Reload MetaData')
        self.reload_metadata.triggered.connect(self.controller.reload_metadata)

        self.make_directory_thumbnails = QAction(QIcon.fromTheme('folder'), 'Make Directory Thumbnails', self)
        self.make_directory_thumbnails.setStatusTip('Make Directory Thumbnails')
        self.make_directory_thumbnails.triggered.connect(self.controller.make_directory_thumbnails)

        self.prepare = QAction(QIcon.fromTheme('media-playback-start'), 'Load Thumbnails', self)
        self.prepare.setShortcut('F6')
        self.prepare.setStatusTip('Load Thumbnails')
        self.prepare.triggered.connect(self.controller.prepare)

        self.view_detail_view = QAction("Detail View", self, checkable=True)
        self.view_icon_view = QAction("Icon View", self, checkable=True)
        self.view_small_icon_view = QAction("Small Icon View", self, checkable=True)

        self.view_icon_view = QAction(QIcon.fromTheme("view-grid-symbolic"),
                                      "Icon View")
        self.view_icon_view.triggered.connect(self.controller.view_icon_view)

        self.view_small_icon_view = QAction(QIcon.fromTheme("view-list-symbolic"),
                                            "Small Icon View")
        self.view_small_icon_view.triggered.connect(self.controller.view_small_icon_view)

        self.view_detail_view = QAction(QIcon.fromTheme("view-more-horizontal-symbolic"),
                                        "Detail View")
        self.view_detail_view.triggered.connect(self.controller.view_detail_view)

        self.view_group = QActionGroup(self)
        self.view_group.addAction(self.view_detail_view)
        self.view_group.addAction(self.view_icon_view)
        self.view_group.addAction(self.view_small_icon_view)

        self.show_hidden = QAction(QIcon.fromTheme('camera-photo'), "Show Hidden", self, checkable=True)
        self.show_hidden.triggered.connect(self.controller.show_hidden)
        self.show_hidden.setShortcut('Ctrl+H')
        self.show_hidden.setChecked(settings.value("globals/show_hidden", False, bool))

        self.show_filtered = QAction(QIcon.fromTheme('camera-photo'), "Show Filtered", self, checkable=True)
        self.show_filtered.triggered.connect(self.controller.show_filtered)

        self.show_abspath = QAction("Show AbsPath", self, checkable=True)
        self.show_abspath.triggered.connect(self.controller.show_abspath)

        self.show_basename = QAction("Show Basename", self, checkable=True)
        self.show_basename.triggered.connect(self.controller.show_basename)

        self.path_options_group = QActionGroup(self)
        self.path_options_group.addAction(self.show_abspath)
        self.path_options_group.addAction(self.show_basename)

        self.toggle_timegaps = QAction("Show Time Gaps", self, checkable=True)
        self.toggle_timegaps.triggered.connect(self.controller.toggle_timegaps)

        # Sorting Options
        self.sort_directories_first = QAction("Directories First", checkable=True)
        self.sort_directories_first.triggered.connect(
            lambda: self.controller._sorter.set_directories_first(self.sort_directories_first.isChecked()))
        self.sort_directories_first.setChecked(True)

        self.sort_reversed = QAction("Reverse Sort", checkable=True)
        self.sort_reversed.triggered.connect(
            lambda: self.controller.set_sort_reversed(self.sort_reversed.isChecked()))

        self.sort_by_name = QAction("Sort by Name", checkable=True)
        self.sort_by_name.triggered.connect(lambda:
                                            self.controller.set_sort_key_func(
                                                lambda x: numeric_sort_key(x.basename().lower())))
        self.sort_by_name.setChecked(True)

        self.sort_by_size = QAction("Sort by Size", checkable=True)
        self.sort_by_size.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.size))

        self.sort_by_ext = QAction("Sort by Extension", checkable=True)
        self.sort_by_ext.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.ext))

        self.sort_by_date = QAction("Sort by Date", checkable=True)
        self.sort_by_date.triggered.connect(lambda: self.controller.set_sort_key_func(FileInfo.mtime))

        def framerate_key(fileinfo):
            metadata = fileinfo.metadata()
            return metadata.get('framerate', 0)

        self.sort_by_framerate = QAction("Sort by Framerate", checkable=True)
        self.sort_by_framerate.triggered.connect(lambda: self.controller.set_sort_key_func(framerate_key))

        def aspect_ratio_key(fileinfo):
            metadata = fileinfo.metadata()
            if 'width' in metadata and 'height' in metadata and metadata['height'] != 0:
                return metadata['width'] / metadata['height']
            else:
                return 0

        self.sort_by_aspect_ratio = QAction("Sort by Aspect Ratio", checkable=True)
        self.sort_by_aspect_ratio.triggered.connect(lambda: self.controller.set_sort_key_func(aspect_ratio_key))

        def area_key(fileinfo):
            metadata = fileinfo.metadata()
            if 'width' in metadata and 'height' in metadata:
                return metadata['width'] * metadata['height']
            else:
                return 0

        self.sort_by_area = QAction("Sort by Area", checkable=True)
        self.sort_by_area.triggered.connect(lambda: self.controller.set_sort_key_func(area_key))

        def duration_key(fileinfo):
            metadata = fileinfo.metadata()
            if 'duration' in metadata:
                return metadata['duration']
            else:
                return 0

        self.sort_by_duration = QAction("Sort by Duration", checkable=True)
        self.sort_by_duration.triggered.connect(lambda: self.controller.set_sort_key_func(duration_key))

        self.sort_by_user = QAction("Sort by User", checkable=True)
        self.sort_by_group = QAction("Sort by Group", checkable=True)
        self.sort_by_permission = QAction("Sort by Permission", checkable=True)
        self.sort_by_random = QAction("Random Shuffle", checkable=True)
        # self.sort_by_random.triggered.connect(lambda: self.controller.set_sort_key_func(None))
        self.sort_by_random.triggered.connect(lambda: print("sort_by_random: not implemented"))

        self.sort_group = QActionGroup(self)
        self.sort_group.addAction(self.sort_by_name)
        self.sort_group.addAction(self.sort_by_size)
        self.sort_group.addAction(self.sort_by_ext)
        self.sort_group.addAction(self.sort_by_date)
        self.sort_group.addAction(self.sort_by_area)
        self.sort_group.addAction(self.sort_by_duration)
        self.sort_group.addAction(self.sort_by_aspect_ratio)
        self.sort_group.addAction(self.sort_by_framerate)
        self.sort_group.addAction(self.sort_by_user)
        self.sort_group.addAction(self.sort_by_group)
        self.sort_group.addAction(self.sort_by_permission)
        self.sort_group.addAction(self.sort_by_random)

        self.group_by_none = QAction("Don't Group", checkable=True)
        self.group_by_none.triggered.connect(self.controller.set_grouper_by_none)
        self.group_by_none.setChecked(True)

        self.group_by_day = QAction("Group by Day", checkable=True)
        self.group_by_day.triggered.connect(self.controller.set_grouper_by_day)

        self.group_by_directory = QAction("Group by Directory", checkable=True)
        self.group_by_directory.triggered.connect(self.controller.set_grouper_by_directory)

        self.group_by_duration = QAction("Group by Duration", checkable=True)
        self.group_by_duration.triggered.connect(self.controller.set_grouper_by_duration)

        self.group_group = QActionGroup(self)
        self.group_group.addAction(self.group_by_none)
        self.group_group.addAction(self.group_by_day)
        self.group_group.addAction(self.group_by_directory)
        self.group_group.addAction(self.group_by_duration)

        self.about = QAction(QIcon.fromTheme('help-about'), 'About dt-fileview', self)
        self.about.setStatusTip('Show About dialog')

        self.about.triggered.connect(self.controller.show_about_dialog)

        self.show_preferences = QAction(QIcon.fromTheme("preferences-system"), "Preferencs...")
        self.show_preferences.triggered.connect(self.controller.show_preferences)

        def on_filter_pin(checked) -> None:
            # FIXME: Could use icon state for this
            if checked:
                self.filter_pin.setIcon(QIcon.fromTheme("remmina-pin-down"))
            else:
                self.filter_pin.setIcon(QIcon.fromTheme("remmina-pin-up"))

        self.filter_pin = QAction(QIcon.fromTheme("remmina-pin-up"), "Pin the filter", checkable=True)
        self.filter_pin.triggered.connect(self.controller.set_filter_pin)
        # self.filter_pin.setToolTip("Pin the filter")

        self.filter_pin.triggered.connect(on_filter_pin)
Esempio n. 15
0
class MainUI(QtWidgets.QMainWindow, main_window_class):
    connectionLostSignal = pyqtSignal(str, str)
    connectionInitiatedSignal = pyqtSignal(str)
    batteryUpdatedSignal = pyqtSignal(int, object, object)
    connectionDoneSignal = pyqtSignal(str)
    connectionFailedSignal = pyqtSignal(str, str)
    disconnectedSignal = pyqtSignal(str)
    linkQualitySignal = pyqtSignal(int)

    _input_device_error_signal = pyqtSignal(str)
    _input_discovery_signal = pyqtSignal(object)
    _log_error_signal = pyqtSignal(object, str)

    def __init__(self, *args):
        super(MainUI, self).__init__(*args)
        self.setupUi(self)

        # Restore window size if present in the config file
        try:
            size = Config().get("window_size")
            self.resize(size[0], size[1])
        except KeyError:
            pass

        ######################################################
        # By lxrocks
        # 'Skinny Progress Bar' tweak for Yosemite
        # Tweak progress bar - artistic I am not - so pick your own colors !!!
        # Only apply to Yosemite
        ######################################################
        import platform

        if platform.system() == 'Darwin':

            (Version, junk, machine) = platform.mac_ver()
            logger.info("This is a MAC - checking if we can apply Progress "
                        "Bar Stylesheet for Yosemite Skinny Bars ")
            yosemite = (10, 10, 0)
            tVersion = tuple(map(int, (Version.split("."))))

            if tVersion >= yosemite:
                logger.info("Found Yosemite - applying stylesheet")

                tcss = """
                    QProgressBar {
                        border: 1px solid grey;
                        border-radius: 5px;
                        text-align: center;
                    }
                    QProgressBar::chunk {
                        background-color: """ + COLOR_BLUE + """;
                    }
                 """
                self.setStyleSheet(tcss)

            else:
                logger.info("Pre-Yosemite - skinny bar stylesheet not applied")

        ######################################################

        self.cf = Crazyflie(ro_cache=None,
                            rw_cache=cfclient.config_path + "/cache")

        cflib.crtp.init_drivers(enable_debug_driver=Config()
                                .get("enable_debug_driver"))

        zmq_params = ZMQParamAccess(self.cf)
        zmq_params.start()

        zmq_leds = ZMQLEDDriver(self.cf)
        zmq_leds.start()

        self.scanner = ScannerThread()
        self.scanner.interfaceFoundSignal.connect(self.foundInterfaces)
        self.scanner.start()

        # Create and start the Input Reader
        self._statusbar_label = QLabel("No input-device found, insert one to"
                                       " fly.")
        self.statusBar().addWidget(self._statusbar_label)

        self.joystickReader = JoystickReader()
        self._active_device = ""
        # self.configGroup = QActionGroup(self._menu_mappings, exclusive=True)

        self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True)

        # TODO: Need to reload configs
        # ConfigManager().conf_needs_reload.add_callback(self._reload_configs)

        self.connect_input = QShortcut("Ctrl+I", self.connectButton,
                                       self._connect)
        self.cf.connection_failed.add_callback(
            self.connectionFailedSignal.emit)
        self.connectionFailedSignal.connect(self._connection_failed)

        self._input_device_error_signal.connect(
            self._display_input_device_error)
        self.joystickReader.device_error.add_callback(
            self._input_device_error_signal.emit)
        self._input_discovery_signal.connect(self.device_discovery)
        self.joystickReader.device_discovery.add_callback(
            self._input_discovery_signal.emit)

        # Hide the 'File' menu on OS X, since its only item, 'Exit', gets
        # merged into the application menu.
        if sys.platform == 'darwin':
            self.menuFile.menuAction().setVisible(False)

        # Connect UI signals
        self.logConfigAction.triggered.connect(self._show_connect_dialog)
        self.interfaceCombo.currentIndexChanged['QString'].connect(
            self.interfaceChanged)
        self.connectButton.clicked.connect(self._connect)
        self.scanButton.clicked.connect(self._scan)
        self.menuItemConnect.triggered.connect(self._connect)
        self.menuItemConfInputDevice.triggered.connect(
            self._show_input_device_config_dialog)
        self.menuItemExit.triggered.connect(self.closeAppRequest)
        self.batteryUpdatedSignal.connect(self._update_battery)
        self._menuitem_rescandevices.triggered.connect(self._rescan_devices)
        self._menuItem_openconfigfolder.triggered.connect(
            self._open_config_folder)

        self.address.setValue(0xE7E7E7E7E7)

        self._auto_reconnect_enabled = Config().get("auto_reconnect")
        self.autoReconnectCheckBox.toggled.connect(
            self._auto_reconnect_changed)
        self.autoReconnectCheckBox.setChecked(Config().get("auto_reconnect"))

        self._disable_input = False

        self.joystickReader.input_updated.add_callback(
            lambda *args: self._disable_input or
            self.cf.commander.send_setpoint(*args))

        self.joystickReader.assisted_input_updated.add_callback(
            lambda *args: self._disable_input or
            self.cf.commander.send_velocity_world_setpoint(*args))

        self.joystickReader.heighthold_input_updated.add_callback(
            lambda *args: self._disable_input or
            self.cf.commander.send_zdistance_setpoint(*args))

        self.joystickReader.hover_input_updated.add_callback(
            self.cf.commander.send_hover_setpoint)

        # Connection callbacks and signal wrappers for UI protection
        self.cf.connected.add_callback(self.connectionDoneSignal.emit)
        self.connectionDoneSignal.connect(self._connected)
        self.cf.disconnected.add_callback(self.disconnectedSignal.emit)
        self.disconnectedSignal.connect(self._disconnected)
        self.cf.connection_lost.add_callback(self.connectionLostSignal.emit)
        self.connectionLostSignal.connect(self._connection_lost)
        self.cf.connection_requested.add_callback(
            self.connectionInitiatedSignal.emit)
        self.connectionInitiatedSignal.connect(self._connection_initiated)
        self._log_error_signal.connect(self._logging_error)

        self.batteryBar.setTextVisible(False)
        self.batteryBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))

        self.linkQualityBar.setTextVisible(False)
        self.linkQualityBar.setStyleSheet(progressbar_stylesheet(COLOR_BLUE))

        # Connect link quality feedback
        self.cf.link_quality_updated.add_callback(self.linkQualitySignal.emit)
        self.linkQualitySignal.connect(
            lambda percentage: self.linkQualityBar.setValue(percentage))

        self._selected_interface = None
        self._initial_scan = True
        self._scan()

        # Parse the log configuration files
        self.logConfigReader = LogConfigReader(self.cf)

        self._current_input_config = None
        self._active_config = None
        self._active_config = None

        self.inputConfig = None

        # Add things to helper so tabs can access it
        cfclient.ui.pluginhelper.cf = self.cf
        cfclient.ui.pluginhelper.inputDeviceReader = self.joystickReader
        cfclient.ui.pluginhelper.logConfigReader = self.logConfigReader
        cfclient.ui.pluginhelper.mainUI = self

        self.logConfigDialogue = LogConfigDialogue(cfclient.ui.pluginhelper)
        self._bootloader_dialog = BootloaderDialog(cfclient.ui.pluginhelper)
        self._cf2config_dialog = Cf2ConfigDialog(cfclient.ui.pluginhelper)
        self._cf1config_dialog = Cf1ConfigDialog(cfclient.ui.pluginhelper)
        self.menuItemBootloader.triggered.connect(self._bootloader_dialog.show)
        self._about_dialog = AboutDialog(cfclient.ui.pluginhelper)
        self.menuItemAbout.triggered.connect(self._about_dialog.show)
        self._menu_cf2_config.triggered.connect(self._cf2config_dialog.show)
        self._menu_cf1_config.triggered.connect(self._cf1config_dialog.show)

        # Load and connect tabs
        self.tabsMenuItem = QMenu("Tabs", self.menuView, enabled=True)
        self.menuView.addMenu(self.tabsMenuItem)

        # self.tabsMenuItem.setMenu(QtWidgets.QMenu())
        tabItems = {}
        self.loadedTabs = []
        for tabClass in cfclient.ui.tabs.available:
            tab = tabClass(self.tabs, cfclient.ui.pluginhelper)
            item = QtWidgets.QAction(tab.getMenuName(), self, checkable=True)
            item.toggled.connect(tab.toggleVisibility)
            self.tabsMenuItem.addAction(item)
            tabItems[tab.getTabName()] = item
            self.loadedTabs.append(tab)
            if not tab.enabled:
                item.setEnabled(False)

        # First instantiate all tabs and then open them in the correct order
        try:
            for tName in Config().get("open_tabs").split(","):
                t = tabItems[tName]
                if (t is not None and t.isEnabled()):
                    # Toggle though menu so it's also marked as open there
                    t.toggle()
        except Exception as e:
            logger.warning("Exception while opening tabs [{}]".format(e))

        # Loading toolboxes (A bit of magic for a lot of automatic)
        self.toolboxesMenuItem = QMenu("Toolboxes", self.menuView,
                                       enabled=True)
        self.menuView.addMenu(self.toolboxesMenuItem)

        self.toolboxes = []
        for t_class in cfclient.ui.toolboxes.toolboxes:
            toolbox = t_class(cfclient.ui.pluginhelper)
            dockToolbox = MyDockWidget(toolbox.getName())
            dockToolbox.setWidget(toolbox)
            self.toolboxes += [dockToolbox, ]

            # Add menu item for the toolbox
            item = QtWidgets.QAction(toolbox.getName(), self)
            item.setCheckable(True)
            item.triggered.connect(self.toggleToolbox)
            self.toolboxesMenuItem.addAction(item)

            dockToolbox.closed.connect(lambda: self.toggleToolbox(False))

            # Setup some introspection
            item.dockToolbox = dockToolbox
            item.menuItem = item
            dockToolbox.dockToolbox = dockToolbox
            dockToolbox.menuItem = item

        # References to all the device sub-menus in the "Input device" menu
        self._all_role_menus = ()
        # Used to filter what new devices to add default mapping to
        self._available_devices = ()
        # Keep track of mux nodes so we can enable according to how many
        # devices we have
        self._all_mux_nodes = ()

        # Check which Input muxes are available
        self._mux_group = QActionGroup(self._menu_inputdevice, exclusive=True)
        for m in self.joystickReader.available_mux():
            node = QAction(m.name,
                           self._menu_inputdevice,
                           checkable=True,
                           enabled=False)
            node.toggled.connect(self._mux_selected)
            self._mux_group.addAction(node)
            self._menu_inputdevice.addAction(node)
            self._all_mux_nodes += (node,)
            mux_subnodes = ()
            for name in m.supported_roles():
                sub_node = QMenu("    {}".format(name),
                                 self._menu_inputdevice,
                                 enabled=False)
                self._menu_inputdevice.addMenu(sub_node)
                mux_subnodes += (sub_node,)
                self._all_role_menus += ({"muxmenu": node,
                                          "rolemenu": sub_node},)
            node.setData((m, mux_subnodes))

        self._mapping_support = True

    def disable_input(self, disable):
        """
        Disable the gamepad input to be able to send setpoint from a tab
        """
        self._disable_input = disable

    def interfaceChanged(self, interface):
        if interface == INTERFACE_PROMPT_TEXT:
            self._selected_interface = None
        else:
            self._selected_interface = interface
        self._update_ui_state()

    def foundInterfaces(self, interfaces):
        selected_interface = self._selected_interface

        self.interfaceCombo.clear()
        self.interfaceCombo.addItem(INTERFACE_PROMPT_TEXT)

        formatted_interfaces = []
        for i in interfaces:
            if len(i[1]) > 0:
                interface = "%s - %s" % (i[0], i[1])
            else:
                interface = i[0]
            formatted_interfaces.append(interface)
        self.interfaceCombo.addItems(formatted_interfaces)

        if self._initial_scan:
            self._initial_scan = False

            try:
                if len(Config().get("link_uri")) > 0:
                    formatted_interfaces.index(Config().get("link_uri"))
                    selected_interface = Config().get("link_uri")
            except KeyError:
                #  The configuration for link_uri was not found
                pass
            except ValueError:
                #  The saved URI was not found while scanning
                pass

        if len(interfaces) == 1 and selected_interface is None:
            selected_interface = interfaces[0][0]

        newIndex = 0
        if selected_interface is not None:
            try:
                newIndex = formatted_interfaces.index(selected_interface) + 1
            except ValueError:
                pass

        self.interfaceCombo.setCurrentIndex(newIndex)

        self.uiState = UIState.DISCONNECTED
        self._update_ui_state()

    def _update_ui_state(self):
        if self.uiState == UIState.DISCONNECTED:
            self.setWindowTitle("Not connected")
            canConnect = self._selected_interface is not None
            self.menuItemConnect.setText("Connect to Crazyflie")
            self.menuItemConnect.setEnabled(canConnect)
            self.connectButton.setText("Connect")
            self.connectButton.setToolTip("Connect to the Crazyflie on"
                                          "the selected interface (Ctrl+I)")
            self.connectButton.setEnabled(canConnect)
            self.scanButton.setText("Scan")
            self.scanButton.setEnabled(True)
            self.address.setEnabled(True)
            self.batteryBar.setValue(3000)
            self._menu_cf2_config.setEnabled(False)
            self._menu_cf1_config.setEnabled(True)
            self.linkQualityBar.setValue(0)
            self.menuItemBootloader.setEnabled(True)
            self.logConfigAction.setEnabled(False)
            self.interfaceCombo.setEnabled(True)
        elif self.uiState == UIState.CONNECTED:
            s = "Connected on %s" % self._selected_interface
            self.setWindowTitle(s)
            self.menuItemConnect.setText("Disconnect")
            self.menuItemConnect.setEnabled(True)
            self.connectButton.setText("Disconnect")
            self.connectButton.setToolTip("Disconnect from"
                                          "the Crazyflie (Ctrl+I)")
            self.scanButton.setEnabled(False)
            self.logConfigAction.setEnabled(True)
            # Find out if there's an I2C EEPROM, otherwise don't show the
            # dialog.
            if len(self.cf.mem.get_mems(MemoryElement.TYPE_I2C)) > 0:
                self._menu_cf2_config.setEnabled(True)
            self._menu_cf1_config.setEnabled(False)
        elif self.uiState == UIState.CONNECTING:
            s = "Connecting to {} ...".format(self._selected_interface)
            self.setWindowTitle(s)
            self.menuItemConnect.setText("Cancel")
            self.menuItemConnect.setEnabled(True)
            self.connectButton.setText("Cancel")
            self.connectButton.setToolTip(
                "Cancel connecting to the Crazyflie")
            self.scanButton.setEnabled(False)
            self.address.setEnabled(False)
            self.menuItemBootloader.setEnabled(False)
            self.interfaceCombo.setEnabled(False)
        elif self.uiState == UIState.SCANNING:
            self.setWindowTitle("Scanning ...")
            self.connectButton.setText("Connect")
            self.menuItemConnect.setEnabled(False)
            self.connectButton.setText("Connect")
            self.connectButton.setEnabled(False)
            self.scanButton.setText("Scanning...")
            self.scanButton.setEnabled(False)
            self.address.setEnabled(False)
            self.menuItemBootloader.setEnabled(False)
            self.interfaceCombo.setEnabled(False)

    @pyqtSlot(bool)
    def toggleToolbox(self, display):
        menuItem = self.sender().menuItem
        dockToolbox = self.sender().dockToolbox

        if display and not dockToolbox.isVisible():
            dockToolbox.widget().enable()
            self.addDockWidget(dockToolbox.widget().preferedDockArea(),
                               dockToolbox)
            dockToolbox.show()
        elif not display:
            dockToolbox.widget().disable()
            self.removeDockWidget(dockToolbox)
            dockToolbox.hide()
            menuItem.setChecked(False)

    def _rescan_devices(self):
        self._statusbar_label.setText("No inputdevice connected!")
        self._menu_devices.clear()
        self._active_device = ""
        self.joystickReader.stop_input()

        # for c in self._menu_mappings.actions():
        #    c.setEnabled(False)
        # devs = self.joystickReader.available_devices()
        # if (len(devs) > 0):
        #    self.device_discovery(devs)

    def _show_input_device_config_dialog(self):
        self.inputConfig = InputConfigDialogue(self.joystickReader)
        self.inputConfig.show()

    def _auto_reconnect_changed(self, checked):
        self._auto_reconnect_enabled = checked
        Config().set("auto_reconnect", checked)
        logger.info("Auto reconnect enabled: {}".format(checked))

    def _show_connect_dialog(self):
        self.logConfigDialogue.show()

    def _update_battery(self, timestamp, data, logconf):
        self.batteryBar.setValue(int(data["pm.vbat"] * 1000))

        color = COLOR_BLUE
        # TODO firmware reports fully-charged state as 'Battery',
        # rather than 'Charged'
        if data["pm.state"] in [BatteryStates.CHARGING, BatteryStates.CHARGED]:
            color = COLOR_GREEN
        elif data["pm.state"] == BatteryStates.LOW_POWER:
            color = COLOR_RED

        self.batteryBar.setStyleSheet(progressbar_stylesheet(color))
        self._aff_volts.setText(("%.3f" % data["pm.vbat"]))

    def _connected(self):
        self.uiState = UIState.CONNECTED
        self._update_ui_state()

        Config().set("link_uri", str(self._selected_interface))

        lg = LogConfig("Battery", 1000)
        lg.add_variable("pm.vbat", "float")
        lg.add_variable("pm.state", "int8_t")
        try:
            self.cf.log.add_config(lg)
            lg.data_received_cb.add_callback(self.batteryUpdatedSignal.emit)
            lg.error_cb.add_callback(self._log_error_signal.emit)
            lg.start()
        except KeyError as e:
            logger.warning(str(e))

        mems = self.cf.mem.get_mems(MemoryElement.TYPE_DRIVER_LED)
        if len(mems) > 0:
            mems[0].write_data(self._led_write_done)

    def _disconnected(self):
        self.uiState = UIState.DISCONNECTED
        self._update_ui_state()

    def _connection_initiated(self):
        self.uiState = UIState.CONNECTING
        self._update_ui_state()

    def _led_write_done(self, mem, addr):
        logger.info("LED write done callback")

    def _logging_error(self, log_conf, msg):
        QMessageBox.about(self, "Log error", "Error when starting log config"
                                             " [{}]: {}".format(log_conf.name,
                                                                msg))

    def _connection_lost(self, linkURI, msg):
        if not self._auto_reconnect_enabled:
            if self.isActiveWindow():
                warningCaption = "Communication failure"
                error = "Connection lost to {}: {}".format(linkURI, msg)
                QMessageBox.critical(self, warningCaption, error)
                self.uiState = UIState.DISCONNECTED
                self._update_ui_state()
        else:
            self._connect()

    def _connection_failed(self, linkURI, error):
        if not self._auto_reconnect_enabled:
            msg = "Failed to connect on {}: {}".format(linkURI, error)
            warningCaption = "Communication failure"
            QMessageBox.critical(self, warningCaption, msg)
            self.uiState = UIState.DISCONNECTED
            self._update_ui_state()
        else:
            self._connect()

    def closeEvent(self, event):
        self.hide()
        self.cf.close_link()
        Config().save_file()

    def resizeEvent(self, event):
        Config().set("window_size", [event.size().width(),
                                     event.size().height()])

    def _connect(self):
        if self.uiState == UIState.CONNECTED:
            self.cf.close_link()
        elif self.uiState == UIState.CONNECTING:
            self.cf.close_link()
            self.uiState = UIState.DISCONNECTED
            self._update_ui_state()
        else:
            self.cf.open_link(self._selected_interface)

    def _scan(self):
        self.uiState = UIState.SCANNING
        self._update_ui_state()
        self.scanner.scanSignal.emit(self.address.value())

    def _display_input_device_error(self, error):
        self.cf.close_link()
        QMessageBox.critical(self, "Input device error", error)

    def _mux_selected(self, checked):
        """Called when a new mux is selected. The menu item contains a
        reference to the raw mux object as well as to the associated device
        sub-nodes"""
        if not checked:
            (mux, sub_nodes) = self.sender().data()
            for s in sub_nodes:
                s.setEnabled(False)
        else:
            (mux, sub_nodes) = self.sender().data()
            for s in sub_nodes:
                s.setEnabled(True)
            self.joystickReader.set_mux(mux=mux)

            # Go though the tree and select devices/mapping that was
            # selected before it was disabled.
            for role_node in sub_nodes:
                for dev_node in role_node.children():
                    if type(dev_node) is QAction and dev_node.isChecked():
                        dev_node.toggled.emit(True)

            self._update_input_device_footer()

    def _get_dev_status(self, device):
        msg = "{}".format(device.name)
        if device.supports_mapping:
            map_name = "No input mapping"
            if device.input_map:
                map_name = device.input_map_name
            msg += " ({})".format(map_name)
        return msg

    def _update_input_device_footer(self):
        """Update the footer in the bottom of the UI with status for the
        input device and its mapping"""

        msg = ""

        if len(self.joystickReader.available_devices()) > 0:
            mux = self.joystickReader._selected_mux
            msg = "Using {} mux with ".format(mux.name)
            for key in list(mux._devs.keys())[:-1]:
                if mux._devs[key]:
                    msg += "{}, ".format(self._get_dev_status(mux._devs[key]))
                else:
                    msg += "N/A, "
            # Last item
            key = list(mux._devs.keys())[-1]
            if mux._devs[key]:
                msg += "{}".format(self._get_dev_status(mux._devs[key]))
            else:
                msg += "N/A"
        else:
            msg = "No input device found"
        self._statusbar_label.setText(msg)

    def _inputdevice_selected(self, checked):
        """Called when a new input device has been selected from the menu. The
        data in the menu object is the associated map menu (directly under the
        item in the menu) and the raw device"""
        (map_menu, device, mux_menu) = self.sender().data()
        if not checked:
            if map_menu:
                map_menu.setEnabled(False)
                # Do not close the device, since we don't know exactly
                # how many devices the mux can have open. When selecting a
                # new mux the old one will take care of this.
        else:
            if map_menu:
                map_menu.setEnabled(True)

            (mux, sub_nodes) = mux_menu.data()
            for role_node in sub_nodes:
                for dev_node in role_node.children():
                    if type(dev_node) is QAction and dev_node.isChecked():
                        if device.id == dev_node.data()[1].id \
                                and dev_node is not self.sender():
                            dev_node.setChecked(False)

            role_in_mux = str(self.sender().parent().title()).strip()
            logger.info("Role of {} is {}".format(device.name,
                                                  role_in_mux))

            Config().set("input_device", str(device.name))

            self._mapping_support = self.joystickReader.start_input(
                device.name,
                role_in_mux)
        self._update_input_device_footer()

    def _inputconfig_selected(self, checked):
        """Called when a new configuration has been selected from the menu. The
        data in the menu object is a referance to the device QAction in parent
        menu. This contains a referance to the raw device."""
        if not checked:
            return

        selected_mapping = str(self.sender().text())
        device = self.sender().data().data()[1]
        self.joystickReader.set_input_map(device.name, selected_mapping)
        self._update_input_device_footer()

    def device_discovery(self, devs):
        """Called when new devices have been added"""
        for menu in self._all_role_menus:
            role_menu = menu["rolemenu"]
            mux_menu = menu["muxmenu"]
            dev_group = QActionGroup(role_menu, exclusive=True)
            for d in devs:
                dev_node = QAction(d.name, role_menu, checkable=True,
                                   enabled=True)
                role_menu.addAction(dev_node)
                dev_group.addAction(dev_node)
                dev_node.toggled.connect(self._inputdevice_selected)

                map_node = None
                if d.supports_mapping:
                    map_node = QMenu("    Input map", role_menu, enabled=False)
                    map_group = QActionGroup(role_menu, exclusive=True)
                    # Connect device node to map node for easy
                    # enabling/disabling when selection changes and device
                    # to easily enable it
                    dev_node.setData((map_node, d))
                    for c in ConfigManager().get_list_of_configs():
                        node = QAction(c, map_node, checkable=True,
                                       enabled=True)
                        node.toggled.connect(self._inputconfig_selected)
                        map_node.addAction(node)
                        # Connect all the map nodes back to the device
                        # action node where we can access the raw device
                        node.setData(dev_node)
                        map_group.addAction(node)
                        # If this device hasn't been found before, then
                        # select the default mapping for it.
                        if d not in self._available_devices:
                            last_map = Config().get("device_config_mapping")
                            if d.name in last_map and last_map[d.name] == c:
                                node.setChecked(True)
                    role_menu.addMenu(map_node)
                dev_node.setData((map_node, d, mux_menu))

        # Update the list of what devices we found
        # to avoid selecting default mapping for all devices when
        # a new one is inserted
        self._available_devices = ()
        for d in devs:
            self._available_devices += (d,)

        # Only enable MUX nodes if we have enough devies to cover
        # the roles
        for mux_node in self._all_mux_nodes:
            (mux, sub_nodes) = mux_node.data()
            if len(mux.supported_roles()) <= len(self._available_devices):
                mux_node.setEnabled(True)

        # TODO: Currently only supports selecting default mux
        if self._all_mux_nodes[0].isEnabled():
            self._all_mux_nodes[0].setChecked(True)

        # If the previous length of the available devies was 0, then select
        # the default on. If that's not available then select the first
        # on in the list.
        # TODO: This will only work for the "Normal" mux so this will be
        #       selected by default
        if Config().get("input_device") in [d.name for d in devs]:
            for dev_menu in self._all_role_menus[0]["rolemenu"].actions():
                if dev_menu.text() == Config().get("input_device"):
                    dev_menu.setChecked(True)
        else:
            # Select the first device in the first mux (will always be "Normal"
            # mux)
            self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True)
            logger.info("Select first device")

        self._update_input_device_footer()

    def _open_config_folder(self):
        QDesktopServices.openUrl(
            QUrl("file:///" +
                 QDir.toNativeSeparators(cfclient.config_path)))

    def closeAppRequest(self):
        self.close()
        sys.exit(0)
Esempio n. 16
0
class SpreadSheet(QMainWindow):

    dateFormats = ["dd/M/yyyy", "yyyy/M/dd", "dd.MM.yyyy"]

    currentDateFormat = dateFormats[0]

    def __init__(self, rows, cols, parent = None):
        super(SpreadSheet, self).__init__(parent)

        self.toolBar = QToolBar()
        self.addToolBar(self.toolBar)
        self.formulaInput = QLineEdit()
        self.cellLabel = QLabel(self.toolBar)
        self.cellLabel.setMinimumSize(80, 0)
        self.toolBar.addWidget(self.cellLabel)
        self.toolBar.addWidget(self.formulaInput)
        self.table = QTableWidget(rows, cols, self)
        for c in range(cols):
            character = chr(ord('A') + c)
            self.table.setHorizontalHeaderItem(c, QTableWidgetItem(character))

        self.table.setItemPrototype(self.table.item(rows - 1, cols - 1))
        self.table.setItemDelegate(SpreadSheetDelegate(self))
        self.createActions()
        self.updateColor(0)
        self.setupMenuBar()
        self.setupContents()
        self.setupContextMenu()
        self.setCentralWidget(self.table)
        self.statusBar()
        self.table.currentItemChanged.connect(self.updateStatus)
        self.table.currentItemChanged.connect(self.updateColor)
        self.table.currentItemChanged.connect(self.updateLineEdit)
        self.table.itemChanged.connect(self.updateStatus)
        self.formulaInput.returnPressed.connect(self.returnPressed)
        self.table.itemChanged.connect(self.updateLineEdit)
        self.setWindowTitle("Spreadsheet")
        # self.circle_widget = QDockWidget(self)
        # self.circle_widget.setWidget(circleWidget(self))
        # self.addDockWidget(Qt.AllDockWidgetAreas, self.circle_widget)
        # Create a sample listener and controller
        self.listener = handListener()
        self.controller = Leap.Controller()



    # def paintEvent(self, event):
    #     self.painterIncetance = QPainter(self)
    #     self.penCircle = QPen(Qt.red)
    #     self.penCircle.setWidth(3)
    #     self.painterIncetance.setPen(self.penCircle)
    #     self.painterIncetance.setBrush(Qt.green)
    #     self.painterIncetance.drawRect(20, 20, 100, 100)

    def createActions(self):
        self.cell_leapAction = QAction("Leap", self)
        self.cell_leapAction.triggered.connect(self.actionLeap)

        self.cell_sumAction = QAction("Sum", self)
        self.cell_sumAction.triggered.connect(self.actionSum)

        self.cell_addAction = QAction("&Add", self)
        self.cell_addAction.setShortcut(Qt.CTRL | Qt.Key_Plus)
        self.cell_addAction.triggered.connect(self.actionAdd)

        self.cell_subAction = QAction("&Subtract", self)
        self.cell_subAction.setShortcut(Qt.CTRL | Qt.Key_Minus)
        self.cell_subAction.triggered.connect(self.actionSubtract)

        self.cell_mulAction = QAction("&Multiply", self)
        self.cell_mulAction.setShortcut(Qt.CTRL | Qt.Key_multiply)
        self.cell_mulAction.triggered.connect(self.actionMultiply)

        self.cell_divAction = QAction("&Divide", self)
        self.cell_divAction.setShortcut(Qt.CTRL | Qt.Key_division)
        self.cell_divAction.triggered.connect(self.actionDivide)

        self.fontAction = QAction("Font...", self)
        self.fontAction.setShortcut(Qt.CTRL | Qt.Key_F)
        self.fontAction.triggered.connect(self.selectFont)

        self.colorAction = QAction(QIcon(QPixmap(16, 16)), "Background &Color...", self)
        self.colorAction.triggered.connect(self.selectColor)

        self.clearAction = QAction("Clear", self)
        self.clearAction.setShortcut(Qt.Key_Delete)
        self.clearAction.triggered.connect(self.clear)

        self.aboutSpreadSheet = QAction("About Spreadsheet", self)
        self.aboutSpreadSheet.triggered.connect(self.showAbout)

        self.exitAction = QAction("E&xit", self)
        self.exitAction.setShortcut(QKeySequence.Quit)
        self.exitAction.triggered.connect(QApplication.instance().quit)

        self.printAction = QAction("&Print", self)
        self.printAction.setShortcut(QKeySequence.Print)
        self.printAction.triggered.connect(self.print_)

        self.firstSeparator = QAction(self)
        self.firstSeparator.setSeparator(True)

        self.secondSeparator = QAction(self)
        self.secondSeparator.setSeparator(True)

    def setupMenuBar(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.dateFormatMenu = self.fileMenu.addMenu("&Date format")
        self.dateFormatGroup = QActionGroup(self)
        for f in self.dateFormats:
            action = QAction(f, self, checkable=True,
                    triggered=self.changeDateFormat)
            self.dateFormatGroup.addAction(action)
            self.dateFormatMenu.addAction(action)
            if f == self.currentDateFormat:
                action.setChecked(True)
                
        self.fileMenu.addAction(self.printAction)
        self.fileMenu.addAction(self.exitAction)
        self.cellMenu = self.menuBar().addMenu("&Cell")
        self.cellMenu.addAction(self.cell_addAction)
        self.cellMenu.addAction(self.cell_subAction)
        self.cellMenu.addAction(self.cell_mulAction)
        self.cellMenu.addAction(self.cell_divAction)
        self.cellMenu.addAction(self.cell_sumAction)
        self.cellMenu.addAction(self.cell_leapAction)
        self.cellMenu.addSeparator()
        self.cellMenu.addAction(self.colorAction)
        self.cellMenu.addAction(self.fontAction)
        self.menuBar().addSeparator()
        self.aboutMenu = self.menuBar().addMenu("&Help")
        self.aboutMenu.addAction(self.aboutSpreadSheet)

    def changeDateFormat(self):
        action = self.sender()
        oldFormat = self.currentDateFormat
        newFormat = self.currentDateFormat = action.text()
        for row in range(self.table.rowCount()):
            item = self.table.item(row, 1)
            date = QDate.fromString(item.text(), oldFormat)
            item.setText(date.toString(newFormat))

    def updateStatus(self, item):
        if item and item == self.table.currentItem():
            self.statusBar().showMessage(item.data(Qt.StatusTipRole), 1000)
            self.cellLabel.setText("Cell: (%s)" % encode_pos(self.table.row(item),
                                                                     self.table.column(item)))

    def updateColor(self, item):
        pixmap = QPixmap(16, 16)
        color = QColor()
        # if item:
        #     color = item.backgroundColor()
        if not color.isValid():
            color = self.palette().base().color()
        painter = QPainter(pixmap)
        painter.fillRect(0, 0, 16, 16, color)
        lighter = color.lighter()
        painter.setPen(lighter)
        # light frame
        painter.drawPolyline(QPoint(0, 15), QPoint(0, 0), QPoint(15, 0))
        painter.setPen(color.darker())
        # dark frame
        painter.drawPolyline(QPoint(1, 15), QPoint(15, 15), QPoint(15, 1))
        painter.end()
        self.colorAction.setIcon(QIcon(pixmap))

    def updateLineEdit(self, item):
        if item != self.table.currentItem():
            return
        if item:
            self.formulaInput.setText(item.data(Qt.EditRole))
        else:
            self.formulaInput.clear()

    def returnPressed(self):
        text = self.formulaInput.text()
        row = self.table.currentRow()
        col = self.table.currentColumn()
        item = self.table.item(row, col)
        if not item:
            self.table.setItem(row, col, SpreadSheetItem(text))
        else:
            item.setData(Qt.EditRole, text)
        self.table.viewport().update()

    def selectColor(self):
        item = self.table.currentItem()
        color = item and QColor(item.background()) or self.table.palette().base().color()
        color = QColorDialog.getColor(color, self)
        if not color.isValid():
            return
        selected = self.table.selectedItems()
        if not selected:
            return
        for i in selected:
            i and i.setBackground(color)
        self.updateColor(self.table.currentItem())

    def selectFont(self):
        selected = self.table.selectedItems()
        if not selected:
            return
        font, ok = QFontDialog.getFont(self.font(), self)
        if not ok:
            return
        for i in selected:
            i and i.setFont(font)

    def runInputDialog(self, title, c1Text, c2Text, opText,
                       outText, cell1, cell2, outCell):
        rows = []
        cols = []
        for r in range(self.table.rowCount()):
            rows.append(str(r + 1))
        for c in range(self.table.columnCount()):
            cols.append(chr(ord('A') + c))
        addDialog = QDialog(self)
        addDialog.setWindowTitle(title)
        group = QGroupBox(title, addDialog)
        group.setMinimumSize(250, 100)
        cell1Label = QLabel(c1Text, group)
        cell1RowInput = QComboBox(group)
        c1Row, c1Col = decode_pos(cell1)
        cell1RowInput.addItems(rows)
        cell1RowInput.setCurrentIndex(c1Row)
        cell1ColInput = QComboBox(group)
        cell1ColInput.addItems(cols)
        cell1ColInput.setCurrentIndex(c1Col)
        operatorLabel = QLabel(opText, group)
        operatorLabel.setAlignment(Qt.AlignHCenter)
        cell2Label = QLabel(c2Text, group)
        cell2RowInput = QComboBox(group)
        c2Row, c2Col = decode_pos(cell2)
        cell2RowInput.addItems(rows)
        cell2RowInput.setCurrentIndex(c2Row)
        cell2ColInput = QComboBox(group)
        cell2ColInput.addItems(cols)
        cell2ColInput.setCurrentIndex(c2Col)
        equalsLabel = QLabel("=", group)
        equalsLabel.setAlignment(Qt.AlignHCenter)
        outLabel = QLabel(outText, group)
        outRowInput = QComboBox(group)
        outRow, outCol = decode_pos(outCell)
        outRowInput.addItems(rows)
        outRowInput.setCurrentIndex(outRow)
        outColInput = QComboBox(group)
        outColInput.addItems(cols)
        outColInput.setCurrentIndex(outCol)

        cancelButton = QPushButton("Cancel", addDialog)
        cancelButton.clicked.connect(addDialog.reject)
        okButton = QPushButton("OK", addDialog)
        okButton.setDefault(True)
        okButton.clicked.connect(addDialog.accept)
        buttonsLayout = QHBoxLayout()
        buttonsLayout.addStretch(1)
        buttonsLayout.addWidget(okButton)
        buttonsLayout.addSpacing(10)
        buttonsLayout.addWidget(cancelButton)

        dialogLayout = QVBoxLayout(addDialog)
        dialogLayout.addWidget(group)
        dialogLayout.addStretch(1)
        dialogLayout.addItem(buttonsLayout)

        cell1Layout = QHBoxLayout()
        cell1Layout.addWidget(cell1Label)
        cell1Layout.addSpacing(10)
        cell1Layout.addWidget(cell1ColInput)
        cell1Layout.addSpacing(10)
        cell1Layout.addWidget(cell1RowInput)

        cell2Layout = QHBoxLayout()
        cell2Layout.addWidget(cell2Label)
        cell2Layout.addSpacing(10)
        cell2Layout.addWidget(cell2ColInput)
        cell2Layout.addSpacing(10)
        cell2Layout.addWidget(cell2RowInput)
        outLayout = QHBoxLayout()
        outLayout.addWidget(outLabel)
        outLayout.addSpacing(10)
        outLayout.addWidget(outColInput)
        outLayout.addSpacing(10)
        outLayout.addWidget(outRowInput)
        vLayout = QVBoxLayout(group)
        vLayout.addItem(cell1Layout)
        vLayout.addWidget(operatorLabel)
        vLayout.addItem(cell2Layout)
        vLayout.addWidget(equalsLabel)
        vLayout.addStretch(1)
        vLayout.addItem(outLayout)
        if addDialog.exec_():
            cell1 = cell1ColInput.currentText() + cell1RowInput.currentText()
            cell2 = cell2ColInput.currentText() + cell2RowInput.currentText()
            outCell = outColInput.currentText() + outRowInput.currentText()
            return True, cell1, cell2, outCell

        return False, None, None, None

    def actionSum(self):
        row_first = 0
        row_last = 0
        row_cur = 0
        col_first = 0
        col_last = 0
        col_cur = 0
        selected = self.table.selectedItems()
        if selected:
            first = selected[0]
            last = selected[-1]
            row_first = self.table.row(first)
            row_last = self.table.row(last)
            col_first = self.table.column(first)
            col_last = self.table.column(last)

        current = self.table.currentItem()
        if current:
            row_cur = self.table.row(current)
            col_cur = self.table.column(current)

        cell1 = encode_pos(row_first, col_first)
        cell2 = encode_pos(row_last, col_last)
        out = encode_pos(row_cur, col_cur)
        ok, cell1, cell2, out = self.runInputDialog("Sum cells", "First cell:",
                "Last cell:", u"\N{GREEK CAPITAL LETTER SIGMA}", "Output to:",
                cell1, cell2, out)
        if ok:
            row, col = decode_pos(out)
            self.table.item(row, col).setText("sum %s %s" % (cell1, cell2))

    def actionMath_helper(self, title, op):
        cell1 = "C1"
        cell2 = "C2"
        out = "C3"
        current = self.table.currentItem()
        if current:
            out = encode_pos(self.table.currentRow(), self.table.currentColumn())
        ok, cell1, cell2, out = self.runInputDialog(title, "Cell 1", "Cell 2",
                op, "Output to:", cell1, cell2, out)
        if ok:
            row, col = decode_pos(out)
            self.table.item(row, col).setText("%s %s %s" % (op, cell1, cell2))

    def actionAdd(self):
        self.actionMath_helper("Addition", "+")

    def actionSubtract(self):
        self.actionMath_helper("Subtraction", "-")

    def actionMultiply(self):
        self.actionMath_helper("Multiplication", "*")

    def actionDivide(self):
        self.actionMath_helper("Division", "/")

    def clear(self):
        for i in self.table.selectedItems():
            i.setText("")

    def actionLeap(self):
        # Have the sample listener receive events from the controller
        self.controller.add_listener(self.listener)


    def setupContextMenu(self):
        self.addAction(self.cell_leapAction)
        self.addAction(self.cell_addAction)
        self.addAction(self.cell_subAction)
        self.addAction(self.cell_mulAction)
        self.addAction(self.cell_divAction)
        self.addAction(self.cell_sumAction)
        self.addAction(self.firstSeparator)
        self.addAction(self.colorAction)
        self.addAction(self.fontAction)
        self.addAction(self.secondSeparator)
        self.addAction(self.clearAction)
        self.setContextMenuPolicy(Qt.ActionsContextMenu)

    def setupContents(self):
        titleBackground = QColor(Qt.lightGray)
        titleFont = self.table.font()
        titleFont.setBold(True)
        # column 0
        self.table.setItem(0, 0, SpreadSheetItem("Item"))
        self.table.item(0, 0).setBackground(titleBackground)
        self.table.item(0, 0).setToolTip("This column shows the purchased item/service")
        self.table.item(0, 0).setFont(titleFont)
        self.table.setItem(1, 0, SpreadSheetItem("AirportBus"))
        self.table.setItem(2, 0, SpreadSheetItem("Flight (Munich)"))
        self.table.setItem(3, 0, SpreadSheetItem("Lunch"))
        self.table.setItem(4, 0, SpreadSheetItem("Flight (LA)"))
        self.table.setItem(5, 0, SpreadSheetItem("Taxi"))
        self.table.setItem(6, 0, SpreadSheetItem("Dinner"))
        self.table.setItem(7, 0, SpreadSheetItem("Hotel"))
        self.table.setItem(8, 0, SpreadSheetItem("Flight (Oslo)"))
        self.table.setItem(9, 0, SpreadSheetItem("Total:"))
        self.table.item(9, 0).setFont(titleFont)
        self.table.item(9, 0).setBackground(Qt.lightGray)
        # column 1
        self.table.setItem(0, 1, SpreadSheetItem("Date"))
        self.table.item(0, 1).setBackground(titleBackground)
        self.table.item(0, 1).setToolTip("This column shows the purchase date, double click to change")
        self.table.item(0, 1).setFont(titleFont)
        self.table.setItem(1, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(2, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(3, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(4, 1, SpreadSheetItem("21/5/2006"))
        self.table.setItem(5, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(6, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(7, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(8, 1, SpreadSheetItem("18/6/2006"))
        self.table.setItem(9, 1, SpreadSheetItem())
        self.table.item(9, 1).setBackground(Qt.lightGray)
        # column 2
        self.table.setItem(0, 2, SpreadSheetItem("Price"))
        self.table.item(0, 2).setBackground(titleBackground)
        self.table.item(0, 2).setToolTip("This column shows the price of the purchase")
        self.table.item(0, 2).setFont(titleFont)
        self.table.setItem(1, 2, SpreadSheetItem("150"))
        self.table.setItem(2, 2, SpreadSheetItem("2350"))
        self.table.setItem(3, 2, SpreadSheetItem("-14"))
        self.table.setItem(4, 2, SpreadSheetItem("980"))
        self.table.setItem(5, 2, SpreadSheetItem("5"))
        self.table.setItem(6, 2, SpreadSheetItem("120"))
        self.table.setItem(7, 2, SpreadSheetItem("300"))
        self.table.setItem(8, 2, SpreadSheetItem("1240"))
        self.table.setItem(9, 2, SpreadSheetItem())
        self.table.item(9, 2).setBackground(Qt.lightGray)
        # column 3
        self.table.setItem(0, 3, SpreadSheetItem("Currency"))
        self.table.item(0, 3).setBackground(titleBackground)
        self.table.item(0, 3).setToolTip("This column shows the currency")
        self.table.item(0, 3).setFont(titleFont)
        self.table.setItem(1, 3, SpreadSheetItem("NOK"))
        self.table.setItem(2, 3, SpreadSheetItem("NOK"))
        self.table.setItem(3, 3, SpreadSheetItem("EUR"))
        self.table.setItem(4, 3, SpreadSheetItem("EUR"))
        self.table.setItem(5, 3, SpreadSheetItem("USD"))
        self.table.setItem(6, 3, SpreadSheetItem("USD"))
        self.table.setItem(7, 3, SpreadSheetItem("USD"))
        self.table.setItem(8, 3, SpreadSheetItem("USD"))
        self.table.setItem(9, 3, SpreadSheetItem())
        self.table.item(9,3).setBackground(Qt.lightGray)
        # column 4
        self.table.setItem(0, 4, SpreadSheetItem("Ex. Rate"))
        self.table.item(0, 4).setBackground(titleBackground)
        self.table.item(0, 4).setToolTip("This column shows the exchange rate to NOK")
        self.table.item(0, 4).setFont(titleFont)
        self.table.setItem(1, 4, SpreadSheetItem("1"))
        self.table.setItem(2, 4, SpreadSheetItem("1"))
        self.table.setItem(3, 4, SpreadSheetItem("8"))
        self.table.setItem(4, 4, SpreadSheetItem("8"))
        self.table.setItem(5, 4, SpreadSheetItem("7"))
        self.table.setItem(6, 4, SpreadSheetItem("7"))
        self.table.setItem(7, 4, SpreadSheetItem("7"))
        self.table.setItem(8, 4, SpreadSheetItem("7"))
        self.table.setItem(9, 4, SpreadSheetItem())
        self.table.item(9,4).setBackground(Qt.lightGray)
        # column 5
        self.table.setItem(0, 5, SpreadSheetItem("NOK"))
        self.table.item(0, 5).setBackground(titleBackground)
        self.table.item(0, 5).setToolTip("This column shows the expenses in NOK")
        self.table.item(0, 5).setFont(titleFont)
        self.table.setItem(1, 5, SpreadSheetItem("* C2 E2"))
        self.table.setItem(2, 5, SpreadSheetItem("* C3 E3"))
        self.table.setItem(3, 5, SpreadSheetItem("* C4 E4"))
        self.table.setItem(4, 5, SpreadSheetItem("* C5 E5"))
        self.table.setItem(5, 5, SpreadSheetItem("* C6 E6"))
        self.table.setItem(6, 5, SpreadSheetItem("* C7 E7"))
        self.table.setItem(7, 5, SpreadSheetItem("* C8 E8"))
        self.table.setItem(8, 5, SpreadSheetItem("* C9 E9"))
        self.table.setItem(9, 5, SpreadSheetItem("sum F2 F9"))
        self.table.item(9,5).setBackground(Qt.lightGray)

    def showAbout(self):
        QMessageBox.about(self, "About Spreadsheet", """
            <HTML>
            <p><b>This demo shows use of <c>QTableWidget</c> with custom handling for
             individual cells.</b></p>
            <p>Using a customized table item we make it possible to have dynamic
             output in different cells. The content that is implemented for this
             particular demo is:
            <ul>
            <li>Adding two cells.</li>
            <li>Subtracting one cell from another.</li>
            <li>Multiplying two cells.</li>
            <li>Dividing one cell with another.</li>
            <li>Summing the contents of an arbitrary number of cells.</li>
            </HTML>
        """)

    def print_(self):
        printer = QPrinter(QPrinter.ScreenResolution)
        dlg = QPrintPreviewDialog(printer)
        view = PrintView()
        view.setModel(self.table.model())
        dlg.paintRequested.connect(view.print_)
        dlg.exec_()
Esempio n. 17
0
class Menu:
    """ Menu class. """

    # List of available backends
    Backends_List = {
        "Backend: JIT": True,
        "Backend: JIT + CUDA": BiotSavart_CUDA.is_available()
    }

    def __init__(self, gui):
        """
        Creates the menu bar.

        @param gui: GUI
        """
        self.gui = gui

        # List of checkboxes that are bound to configuration
        self.config_bound_checkboxes = {}

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        # File menu
        file_menu = QMenu("&File", self.gui)
        file_menu.addAction(qta.icon("fa.folder"), "&Open File …", self.gui.file_open, Qt.CTRL + Qt.Key_O)
        file_menu.addAction(qta.icon("fa.save"), "&Save File", self.gui.file_save, Qt.CTRL + Qt.Key_S)
        file_menu.addAction(
            qta.icon("fa.save"),
            "Save File &As …",
            self.gui.file_save_as,
            Qt.CTRL + Qt.SHIFT + Qt.Key_S
        )
        file_menu.addSeparator()
        file_menu.addAction(qta.icon("fa.picture-o"), "Save &Image …", self.gui.file_save_image, Qt.CTRL + Qt.Key_I)
        file_menu.addSeparator()
        file_menu.addAction(qta.icon("fa.window-close"), "&Quit", self.gui.close, Qt.CTRL + Qt.Key_Q)
        self.gui.menuBar().addMenu(file_menu)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        # Wire menu
        wire_menu = QMenu("&Load Wire Preset", self.gui)
        for preset in Wire_Presets.List:
            action = QAction(preset["id"], wire_menu)
            action.setIcon(qta.icon("mdi.vector-square"))
            action.triggered.connect(
                partial(self.gui.sidebar_left.wire_widget.set_wire, points=preset["points"])
            )
            wire_menu.addAction(action)
        self.gui.menuBar().addMenu(wire_menu)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        # View menu
        view_menu = QMenu("&View", self.gui)
        self.add_config_bound_checkbox("Show Wire Segments", "show_wire_segments", view_menu, self.gui.redraw)
        self.add_config_bound_checkbox("Show Wire Points", "show_wire_points", view_menu, self.gui.redraw)
        view_menu.addSeparator()
        self.add_config_bound_checkbox("Show Colored Labels", "show_colored_labels", view_menu, self.gui.redraw)
        view_menu.addSeparator()
        self.add_config_bound_checkbox("Show Coordinate System", "show_coordinate_system", view_menu, self.gui.redraw)
        self.add_config_bound_checkbox("Show Perspective Info", "show_perspective_info", view_menu, self.gui.redraw)
        view_menu.addSeparator()
        self.add_config_bound_checkbox("Dark Background", "dark_background", view_menu, self.gui.redraw)
        self.gui.menuBar().addMenu(view_menu)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        # Options menu
        options_menu = QMenu("&Options", self.gui)
        self.options_backend_group = QActionGroup(self.gui)
        self.options_backend_group.setExclusive(True)
        self.gui.blockSignals(True)
        self.backend_actions = []
        for i, item in enumerate(self.Backends_List.items()):
            name, enabled = item
            action = QAction(name)
            self.backend_actions.append(action)
            action.setCheckable(True)
            action.setEnabled(enabled)
            action.changed.connect(partial(self.on_backend_changed, i))
            self.options_backend_group.addAction(action)
            options_menu.addAction(action)
        self.gui.blockSignals(False)
        self.gui.menuBar().addMenu(options_menu)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        # Help menu
        help_menu = QMenu("&Help", self.gui)
        help_menu.addAction(qta.icon("fa.info"), "&Usage", lambda: Usage_Dialog().show(), Qt.Key_F1)
        help_menu.addSeparator()
        help_menu.addAction(qta.icon("fa.coffee"), "&About", lambda: About_Dialog().show())
        self.gui.menuBar().addMenu(help_menu)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        self.reinitialize()

    def reinitialize(self):
        """
        Re-initializes the menu.
        """
        Debug(self, ".reinitialize()")

        self.gui.blockSignals(True)

        # Default to JIT backend if CUDA backend is selected but not available
        if self.gui.config.get_int("backend") == 1:
            if not BiotSavart_CUDA.is_available():
                self.gui.config.set_int("backend", 0)

        for i, name in enumerate(self.Backends_List):
            self.backend_actions[i].setChecked(self.gui.config.get_int("backend") == i)

        self.reinitialize_config_bound_checkboxes()

        self.gui.blockSignals(False)

    # ------------------------------------------------------------------------------------------------------------------

    def on_backend_changed(self, index):
        """
        Gets called when the backend changed.

        @param index: Backend list index
        """
        if self.gui.signalsBlocked():
            return

        if self.backend_actions[index].isChecked():
            self.gui.config.set_int("backend", index)
            self.gui.sidebar_right.field_widget.set_field()

    # ------------------------------------------------------------------------------------------------------------------

    def add_config_bound_checkbox(self, label: str, key: str, menu, callback):
        """
        Creates a checkbox inside some menu. Checkbox state is bound to configuration.

        @param label: Checkbox label
        @param key: Configuration key
        @param menu: Menu
        @param callback:
        """
        checkbox = QAction(label, menu)
        checkbox.setCheckable(True)
        checkbox.triggered.connect(partial(self.config_bound_checkbox_changed, key))
        self.config_bound_checkboxes[key] = {"checkbox": checkbox, "callback_final": callback}
        checkbox.setChecked(self.gui.config.get_bool(key))
        menu.addAction(checkbox)

    def config_bound_checkbox_changed(self, key: str):
        """
        Handles change of checkbox state.

        @param key: Configuration key
        """
        self.gui.config.set_bool(key, self.config_bound_checkboxes[key]["checkbox"].isChecked())
        self.config_bound_checkboxes[key]["callback_final"]()

    def reinitialize_config_bound_checkboxes(self):
        """
        Re-initializes the configuration bound checkboxes.

        Note: This doesn't block the change signals.
        """
        for item in self.config_bound_checkboxes.items():
            key, dictionary = item
            dictionary["checkbox"].setChecked(self.gui.config.get_bool(key))
Esempio n. 18
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        widget = QWidget()
        self.setCentralWidget(widget)

        topFiller = QWidget()
        topFiller.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.infoLabel = QLabel(
            "<i>Choose a menu option, or right-click to invoke a context menu</i>",
            alignment=Qt.AlignCenter,
        )
        self.infoLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)

        bottomFiller = QWidget()
        bottomFiller.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        vbox = QVBoxLayout()
        vbox.setContentsMargins(5, 5, 5, 5)
        vbox.addWidget(topFiller)
        vbox.addWidget(self.infoLabel)
        vbox.addWidget(bottomFiller)
        widget.setLayout(vbox)

        self.createActions()
        self.createMenus()

        message = "A context menu is available by right-clicking"
        self.statusBar().showMessage(message)

        self.setWindowTitle("Menus")
        self.setMinimumSize(160, 160)
        self.resize(480, 320)

    def contextMenuEvent(self, event):
        menu = QMenu(self)
        menu.addAction(self.cutAct)
        menu.addAction(self.copyAct)
        menu.addAction(self.pasteAct)
        menu.exec_(event.globalPos())

    def newFile(self):
        self.infoLabel.setText("Invoked <b>File|New</b>")

    def open(self):
        self.infoLabel.setText("Invoked <b>File|Open</b>")

    def save(self):
        self.infoLabel.setText("Invoked <b>File|Save</b>")

    def print_(self):
        self.infoLabel.setText("Invoked <b>File|Print</b>")

    def undo(self):
        self.infoLabel.setText("Invoked <b>Edit|Undo</b>")

    def redo(self):
        self.infoLabel.setText("Invoked <b>Edit|Redo</b>")

    def cut(self):
        self.infoLabel.setText("Invoked <b>Edit|Cut</b>")

    def copy(self):
        self.infoLabel.setText("Invoked <b>Edit|Copy</b>")

    def paste(self):
        self.infoLabel.setText("Invoked <b>Edit|Paste</b>")

    def bold(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Bold</b>")

    def italic(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Italic</b>")

    def leftAlign(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Left Align</b>")

    def rightAlign(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Right Align</b>")

    def justify(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Justify</b>")

    def center(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Center</b>")

    def setLineSpacing(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Set Line Spacing</b>")

    def setParagraphSpacing(self):
        self.infoLabel.setText(
            "Invoked <b>Edit|Format|Set Paragraph Spacing</b>")

    def about(self):
        self.infoLabel.setText("Invoked <b>Help|About</b>")
        QMessageBox.about(
            self,
            "About Menu",
            "The <b>Menu</b> example shows how to create menu-bar menus "
            "and context menus.",
        )

    def aboutQt(self):
        self.infoLabel.setText("Invoked <b>Help|About Qt</b>")

    def createActions(self):
        self.newAct = QAction(
            "&New",
            self,
            shortcut=QKeySequence.New,
            statusTip="Create a new file",
            triggered=self.newFile,
        )

        self.openAct = QAction(
            "&Open...",
            self,
            shortcut=QKeySequence.Open,
            statusTip="Open an existing file",
            triggered=self.open,
        )

        self.saveAct = QAction(
            "&Save",
            self,
            shortcut=QKeySequence.Save,
            statusTip="Save the document to disk",
            triggered=self.save,
        )

        self.printAct = QAction(
            "&Print...",
            self,
            shortcut=QKeySequence.Print,
            statusTip="Print the document",
            triggered=self.print_,
        )

        self.exitAct = QAction(
            "E&xit",
            self,
            shortcut="Ctrl+Q",
            statusTip="Exit the application",
            triggered=self.close,
        )

        self.undoAct = QAction(
            "&Undo",
            self,
            shortcut=QKeySequence.Undo,
            statusTip="Undo the last operation",
            triggered=self.undo,
        )

        self.redoAct = QAction(
            "&Redo",
            self,
            shortcut=QKeySequence.Redo,
            statusTip="Redo the last operation",
            triggered=self.redo,
        )

        self.cutAct = QAction(
            "Cu&t",
            self,
            shortcut=QKeySequence.Cut,
            statusTip="Cut the current selection's contents to the clipboard",
            triggered=self.cut,
        )

        self.copyAct = QAction(
            "&Copy",
            self,
            shortcut=QKeySequence.Copy,
            statusTip="Copy the current selection's contents to the clipboard",
            triggered=self.copy,
        )

        self.pasteAct = QAction(
            "&Paste",
            self,
            shortcut=QKeySequence.Paste,
            statusTip=
            "Paste the clipboard's contents into the current selection",
            triggered=self.paste,
        )

        self.boldAct = QAction(
            "&Bold",
            self,
            checkable=True,
            shortcut="Ctrl+B",
            statusTip="Make the text bold",
            triggered=self.bold,
        )

        boldFont = self.boldAct.font()
        boldFont.setBold(True)
        self.boldAct.setFont(boldFont)

        self.italicAct = QAction(
            "&Italic",
            self,
            checkable=True,
            shortcut="Ctrl+I",
            statusTip="Make the text italic",
            triggered=self.italic,
        )

        italicFont = self.italicAct.font()
        italicFont.setItalic(True)
        self.italicAct.setFont(italicFont)

        self.setLineSpacingAct = QAction(
            "Set &Line Spacing...",
            self,
            statusTip="Change the gap between the lines of a paragraph",
            triggered=self.setLineSpacing,
        )

        self.setParagraphSpacingAct = QAction(
            "Set &Paragraph Spacing...",
            self,
            statusTip="Change the gap between paragraphs",
            triggered=self.setParagraphSpacing,
        )

        self.aboutAct = QAction(
            "&About",
            self,
            statusTip="Show the application's About box",
            triggered=self.about,
        )

        self.aboutQtAct = QAction(
            "About &Qt",
            self,
            statusTip="Show the Qt library's About box",
            triggered=self.aboutQt,
        )
        self.aboutQtAct.triggered.connect(QApplication.instance().aboutQt)

        self.leftAlignAct = QAction(
            "&Left Align",
            self,
            checkable=True,
            shortcut="Ctrl+L",
            statusTip="Left align the selected text",
            triggered=self.leftAlign,
        )

        self.rightAlignAct = QAction(
            "&Right Align",
            self,
            checkable=True,
            shortcut="Ctrl+R",
            statusTip="Right align the selected text",
            triggered=self.rightAlign,
        )

        self.justifyAct = QAction(
            "&Justify",
            self,
            checkable=True,
            shortcut="Ctrl+J",
            statusTip="Justify the selected text",
            triggered=self.justify,
        )

        self.centerAct = QAction(
            "&Center",
            self,
            checkable=True,
            shortcut="Ctrl+C",
            statusTip="Center the selected text",
            triggered=self.center,
        )

        self.alignmentGroup = QActionGroup(self)
        self.alignmentGroup.addAction(self.leftAlignAct)
        self.alignmentGroup.addAction(self.rightAlignAct)
        self.alignmentGroup.addAction(self.justifyAct)
        self.alignmentGroup.addAction(self.centerAct)
        self.leftAlignAct.setChecked(True)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.printAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.undoAct)
        self.editMenu.addAction(self.redoAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)
        self.editMenu.addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

        self.formatMenu = self.editMenu.addMenu("&Format")
        self.formatMenu.addAction(self.boldAct)
        self.formatMenu.addAction(self.italicAct)
        self.formatMenu.addSeparator().setText("Alignment")
        self.formatMenu.addAction(self.leftAlignAct)
        self.formatMenu.addAction(self.rightAlignAct)
        self.formatMenu.addAction(self.justifyAct)
        self.formatMenu.addAction(self.centerAct)
        self.formatMenu.addSeparator()
        self.formatMenu.addAction(self.setLineSpacingAct)
        self.formatMenu.addAction(self.setParagraphSpacingAct)
Esempio n. 19
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
			if not screenRect.contains(self.geometry()):
				self.showMaximized()
		if sys.platform.startswith('darwin'):
			# https://github.com/retext-project/retext/issues/198
			searchPaths = QIcon.themeSearchPaths()
			searchPaths.append('/opt/local/share/icons')
			searchPaths.append('/usr/local/share/icons')
			QIcon.setThemeSearchPaths(searchPaths)
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QIcon.themeName() in ('hicolor', ''):
			if not QFile.exists(getBundledIcon('document-new')):
				QIcon.setThemeName(get_icon_theme())
		if QFile.exists(getBundledIcon('retext')):
			self.setWindowIcon(QIcon(getBundledIcon('retext')))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		self.toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		self.toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh',
			lambda: self.currentTab.readTextFromFile())
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionCloseCurrentTab = self.act(self.tr('Close tab'), 'window-close',
			lambda: self.closeTab(self.ind), shct=QKeySequence.Close)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find',
			self.search, shct=QKeySequence.Find)
		self.actionGoToLine = self.act(self.tr('Go to line'),
			trig=self.goToLine, shct=Qt.CTRL+Qt.Key_G)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(getBundledIcon('document-preview')))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		menuPreview = QMenu()
		menuPreview.addAction(self.actionLivePreview)
		self.actionPreview.setMenu(menuPreview)
		self.actionInsertTable = self.act(self.tr('Insert table'),
			trig=lambda: self.insertFormatting('table'))
		self.actionTableMode = self.act(self.tr('Table editing mode'),
			shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.currentTab.editBox.enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setChecked(self.isFullScreen())
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste)
		self.actionPasteImage = self.act(self.tr('Paste image'), 'edit-paste',
			lambda: self.currentTab.editBox.pasteImage(), shct=Qt.CTRL+Qt.SHIFT+Qt.Key_V)
		self.actionMoveUp = self.act(self.tr('Move line up'), 'go-up',
			lambda: self.currentTab.editBox.moveLineUp(), shct=Qt.ALT+Qt.Key_Up)
		self.actionMoveDown = self.act(self.tr('Move line down'), 'go-down',
			lambda: self.currentTab.editBox.moveLineDown(), shct=Qt.ALT+Qt.Key_Down)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant is not None:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		if ReTextWebKitPreview is None:
			globalSettings.useWebKit = False
			self.actionWebKit.setEnabled(False)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionWebEngine = self.act(self.tr('Use WebEngine (Chromium) renderer'),
			trigbool=self.enableWebEngine)
		if ReTextWebEnginePreview is None:
			globalSettings.useWebEngine = False
		self.actionWebEngine.setChecked(globalSettings.useWebEngine)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionReplace = self.act(self.tr('Replace'), 'edit-find-replace',
			lambda: self.find(replace=True))
		self.actionReplaceAll = self.act(self.tr('Replace all'), trig=self.replaceAll)
		menuReplace = QMenu()
		menuReplace.addAction(self.actionReplaceAll)
		self.actionReplace.setMenu(menuReplace)
		self.actionCloseSearch = self.act(self.tr('Close'), 'window-close',
			lambda: self.searchBar.setVisible(False),
			shct=QKeySequence.Cancel)
		self.actionCloseSearch.setPriority(QAction.LowPriority)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup.name == globalSettings.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertFormatting('bold'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertFormatting('italic'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertFormatting('underline'))
		self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering',
			'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote',
			'table')
		self.usefulChars = ('deg', 'divide', 'euro', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.formattingBox = QComboBox(self.editBar)
		self.formattingBox.addItem(self.tr('Formatting'))
		self.formattingBox.addItems(self.usefulTags)
		self.formattingBox.activated[str].connect(self.insertFormatting)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = self.menuBar()
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addAction(self.actionCloseCurrentTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addAction(self.actionPasteImage)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionMoveUp)
		menuEdit.addAction(self.actionMoveDown)
		menuEdit.addSeparator()
		if enchant is not None:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionGoToLine)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		if ReTextWebKitPreview is not None or ReTextWebEnginePreview is None:
			menuEdit.addAction(self.actionWebKit)
		else:
			menuEdit.addAction(self.actionWebEngine)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionInsertTable)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.toolBar.addAction(self.actionNew)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionOpen)
		self.toolBar.addAction(self.actionSave)
		self.toolBar.addAction(self.actionPrint)
		self.toolBar.addSeparator()
		self.toolBar.addAction(self.actionPreview)
		self.toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.formattingBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.replaceEdit = QLineEdit(self.searchBar)
		self.replaceEdit.setPlaceholderText(self.tr('Replace with'))
		self.replaceEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addWidget(self.replaceEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.addAction(self.actionReplace)
		self.searchBar.addAction(self.actionCloseSearch)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant is not None:
			self.sl = globalSettings.spellCheckLocale
			try:
				enchant.Dict(self.sl or None)
			except enchant.errors.Error as e:
				warnings.warn(str(e), RuntimeWarning)
				globalSettings.spellCheck = False
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def restoreLastOpenedFiles(self):
		for file in readListFromSettings("lastFileList"):
			self.openFileWrapper(file)

		# Show the tab of last opened file
		lastTabIndex = globalSettings.lastTabIndex
		if lastTabIndex >= 0 and lastTabIndex < self.tabWidget.count():
			self.tabWidget.setCurrentIndex(lastTabIndex)

	def iterateTabs(self):
		for i in range(self.tabWidget.count()):
			yield self.tabWidget.widget(i)

	def updateStyleSheet(self):
		self.ss = None
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.setMovable(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent
		self.tabWidget.setTabBarAutoHide(globalSettings.tabBarAutoHide)

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(getBundledIcon(name)))

	def printError(self):
		import traceback
		print('Exception occurred while parsing document:', file=sys.stderr)
		traceback.print_exc()


	def tabFileNameChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		filename of the current tab has changed.
		'''
		if tab == self.currentTab:
			if tab.fileName:
				self.setWindowTitle("")
				if globalSettings.windowTitleFullPath:
					self.setWindowTitle(tab.fileName + '[*]')
				self.setWindowFilePath(tab.fileName)
				self.tabWidget.setTabText(self.ind, tab.getBaseName())
				self.tabWidget.setTabToolTip(self.ind, tab.fileName)
				QDir.setCurrent(QFileInfo(tab.fileName).dir().path())
			else:
				self.setWindowFilePath('')
				self.setWindowTitle(self.tr('New document') + '[*]')

			canReload = bool(tab.fileName) and not self.autoSaveActive(tab)
			self.actionSetEncoding.setEnabled(canReload)
			self.actionReload.setEnabled(canReload)

	def tabActiveMarkupChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		active markup class of the current tab has changed.
		'''
		if tab == self.currentTab:
			markupClass = tab.getActiveMarkupClass()
			dtMarkdown = (markupClass == markups.MarkdownMarkup)
			dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup)
			self.formattingBox.setEnabled(dtMarkdown)
			self.symbolBox.setEnabled(dtMarkdown)
			self.actionUnderline.setEnabled(dtMarkdown)
			self.actionBold.setEnabled(dtMkdOrReST)
			self.actionItalic.setEnabled(dtMkdOrReST)

	def tabModificationStateChanged(self, tab):
		'''
		Perform all UI state changes that need to be done when the
		modification state of the current tab has changed.
		'''
		if tab == self.currentTab:
			changed = tab.editBox.document().isModified()
			if self.autoSaveActive(tab):
				changed = False
			self.actionSave.setEnabled(changed)
			self.setWindowModified(changed)

	def createTab(self, fileName):
		previewStatesByName = {
			'editor': PreviewDisabled,
			'normal-preview': PreviewNormal,
			'live-preview': PreviewLive,
		}
		previewState = previewStatesByName.get(globalSettings.defaultPreviewState, PreviewDisabled)
		if previewState == PreviewNormal and not fileName:
			previewState = PreviewDisabled  # Opening empty document in preview mode makes no sense
		self.currentTab = ReTextTab(self, fileName, previewState)
		self.currentTab.fileNameChanged.connect(lambda: self.tabFileNameChanged(self.currentTab))
		self.currentTab.modificationStateChanged.connect(lambda: self.tabModificationStateChanged(self.currentTab))
		self.currentTab.activeMarkupChanged.connect(lambda: self.tabActiveMarkupChanged(self.currentTab))
		self.tabWidget.addTab(self.currentTab, self.tr("New document"))
		self.currentTab.updateBoxesVisibility()
		if previewState > 0:
			QTimer.singleShot(500, self.currentTab.triggerPreviewUpdate)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.createTab("")
			closedTab = self.tabWidget.widget(ind)
			if closedTab.fileName:
				self.fileSystemWatcher.removePath(closedTab.fileName)
			self.tabWidget.removeTab(ind)
			closedTab.deleteLater()

	def changeIndex(self, ind):
		'''
		This function is called when a different tab is selected.
		It changes the state of the window to mirror the current state
		of the newly selected tab. Future changes to this state will be
		done in response to signals emitted by the tab, to which the
		window was subscribed when the tab was created. The window is
		subscribed to all tabs like this, but only the active tab will
		logically generate these signals.
		Aside from the above this function also calls the handlers for
		the other changes that are implied by a tab switch: filename
		change, modification state change and active markup change.
		'''
		self.currentTab = self.tabWidget.currentWidget()
		editBox = self.currentTab.editBox
		previewState = self.currentTab.previewState
		self.actionUndo.setEnabled(editBox.document().isUndoAvailable())
		self.actionRedo.setEnabled(editBox.document().isRedoAvailable())
		self.actionCopy.setEnabled(editBox.textCursor().hasSelection())
		self.actionCut.setEnabled(editBox.textCursor().hasSelection())
		self.actionPreview.setChecked(previewState >= PreviewLive)
		self.actionLivePreview.setChecked(previewState == PreviewLive)
		self.actionTableMode.setChecked(editBox.tableModeEnabled)
		self.editBar.setEnabled(previewState < PreviewNormal)
		self.ind = ind
		editBox.setFocus(Qt.OtherFocusReason)

		self.tabFileNameChanged(self.currentTab)
		self.tabModificationStateChanged(self.currentTab)
		self.tabActiveMarkupChanged(self.currentTab)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			self.setEditorFont(font)

	def setEditorFont(self, font):
		globalSettings.editorFont = font
		for tab in self.iterateTabs():
			tab.editBox.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			self.setPreviewFont(font)

	def setPreviewFont(self, font):
		globalSettings.font = font
		for tab in self.iterateTabs():
			tab.triggerPreviewUpdate()

	def preview(self, viewmode):
		self.currentTab.previewState = viewmode * 2
		self.actionLivePreview.setChecked(False)
		self.editBar.setDisabled(viewmode)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableLivePreview(self, livemode):
		self.currentTab.previewState = int(livemode)
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.currentTab.updateBoxesVisibility()
		self.currentTab.triggerPreviewUpdate()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		globalSettings.useWebEngine = False
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableWebEngine(self, enable):
		globalSettings.useWebKit = False
		globalSettings.useWebEngine = enable
		for tab in self.iterateTabs():
			tab.rebuildPreviewBox()

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for tab in self.iterateTabs():
				tab.editBox.installFakeVimHandler()
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		try:
			dict = enchant.Dict(self.sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
			self.actionEnableSC.setChecked(False)
			yes = False
		self.setAllDictionaries(dict if yes else None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for tab in self.iterateTabs():
			hl = tab.highlighter
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		localedlg = LocaleDialog(self, defaultText=self.sl)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		try:
			enchant.Dict(sl or None)
		except enchant.errors.Error as e:
			QMessageBox.warning(self, '', str(e))
		else:
			self.sl = sl or None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
			if localedlg.checkBox.isChecked():
				globalSettings.spellCheckLocale = sl

	def search(self):
		self.searchBar.setVisible(True)
		self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def goToLine(self):
		line, ok = QInputDialog.getInt(self, self.tr("Go to line"), self.tr("Type the line number"))
		if ok:
			self.currentTab.goToLine(line-1)

	def searchBarVisibilityChanged(self, visible):
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False, replace=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text() if replace else None
		found = self.currentTab.find(text, flags, replaceText=replaceText)
		self.setSearchEditColor(found)

	def replaceAll(self):
		text = self.searchEdit.text()
		replaceText = self.replaceEdit.text()
		found = self.currentTab.replaceAll(text, replaceText)
		self.setSearchEditColor(found)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def showInDir(self):
		if self.currentTab.fileName:
			path = QFileInfo(self.currentTab.fileName).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def moveToTopOfRecentFileList(self, fileName):
		if fileName:
			files = readListFromSettings("recentFileList")
			if fileName in files:
				files.remove(fileName)
			files.insert(0, fileName)
			recentCount = globalSettings.recentDocumentsCount
			if len(files) > recentCount:
				del files[recentCount:]
			writeListToSettings("recentFileList", files)

	def createNew(self, text=None):
		self.createTab("")
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.currentTab.editBox.textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFunction(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFunction(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.currentTab.getActiveMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype is None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown", "text/markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), QDir.currentPath(),
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	@pyqtSlot(str)
	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.currentTab.fileName or
				self.currentTab.editBox.toPlainText() or
				self.currentTab.editBox.document().isModified()
			)
			if noEmptyTab:
				self.createTab(fileName)
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.currentTab.readTextFromFile(fileName)
			self.moveToTopOfRecentFileList(self.currentTab.fileName)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		codecsSet = set(bytes(QTextCodec.codecForName(alias).name())
		                for alias in QTextCodec.availableCodecs())
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in sorted(codecsSet)],
			0, False)
		if ok:
			self.currentTab.readTextFromFile(None, encoding)

	def saveFileAs(self):
		self.saveFile(dlg=True)

	def saveAll(self):
		for tab in self.iterateTabs():
			if (tab.fileName and tab.editBox.document().isModified()
				and QFileInfo(tab.fileName).isWritable()):
				tab.saveTextToFile()

	def saveFile(self, dlg=False):
		fileNameToSave = self.currentTab.fileName

		if (not fileNameToSave) or dlg:
			proposedFileName = ""
			markupClass = self.currentTab.getActiveMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			if fileNameToSave is not None:
				proposedFileName = fileNameToSave
			fileNameToSave = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), proposedFileName, defaultExt)[0]
			if fileNameToSave:
				if not QFileInfo(fileNameToSave).suffix():
					fileNameToSave += ext
				# Make sure we don't overwrite a file opened in other tab
				for tab in self.iterateTabs():
					if tab is not self.currentTab and tab.fileName == fileNameToSave:
						QMessageBox.warning(self, "",
							self.tr("Cannot save to file which is open in another tab!"))
						return False
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if fileNameToSave:
			if self.currentTab.saveTextToFile(fileNameToSave):
				self.moveToTopOfRecentFileList(self.currentTab.fileName)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		result = htmlFile.open(QIODevice.WriteOnly)
		if not result:
			QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
			return
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self, title, htmltext):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, title)
		td.setHtml(htmltext)
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		title, htmltext, _ = self.currentTab.getDocumentForExport()
		try:
			document = self.textDocument(title, htmltext)
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), self.currentTab.getBaseName() + ".odt",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat(b"odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), self.currentTab.getBaseName() + ".html",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self, title, htmltext, preview):
		if globalSettings.useWebKit:
			return preview
		try:
			return self.textDocument(title, htmltext)
		except Exception:
			self.printError()

	def standardPrinter(self, title):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(title)
		printer.setCreator('ReText %s' % app_version)
		if globalSettings.paperSize:
			pageSize = self.getPageSizeByName(globalSettings.paperSize)
			if pageSize is not None:
				printer.setPaperSize(pageSize)
			else:
				QMessageBox.warning(self, '',
					self.tr('Unrecognized paperSize setting "%s".') %
					globalSettings.paperSize)
		return printer

	def getPageSizeByName(self, pageSizeName):
		""" Returns a validated PageSize instance corresponding to the given
		name. Returns None if the name is not a valid PageSize.
		"""
		pageSize = None

		lowerCaseNames = {pageSize.lower(): pageSize for pageSize in
		                  self.availablePageSizes()}
		if pageSizeName.lower() in lowerCaseNames:
			pageSize = getattr(QPagedPaintDevice, lowerCaseNames[pageSizeName.lower()])

		return pageSize

	def availablePageSizes(self):
		""" List available page sizes. """

		sizes = [x for x in dir(QPagedPaintDevice)
		         if type(getattr(QPagedPaintDevice, x)) == QPagedPaintDevice.PageSize]
		return sizes

	def savePdf(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			self.currentTab.getBaseName() + ".pdf",
			self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			title, htmltext, preview = self.currentTab.getDocumentForExport()
			if globalSettings.useWebEngine and hasattr(preview.page(), "printToPdf"):
				pageSize = self.getPageSizeByName(globalSettings.paperSize)
				if pageSize is None:
					pageSize = QPageSize(QPageSize.A4)
				margins = QMarginsF(20, 20, 13, 20)  # left, top, right, bottom (in millimeters)
				layout = QPageLayout(pageSize, QPageLayout.Portrait, margins, QPageLayout.Millimeter)
				preview.page().printToPdf(fileName, layout)  # Available since Qt 5.7
				return
			printer = self.standardPrinter(title)
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printFile(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		printer = self.standardPrinter(title)
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint(title, htmltext, preview)
			if document != None:
				document.print(printer)

	def printPreview(self):
		title, htmltext, preview = self.currentTab.getDocumentForExport()
		document = self.getDocumentForPrint(title, htmltext, preview)
		if document is None:
			return
		printer = self.standardPrinter(title)
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		import shlex
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		else:
			fileName = 'out' + defaultext
		basename = '.%s.retext-temp' % self.currentTab.getBaseName()
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename + self.currentTab.getActiveMarkupClass().default_extension
			self.currentTab.writeTextToFile(tmpname)
		command = command.replace('%of', shlex.quote(fileName))
		command = command.replace('%html' if html else '%if', shlex.quote(tmpname))
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()

	def autoSaveActive(self, tab=None):
		tab = tab if tab else self.currentTab
		return bool(self.autoSaveEnabled and tab.fileName and
			    QFileInfo(tab.fileName).isWritable())

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())
			self.actionPasteImage.setEnabled(mimeData.hasImage())

	def insertFormatting(self, formatting):
		if formatting == 'table':
			dialog = InsertTableDialog(self)
			dialog.show()
			self.formattingBox.setCurrentIndex(0)
			return

		cursor = self.currentTab.editBox.textCursor()
		text = cursor.selectedText()
		moveCursorTo = None

		def c(cursor):
			nonlocal moveCursorTo
			moveCursorTo = cursor.position()

		def ensurenl(cursor):
			if not cursor.atBlockStart():
				cursor.insertText('\n\n')

		toinsert = {
			'header': (ensurenl, '# ', text),
			'italic': ('*', text, c, '*'),
			'bold': ('**', text, c, '**'),
			'underline': ('<u>', text, c, '</u>'),
			'numbering': (ensurenl, ' 1. ', text),
			'bullets': (ensurenl, '  * ', text),
			'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'),
			'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'),
			'inline code': ('`', text, c, '`'),
			'code block': (ensurenl, '    ', text),
			'blockquote': (ensurenl, '> ', text),
		}

		if formatting not in toinsert:
			return

		cursor.beginEditBlock()
		for token in toinsert[formatting]:
			if callable(token):
				token(cursor)
			else:
				cursor.insertText(token)
		cursor.endEditBlock()

		self.formattingBox.setCurrentIndex(0)
		# Bring back the focus on the editor
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

		if moveCursorTo:
			cursor.setPosition(moveCursorTo)
			self.currentTab.editBox.setTextCursor(cursor)

	def insertSymbol(self, num):
		if num:
			self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		tab = None
		for testtab in self.iterateTabs():
			if testtab.fileName == fileName:
				tab = testtab
		if tab is None:
			self.fileSystemWatcher.removePath(fileName)
			return
		if not QFile.exists(fileName):
			self.tabWidget.setCurrentWidget(tab)
			tab.editBox.document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not tab.editBox.document().isModified():
			# File was not modified in ReText, reload silently
			tab.readTextFromFile()
		else:
			self.tabWidget.setCurrentWidget(tab)
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				tab.readTextFromFile()
			else:
				self.autoSaveEnabled = False
				tab.editBox.document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		tab = self.tabWidget.widget(ind)
		if self.autoSaveActive(tab):
			tab.saveTextToFile()
			return True
		if not tab.editBox.document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFile(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for ind in range(self.tabWidget.count()):
			if not self.maybeSave(ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry:
			globalSettings.windowGeometry = self.saveGeometry()
		if globalSettings.openLastFilesOnStartup:
			files = [tab.fileName for tab in self.iterateTabs()]
			writeListToSettings("lastFileList", files)
			globalSettings.lastTabIndex = self.tabWidget.currentIndex()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			_, htmltext, _ = self.currentTab.getDocumentForExport(includeStyleSheet=False)
		except Exception:
			return self.printError()
		winTitle = self.currentTab.getBaseName()
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2017')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markupClass):
		globalSettings.defaultMarkup = markupClass.name
		for tab in self.iterateTabs():
			if not tab.fileName:
				tab.updateActiveMarkupClass()
Esempio n. 20
0
    def initUI(self):

        self.view = TextView(self)
        self.setCentralWidget(self.view)

        self.toolbar = self.addToolBar('Text Effects')

        # Set up the text effects tools
        actionGroup = QActionGroup(self)

        noneAction = QAction(QIcon("none.png"), "&Clear", self)
        noneAction.setStatusTip("Clear Effects")
        noneAction.triggered.connect(self.view.noEffect)
        actionGroup.addAction(noneAction)

        blurAction = QAction(QIcon("blur.png"), "&Blur", self)
        blurAction.setStatusTip("Blur Text")
        blurAction.triggered.connect(self.view.blur)
        actionGroup.addAction(blurAction)

        opacityAction = QAction(QIcon("opacity.png"), "&Transparency", self)
        opacityAction.setStatusTip("Fade Text")
        opacityAction.triggered.connect(self.view.opacity)
        actionGroup.addAction(opacityAction)

        shadowAction = QAction(QIcon("shadow.png"), "&Drop Shadow", self)
        shadowAction.setStatusTip("Drop-shadow Text")
        shadowAction.triggered.connect(self.view.shadow)
        actionGroup.addAction(shadowAction)

        self.toolbar.addActions(actionGroup.actions())
        self.toolbar.addSeparator()

        # Set up the font selection tools
        boldAction = QAction(QIcon("bold.png"), "&Bold", self)
        boldAction.setStatusTip("Bold Text")
        boldAction.setCheckable(True)
        boldAction.setChecked(True)
        boldAction.triggered[bool].connect(self.view.bold)
        self.toolbar.addAction(boldAction)

        italicAction = QAction(QIcon("italic.png"), "&Italic", self)
        italicAction.setStatusTip("Italic Text")
        italicAction.setCheckable(True)
        italicAction.triggered[bool].connect(self.view.italic)
        self.toolbar.addAction(italicAction)

        self.fontBox = QFontComboBox(self)
        self.fontBox.setCurrentFont(QFont("PT Sans", 16, QFont.Bold))
        self.fontBox.currentFontChanged.connect(self.view.fontFamily)
        self.toolbar.addWidget(self.fontBox)

        self.fontSizeBox = QComboBox(self)
        self.fontSizeBox.setEditable(True)
        strlist = []
        intlist = QFontDatabase.standardSizes()
        for item in intlist:
            strlist.append(str(item))
        self.fontSizeBox.addItems(strlist)
        self.fontSizeBox.setCurrentText("16")
        self.fontSizeBox.currentTextChanged.connect(self.view.fontSize)
        self.toolbar.addWidget(self.fontSizeBox)

        self.setGeometry(300, 300, 600, 500)
        self.setWindowTitle('Renderer')
        self.show()
Esempio n. 21
0
class MainFileMenu(QMenu):
    """Menu with file actions."""
    def __init__(self, parent, layer_editor, title='&File'):
        """Initializes the class."""
        super(MainFileMenu, self).__init__(parent)
        self.setTitle(title)
        self._layer_editor = layer_editor
        self.parent = parent
        self._about_dialog = GMAboutDialog(parent, 'GeoMop LayerEditor')

        self._new_file_action = QAction('&New File ...', self)
        self._new_file_action.setShortcut(
            cfg.get_shortcut('new_file').key_sequence)
        self._new_file_action.setStatusTip('New layer data file')
        self._new_file_action.triggered.connect(self._layer_editor.new_file)
        self.addAction(self._new_file_action)

        self._open_file_action = QAction('&Open File ...', self)
        self._open_file_action.setShortcut(
            cfg.get_shortcut('open_file').key_sequence)
        self._open_file_action.setStatusTip('Open layer data file')
        self._open_file_action.triggered.connect(self._layer_editor.open_file)
        self.addAction(self._open_file_action)

        self._save_file_action = QAction('&Save File', self)
        self._save_file_action.setShortcut(
            cfg.get_shortcut('save_file').key_sequence)
        self._save_file_action.setStatusTip('Save layer data file')
        self._save_file_action.triggered.connect(self._layer_editor.save_file)
        self.addAction(self._save_file_action)

        self._save_as_action = QAction('Save &As ...', self)
        self._save_as_action.setShortcut(
            cfg.get_shortcut('save_file_as').key_sequence)
        self._save_as_action.setStatusTip('Save layer data file as')
        self._save_as_action.triggered.connect(self._layer_editor.save_as)
        self.addAction(self._save_as_action)

        self._recent_file_signal_connect = False
        self._recent = self.addMenu('Open &Recent Files')
        self._recent_group = QActionGroup(self, exclusive=True)

        self.addSeparator()

        self._import_file_action = QAction('&Add Shape File ...', self)
        self._import_file_action.setStatusTip('Add shape file')
        self._import_file_action.triggered.connect(
            self._layer_editor.add_shape_file)
        self.addAction(self._import_file_action)

        self.addSeparator()

        self._about_action = QAction('About', self)
        self._about_action.triggered.connect(self._on_about_action_clicked)
        self.addAction(self._about_action)

        self._help_dialog = LE_help_dialog(parent)
        self._help_action = QAction('Help', self)
        self._help_action.triggered.connect(self._on_help_action_clicked)
        self.addAction(self._help_action)

        self.addSeparator()

        self._exit_action = QAction('E&xit', self)
        self._exit_action.setShortcut(cfg.get_shortcut('exit').key_sequence)
        self._exit_action.setStatusTip('Exit application')
        self._exit_action.triggered.connect(self._exit_clicked)
        self.addAction(self._exit_action)

    def update_recent_files(self, from_row=1):
        """update recent file in menu"""
        if self._recent_file_signal_connect:
            self._recent_group.triggered.disconnect()
            self._recent_file_signal_connect = False
        for action in self._recent_group.actions():
            self._recent_group.removeAction(action)
        if len(cfg.config.recent_files) < from_row + 1:
            self._recent.setEnabled(False)
            return
        self._recent.setEnabled(True)
        for i in range(from_row, len(cfg.config.recent_files)):
            action = QAction(cfg.config.recent_files[i], self, checkable=True)
            action.setData(cfg.config.recent_files[i])
            reaction = self._recent_group.addAction(action)
            self._recent.addAction(reaction)
        self._recent_group.triggered.connect(self._layer_editor.open_recent)
        self._recent_file_signal_connect = True

    def _on_about_action_clicked(self):
        """Displays about dialog."""
        if not self._about_dialog.isVisible():
            self._about_dialog.show()

    def _on_help_action_clicked(self):
        """Displays help dialog."""
        if not self._help_dialog.isVisible():
            self._help_dialog.show()

    def _exit_clicked(self):
        """Performs actions before app is closed."""
        # prompt user to save changes (if any)
        if not self._layer_editor.mainwindow.close():
            return
        qApp.quit()
Esempio n. 22
0
class ThreadlinkUtility(QMainWindow):
    """Main class for the PCBA Test Utility.
    
    Creates main window for the program, the file menu, status bar, and the 
    settings/configuration window.
    """
    def __init__(self):
        super().__init__()
        self.system_font = QApplication.font().family()
        self.label_font = QFont(self.system_font, 12)
        self.config_font = QFont(self.system_font, 12)
        self.config_path_font = QFont(self.system_font, 12)

        self.settings = QSettings("BeadedStream", "Threadlink TestUtility")

        settings_defaults = {
            "user_id": "",
            "port1_tac_id": "",
            "hex_files_path": "/path/to/hex/files",
            "report_dir_path": "/path/to/report/folder",
            "atprogram_file_path": "/path/to/atprogram.exe"
        }

        for key in settings_defaults:
            if not self.settings.value(key):
                self.settings.setValue(key, settings_defaults[key])

        self.sm = serialmanager.SerialManager()
        self.serial_thread = QThread()
        self.sm.moveToThread(self.serial_thread)
        self.serial_thread.start()

        self.m = model.Model()
        self.r = report.Report()

        self.sm.port_unavailable_signal.connect(self.port_unavailable)

        # Part number : [serial prefix, procedure class]
        self.product_data = {
            "45211-01": ["THL", threadlink.Threadlink],
        }
        # Create program actions.
        self.config = QAction("Settings", self)
        self.config.setShortcut("Ctrl+E")
        self.config.setStatusTip("Program Settings")
        self.config.triggered.connect(self.configuration)

        self.quit = QAction("Quit", self)
        self.quit.setShortcut("Ctrl+Q")
        self.quit.setStatusTip("Exit Program")
        self.quit.triggered.connect(self.close)

        self.about_tu = QAction("About Threadlink Utility", self)
        self.about_tu.setShortcut("Ctrl+U")
        self.about_tu.setStatusTip("About Program")
        self.about_tu.triggered.connect(self.about_program)

        self.aboutqt = QAction("About Qt", self)
        self.aboutqt.setShortcut("Ctrl+I")
        self.aboutqt.setStatusTip("About Qt")
        self.aboutqt.triggered.connect(self.about_qt)

        # Create menubar
        self.menubar = self.menuBar()
        self.file_menu = self.menubar.addMenu("&File")
        self.file_menu.addAction(self.config)
        self.file_menu.addAction(self.quit)

        self.serial_menu = self.menubar.addMenu("&Serial")
        self.serial_menu.installEventFilter(self)
        self.ports_menu = QMenu("&Ports", self)
        self.serial_menu.addMenu(self.ports_menu)
        self.ports_menu.aboutToShow.connect(self.populate_ports)
        self.ports_group = QActionGroup(self)
        self.ports_group.triggered.connect(self.connect_port)

        self.help_menu = self.menubar.addMenu("&Help")
        self.help_menu.addAction(self.about_tu)
        self.help_menu.addAction(self.aboutqt)

        self.initUI()
        self.center()

    def center(self):
        """Centers the application on the screen the mouse pointer is
        currently on."""
        frameGm = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        centerPoint = QApplication.desktop().screenGeometry(screen).center()
        frameGm.moveCenter(centerPoint)
        self.move(frameGm.topLeft())

    def resource_path(self, relative_path):
        """Gets the path of the application relative root path to allow us
        to find the logo."""
        if hasattr(sys, '_MEIPASS'):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.abspath("."), relative_path)

    def initUI(self):
        """"Sets up the UI."""
        RIGHT_SPACING = 350
        LINE_EDIT_WIDTH = 200
        self.central_widget = QWidget()

        self.tester_id_lbl = QLabel("Please enter tester ID: ")
        self.tester_id_lbl.setFont(self.label_font)
        self.pcba_pn_lbl = QLabel("Please select PCBA part number: ")
        self.pcba_pn_lbl.setFont(self.label_font)
        self.pcba_sn_lbl = QLabel("Please enter or scan DUT serial number: ")
        self.pcba_sn_lbl.setFont(self.label_font)

        self.tester_id_input = QLineEdit()
        self.tester_id_input.setText(self.settings.value("user_id"))
        self.pcba_sn_input = QLineEdit()
        self.tester_id_input.setFixedWidth(LINE_EDIT_WIDTH)
        self.pcba_sn_input.setFixedWidth(LINE_EDIT_WIDTH)

        self.pcba_pn_input = QComboBox()
        self.pcba_pn_input.addItem("45211-01")
        self.pcba_pn_input.setFixedWidth(LINE_EDIT_WIDTH)

        self.start_btn = QPushButton("Start")
        self.start_btn.setFixedWidth(200)
        self.start_btn.setAutoDefault(True)
        self.start_btn.clicked.connect(self.parse_values)

        self.logo_img = QPixmap(self.resource_path("h_logo.png"))
        self.logo_img = self.logo_img.scaledToWidth(600)
        self.logo = QLabel()
        self.logo.setPixmap(self.logo_img)

        hbox_logo = QHBoxLayout()
        hbox_logo.addStretch()
        hbox_logo.addWidget(self.logo)
        hbox_logo.addStretch()

        hbox_test_id = QHBoxLayout()
        hbox_test_id.addStretch()
        hbox_test_id.addWidget(self.tester_id_lbl)
        hbox_test_id.addWidget(self.tester_id_input)
        hbox_test_id.addSpacing(RIGHT_SPACING)

        hbox_pn = QHBoxLayout()
        hbox_pn.addStretch()
        hbox_pn.addWidget(self.pcba_pn_lbl)
        hbox_pn.addWidget(self.pcba_pn_input)
        hbox_pn.addSpacing(RIGHT_SPACING)

        hbox_sn = QHBoxLayout()
        hbox_sn.addStretch()
        hbox_sn.addWidget(self.pcba_sn_lbl)
        hbox_sn.addWidget(self.pcba_sn_input)
        hbox_sn.addSpacing(RIGHT_SPACING)

        hbox_start_btn = QHBoxLayout()
        hbox_start_btn.addStretch()
        hbox_start_btn.addWidget(self.start_btn)
        hbox_start_btn.addSpacing(RIGHT_SPACING)

        vbox = QVBoxLayout()
        vbox.addStretch()
        vbox.addLayout(hbox_logo)
        vbox.addSpacing(100)
        vbox.addLayout(hbox_test_id)
        vbox.addSpacing(50)
        vbox.addLayout(hbox_pn)
        vbox.addSpacing(50)
        vbox.addLayout(hbox_sn)
        vbox.addSpacing(50)
        vbox.addLayout(hbox_start_btn)
        vbox.addStretch()

        self.central_widget.setLayout(vbox)
        self.setCentralWidget(self.central_widget)
        self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.setWindowTitle(
            "BeadedStream Manufacturing Threadlink Test Utility")

    def create_messagebox(self, type, title, text, info_text):
        """A helper method for creating message boxes."""
        msgbox = QMessageBox(self)
        msgbox.setWindowTitle(title)
        msgbox.setText(text)
        msgbox.setInformativeText(info_text)
        if type == "Warning":
            msgbox.setIcon(QMessageBox.Warning)
        elif type == "Error":
            msgbox.setIcon(QMessageBox.Error)
        elif type == "Information":
            msgbox.setIcon(QMessageBox.Information)
        else:
            raise InvalidMsgType
        return msgbox

    def about_program(self):
        """Displays information about the program."""
        QMessageBox.about(self, "About Threadlink Test Utility", ABOUT_TEXT)

    def about_qt(self):
        """Displays information about Qt."""
        QMessageBox.aboutQt(self, "About Qt")

    def populate_ports(self):
        """Populates ports menu from connected COM ports."""
        ports = serialmanager.SerialManager.scan_ports()
        self.ports_menu.clear()

        if not ports:
            self.ports_menu.addAction("None")
            self.sm.close_port()

        for port in ports:
            port_description = port.description
            action = self.ports_menu.addAction(port_description)
            port_name = port.device
            if self.sm.is_connected(port_name):
                action.setCheckable(True)
                action.setChecked(True)
            self.ports_group.addAction(action)

    def connect_port(self, action: QAction):
        """Connects to a COM port by parsing the text from a clicked QAction
        menu object."""
        p = "COM[0-9]+"
        m = re.search(p, action.text())
        if m:
            port_name = m.group()
            self.sm.open_port(port_name)
        else:
            QMessageBox.warning(self, "Warning", "Invalid port selection!")

    def port_unavailable(self):
        """Displays warning message about unavailable port."""
        QMessageBox.warning(self, "Warning", "Port unavailable!")

    def parse_values(self):
        """Parses and validates input values from the start page."""
        self.tester_id = self.tester_id_input.text().upper()
        self.settings.setValue("user_id", self.tester_id)
        self.pcba_pn = self.pcba_pn_input.currentText()
        self.pcba_sn = self.pcba_sn_input.text().upper()

        if (self.tester_id and self.pcba_pn and self.pcba_sn):

            # The serial number should be seven characters long and start with
            # the specific prefix for the given product.
            if (self.pcba_sn[0:3] == self.product_data[self.pcba_pn][0]
                    and len(self.pcba_sn) == 7):
                self.r.write_data("tester_id", self.tester_id, "PASS")
                self.r.write_data("pcba_sn", self.pcba_sn, "PASS")
                self.r.write_data("pcba_pn", self.pcba_pn, "PASS")
            else:
                self.err_msg = self.create_messagebox("Warning", "Error",
                                                      "Error",
                                                      "Bad serial number!")
                self.err_msg.show()
                return
        else:
            self.err_msg = self.create_messagebox("Warning", "Error", "Error",
                                                  "Missing value!")
            self.err_msg.show()
            return

        self.start_procedure()

    def start_procedure(self):
        """Sets up procedure layout by creating test statuses and initializing
        the appropriate board class (currently D505, potentially others in 
        the future)."""
        central_widget = QWidget()

        status_lbl_stylesheet = ("QLabel {border: 2px solid grey;"
                                 "color: black; font-size: 20px}")
        status_style_pass = """QLabel {background: #8cff66;
                                border: 2px solid grey; font-size: 20px}"""

        # ______Labels______
        self.tester_id_status = QLabel(f"Tester ID: {self.tester_id}")
        self.pcba_pn_status = QLabel(f"PCBA PN: {self.pcba_pn}")
        self.pcba_sn_status = QLabel(f"PCBA SN: {self.pcba_sn}")
        self.input_i_status = QLabel(f"Input Current: _____ mA")
        self.supply_5v_status = QLabel("5V Supply: _____V")
        self.output_2p5v_status = QLabel("2.5V Output: _____V")
        self.supply_1p8v_status = QLabel("1.8V Supply: _____V")
        self.xmega_prog_status = QLabel("XMega Programming: _____")
        self.one_wire_prog_status = QLabel("1-Wire Programming:_____")
        self.internal_5v_status = QLabel("Internal 5V: _____V")
        self.tac_id_status = QLabel("TAC ID: _____")
        self.hall_effect_status = QLabel("Hall Effect Sensor Test:_____")
        self.led_test_status = QLabel("LED Test:_____")

        self.tester_id_status.setStyleSheet(status_style_pass)
        self.pcba_pn_status.setStyleSheet(status_style_pass)
        self.pcba_sn_status.setStyleSheet(status_style_pass)
        self.input_i_status.setStyleSheet(status_lbl_stylesheet)
        self.supply_5v_status.setStyleSheet(status_lbl_stylesheet)
        self.output_2p5v_status.setStyleSheet(status_lbl_stylesheet)
        self.supply_1p8v_status.setStyleSheet(status_lbl_stylesheet)
        self.xmega_prog_status.setStyleSheet(status_lbl_stylesheet)
        self.one_wire_prog_status.setStyleSheet(status_lbl_stylesheet)
        self.internal_5v_status.setStyleSheet(status_lbl_stylesheet)
        self.tac_id_status.setStyleSheet(status_lbl_stylesheet)
        self.hall_effect_status.setStyleSheet(status_lbl_stylesheet)
        self.led_test_status.setStyleSheet(status_lbl_stylesheet)

        # ______Layout______
        status_vbox1 = QVBoxLayout()
        status_vbox1.setSpacing(10)
        status_vbox1.addWidget(self.tester_id_status)
        status_vbox1.addWidget(self.pcba_pn_status)
        status_vbox1.addWidget(self.pcba_sn_status)
        status_vbox1.addWidget(self.input_i_status)
        status_vbox1.addWidget(self.supply_5v_status)
        status_vbox1.addWidget(self.output_2p5v_status)
        status_vbox1.addWidget(self.supply_1p8v_status)
        status_vbox1.addWidget(self.xmega_prog_status)
        status_vbox1.addWidget(self.one_wire_prog_status)
        status_vbox1.addWidget(self.internal_5v_status)
        status_vbox1.addWidget(self.tac_id_status)
        status_vbox1.addWidget(self.hall_effect_status)
        status_vbox1.addWidget(self.led_test_status)
        status_vbox1.addStretch()

        status_group = QGroupBox("Test Statuses")
        status_group.setFont(self.label_font)
        status_group.setLayout(status_vbox1)

        # Use the product data dictionary to call the procdure class that
        # corresponds to the part number. Create an instance of it passing it
        # the instances of test_utility, model, serial_manager and report.
        self.procedure = self.product_data[self.pcba_pn][1](self, self.m,
                                                            self.sm, self.r)

        grid = QGridLayout()
        grid.setColumnStretch(0, 5)
        grid.setColumnStretch(1, 15)
        grid.addWidget(status_group, 0, 0, Qt.AlignTop)
        grid.addWidget(self.procedure, 0, 1)

        # layout = QHBoxLayout()
        # layout.addWidget(self.procedure)

        central_widget.setLayout(grid)

        self.setCentralWidget(central_widget)

    def configuration(self):
        """Sets up configuration/settings window elements."""
        FILE_BTN_WIDTH = 30

        self.settings_widget = QDialog(self)

        port1_lbl = QLabel("Port 1 TAC ID:")
        port1_lbl.setFont(self.config_font)
        self.port1_tac_id = QLineEdit(self.settings.value("port1_tac_id"))

        port_layout = QGridLayout()
        port_layout.addWidget(port1_lbl, 0, 0)
        port_layout.addWidget(self.port1_tac_id, 0, 1)

        port_group = QGroupBox("TAC IDs")
        port_group.setLayout(port_layout)

        self.hex_btn = QPushButton("[...]")
        self.hex_btn.setFixedWidth(FILE_BTN_WIDTH)
        self.hex_btn.clicked.connect(self.set_hex_dir)
        self.hex_lbl = QLabel("Choose the location of hex files: ")
        self.hex_lbl.setFont(self.config_font)
        self.hex_path_lbl = QLabel(self.settings.value("hex_files_path"))
        self.hex_path_lbl.setFont(self.config_path_font)
        self.hex_path_lbl.setStyleSheet("QLabel {color: blue}")

        self.report_btn = QPushButton("[...]")
        self.report_btn.setFixedWidth(FILE_BTN_WIDTH)
        self.report_btn.clicked.connect(self.set_report_location)
        self.report_lbl = QLabel("Set report save location: ")
        self.report_lbl.setFont(self.config_font)
        self.report_path_lbl = QLabel(self.settings.value("report_dir_path"))
        self.report_path_lbl.setFont(self.config_path_font)
        self.report_path_lbl.setStyleSheet("QLabel {color: blue}")

        self.atprogram_btn = QPushButton("[...]")
        self.atprogram_btn.setFixedWidth(FILE_BTN_WIDTH)
        self.atprogram_btn.clicked.connect(self.choose_atprogram_file)
        self.atprogram_lbl = QLabel("Select atprogram.exe.")
        self.atprogram_lbl.setFont(self.config_font)
        self.atprogram_path_lbl = QLabel(
            self.settings.value("atprogram_file_path"))
        self.atprogram_path_lbl.setFont(self.config_path_font)
        self.atprogram_path_lbl.setStyleSheet("QLabel {color: blue}")

        save_loc_layout = QGridLayout()
        save_loc_layout.addWidget(self.hex_lbl, 0, 0)
        save_loc_layout.addWidget(self.hex_btn, 0, 1)
        save_loc_layout.addWidget(self.hex_path_lbl, 1, 0)
        save_loc_layout.addWidget(self.report_lbl, 2, 0)
        save_loc_layout.addWidget(self.report_btn, 2, 1)
        save_loc_layout.addWidget(self.report_path_lbl, 3, 0)
        save_loc_layout.addWidget(self.atprogram_lbl, 4, 0)
        save_loc_layout.addWidget(self.atprogram_btn, 4, 1)
        save_loc_layout.addWidget(self.atprogram_path_lbl, 5, 0)

        save_loc_group = QGroupBox("Save Locations")
        save_loc_group.setLayout(save_loc_layout)

        apply_btn = QPushButton("Apply Settings")
        apply_btn.clicked.connect(self.apply_settings)
        cancel_btn = QPushButton("Cancel")
        cancel_btn.clicked.connect(self.cancel_settings)

        button_layout = QHBoxLayout()
        button_layout.addWidget(cancel_btn)
        button_layout.addStretch()
        button_layout.addWidget(apply_btn)

        hbox_top = QHBoxLayout()
        hbox_top.addWidget(port_group)

        hbox_bottom = QHBoxLayout()
        # hbox_bottom.addStretch()
        hbox_bottom.addWidget(save_loc_group)
        # hbox_bottom.addStretch()

        grid = QGridLayout()
        grid.addLayout(hbox_top, 0, 0)
        grid.addLayout(hbox_bottom, 1, 0)
        grid.addLayout(button_layout, 2, 0)
        grid.setHorizontalSpacing(100)

        self.settings_widget.setLayout(grid)
        self.settings_widget.setWindowTitle(
            "Threadlink Configuration Settings")
        self.settings_widget.show()
        # self.settings_widget.resize(800, 600)

        # frameGm = self.frameGeometry()
        # screen = QApplication.desktop().screenNumber(
        #     QApplication.desktop().cursor().pos())
        # centerPoint = QApplication.desktop().screenGeometry(screen).center()
        # frameGm.moveCenter(centerPoint)
        # self.settings_widget.move(frameGm.topLeft())

    def set_hex_dir(self):
        """Opens file dialog for selecting the hex files directory."""

        hex_files_path = QFileDialog.getExistingDirectory(
            self, "Select hex files directory.")
        self.hex_path_lbl.setText(hex_files_path)

    def set_report_location(self):
        """Opens file dialog for setting the save location for the report."""

        report_dir = QFileDialog.getExistingDirectory(
            self, "Select report save location.")
        self.report_path_lbl.setText(report_dir)

    def choose_atprogram_file(self):
        """Opens file dialog for selecting the atprogram executable."""

        atprogram_file_path = QFileDialog.getOpenFileName(
            self, "Select atprogram.exe.", "", "Application (*.exe)")[0]
        self.atprogram_path_lbl.setText(atprogram_file_path)

    def cancel_settings(self):
        """Close the settings widget without applying changes."""

        self.settings_widget.close()

    def apply_settings(self):
        """Read user inputs and apply settings."""

        p = r"([a-fA-F0-9]){8}"

        port1_value = self.port1_tac_id.text()

        if re.fullmatch(p, port1_value):
            self.settings.setValue("port1_tac_id", port1_value)

        else:
            QMessageBox.warning(
                self.settings_widget, "Warning!", f"Bad TAC ID!\n"
                "IDs are 8 digit hex values.\n"
                "E.g.: 000a5296")
            return

        self.settings.setValue("hex_files_path", self.hex_path_lbl.text())
        self.settings.setValue("report_dir_path", self.report_path_lbl.text())
        self.settings.setValue("atprogram_file_path",
                               self.atprogram_path_lbl.text())

        QMessageBox.information(self.settings_widget, "Information",
                                "Settings applied!")

        self.settings_widget.close()

    def closeEvent(self, event):
        """Override QWidget closeEvent to provide user with confirmation
        dialog and ensure threads are terminated appropriately."""

        event.accept()

        quit_msg = "Are you sure you want to exit the program?"
        confirmation = QMessageBox.question(self, 'Message', quit_msg,
                                            QMessageBox.Yes, QMessageBox.No)

        if confirmation == QMessageBox.Yes:
            self.serial_thread.quit()
            self.serial_thread.wait()
            event.accept()
        else:
            event.ignore()
Esempio n. 23
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QIcon.themeName() in ('hicolor', ''):
			if not QFile.exists(icon_path + 'document-new.png'):
				QIcon.setThemeName(get_icon_theme())
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.editBoxes = []
		self.previewBoxes = []
		self.highlighters = []
		self.markups = []
		self.fileNames = []
		self.actionPreviewChecked = []
		self.actionLivePreviewChecked = []
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain)
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertChars('**'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertChars('*'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertTag('u'))
		self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span',
			'table', 'td', 'tr', 'u')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.tagsBox = QComboBox(self.editBar)
		self.tagsBox.addItem(self.tr('Tags'))
		self.tagsBox.addItems(self.usefulTags)
		self.tagsBox.activated.connect(self.insertTag)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = QMenuBar(self)
		menubar.setGeometry(QRect(0, 0, 800, 25))
		self.setMenuBar(menubar)
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addMenu(self.menuRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionLivePreview)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		menubar.addMenu(menuFile)
		menubar.addMenu(menuEdit)
		menubar.addMenu(menuHelp)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.tagsBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
				self.enableSpellCheck(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			self.ss = ''

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def getSplitter(self, index):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBoxes[index], self.previewBoxes[index]:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter

	def getWebView(self):
		webView = QWebView()
		if not globalSettings.handleWebLinks:
			webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			webView.page().linkClicked.connect(QDesktopServices.openUrl)
		settings = webView.settings()
		settings.setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		settings.setDefaultTextEncoding('utf-8')
		return webView

	def createTab(self, fileName):
		self.previewBlocked = False
		self.editBoxes.append(ReTextEdit(self))
		self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document()))
		if enchant_available and self.actionEnableSC.isChecked():
			self.highlighters[-1].dictionary = \
			enchant.Dict(self.sl) if self.sl else enchant.Dict()
			self.highlighters[-1].rehighlight()
		if globalSettings.useWebKit:
			self.previewBoxes.append(self.getWebView())
		else:
			self.previewBoxes.append(QTextBrowser())
			self.previewBoxes[-1].setOpenExternalLinks(True)
		self.previewBoxes[-1].setVisible(False)
		self.fileNames.append(fileName)
		markupClass = self.getMarkupClass(fileName)
		self.markups.append(self.getMarkup(fileName))
		self.highlighters[-1].docType = (markupClass.name if markupClass else '')
		liveMode = globalSettings.restorePreviewState and globalSettings.previewState
		self.actionPreviewChecked.append(liveMode)
		self.actionLivePreviewChecked.append(liveMode)
		metrics = QFontMetrics(self.editBoxes[-1].font())
		self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' '))
		self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox)
		self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled)
		self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled)
		self.editBoxes[-1].copyAvailable.connect(self.enableCopy)
		self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged)
		if globalSettings.useFakeVim:
			self.installFakeVimHandler(self.editBoxes[-1])
		return self.getSplitter(-1)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
			if self.fileNames[ind]:
				self.fileSystemWatcher.removePath(self.fileNames[ind])
			del self.editBoxes[ind]
			del self.previewBoxes[ind]
			del self.highlighters[ind]
			del self.markups[ind]
			del self.fileNames[ind]
			del self.actionPreviewChecked[ind]
			del self.actionLivePreviewChecked[ind]
			self.tabWidget.removeTab(ind)

	def getMarkupClass(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		if fileName:
			markupClass = markups.get_markup_for_file_name(
				fileName, return_class=True)
			if markupClass:
				return markupClass
		return self.defaultMarkup

	def getMarkup(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		markupClass = self.getMarkupClass(fileName=fileName)
		if markupClass and markupClass.available():
			return markupClass(filename=fileName)

	def docTypeChanged(self):
		oldType = self.highlighters[self.ind].docType
		markupClass = self.getMarkupClass()
		newType = markupClass.name if markupClass else ''
		if oldType != newType:
			self.markups[self.ind] = self.getMarkup()
			self.updatePreviewBox()
			self.highlighters[self.ind].docType = newType
			self.highlighters[self.ind].rehighlight()
		dtMarkdown = (newType == DOCTYPE_MARKDOWN)
		dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST))
		self.tagsBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		if ind > -1:
			self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable())
			self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable())
			self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionPreview.setChecked(self.actionPreviewChecked[ind])
			self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind])
			self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled)
			self.editBar.setDisabled(self.actionPreviewChecked[ind])
		self.ind = ind
		if self.fileNames[ind]:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(self.editBoxes[ind].document().isModified())
		if globalSettings.restorePreviewState:
			globalSettings.previewState = self.actionLivePreviewChecked[ind]
		if self.actionLivePreviewChecked[ind]:
			self.enableLivePreview(True)
		self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			globalSettings.editorFont = font
			for editor in self.editBoxes:
				editor.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			globalSettings.font = font
			self.updatePreviewBox()

	def preview(self, viewmode):
		self.actionPreviewChecked[self.ind] = viewmode
		if self.actionLivePreview.isChecked():
			self.actionLivePreview.setChecked(False)
			return self.enableLivePreview(False)
		self.editBar.setDisabled(viewmode)
		self.editBoxes[self.ind].setVisible(not viewmode)
		self.previewBoxes[self.ind].setVisible(viewmode)
		if viewmode:
			self.updatePreviewBox()

	def enableLivePreview(self, livemode):
		if globalSettings.restorePreviewState:
			globalSettings.previewState = livemode
		self.actionLivePreviewChecked[self.ind] = livemode
		self.actionPreviewChecked[self.ind] = livemode
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.previewBoxes[self.ind].setVisible(livemode)
		self.editBoxes[self.ind].setVisible(True)
		if livemode:
			self.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		oldind = self.ind
		self.tabWidget.clear()
		for self.ind in range(len(self.editBoxes)):
			if enable:
				self.previewBoxes[self.ind] = self.getWebView()
			else:
				self.previewBoxes[self.ind] = QTextBrowser()
				self.previewBoxes[self.ind].setOpenExternalLinks(True)
			splitter = self.getSplitter(self.ind)
			self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True))
			self.updatePreviewBox()
			self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind])
		self.ind = oldind
		self.tabWidget.setCurrentIndex(self.ind)

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def installFakeVimHandler(self, editor):
		if ReTextFakeVimHandler:
			fakeVimEditor = ReTextFakeVimHandler(editor, self)
			fakeVimEditor.setSaveAction(self.actionSave)
			fakeVimEditor.setQuitAction(self.actionQuit)
			self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove)

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for editor in self.editBoxes:
				self.installFakeVimHandler(editor)
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			if self.sl:
				self.setAllDictionaries(enchant.Dict(self.sl))
			else:
				self.setAllDictionaries(enchant.Dict())
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for hl in self.highlighters:
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.editBoxes[self.ind]
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def getHtml(self, includeStyleSheet=True, includeTitle=True,
	            includeMeta=False, webenv=False):
		if self.markups[self.ind] is None:
			markupClass = self.getMarkupClass()
			errMsg = self.tr('Could not parse file contents, check if '
			'you have the <a href="%s">necessary module</a> installed!')
			try:
				errMsg %= markupClass.attributes[MODULE_HOME_PAGE]
			except (AttributeError, KeyError):
				# Remove the link if markupClass doesn't have the needed attribute
				errMsg = errMsg.replace('<a href="%s">', '')
				errMsg = errMsg.replace('</a>', '')
			return '<p style="color: red">%s</p>' % errMsg
		text = self.editBoxes[self.ind].toPlainText()
		headers = ''
		if includeStyleSheet:
			headers += '<style type="text/css">\n' + self.ss + '</style>\n'
		cssFileName = self.getDocumentTitle(baseName=True)+'.css'
		if QFile(cssFileName).exists():
			headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \
			% cssFileName
		if includeMeta:
			headers += ('<meta name="generator" content="ReText %s">\n' %
			            app_version)
		fallbackTitle = self.getDocumentTitle() if includeTitle else ''
		return self.markups[self.ind].get_whole_html(text,
			custom_headers=headers, include_stylesheet=includeStyleSheet,
			fallback_title=fallbackTitle, webenv=webenv)

	def updatePreviewBox(self):
		self.previewBlocked = False
		pb = self.previewBoxes[self.ind]
		textedit = isinstance(pb, QTextEdit)
		if textedit:
			scrollbar = pb.verticalScrollBar()
			disttobottom = scrollbar.maximum() - scrollbar.value()
		else:
			frame = pb.page().mainFrame()
			scrollpos = frame.scrollPosition()
		try:
			html = self.getHtml()
		except Exception:
			return self.printError()
		if textedit:
			pb.setHtml(html)
			pb.document().setDefaultFont(globalSettings.font)
			scrollbar.setValue(scrollbar.maximum() - disttobottom)
		else:
			pb.settings().setFontFamily(QWebSettings.StandardFont,
			                            globalSettings.font.family())
			pb.settings().setFontSize(QWebSettings.DefaultFontSize,
			                          globalSettings.font.pointSize())
			pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind]))
			frame.setScrollPosition(scrollpos)

	def updateLivePreviewBox(self):
		if self.actionLivePreview.isChecked() and self.previewBlocked == False:
			self.previewBlocked = True
			QTimer.singleShot(1000, self.updatePreviewBox)

	def showInDir(self):
		if self.fileNames[self.ind]:
			path = QFileInfo(self.fileNames[self.ind]).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True))
		self.setWindowFilePath(self.fileNames[self.ind])
		files = readListFromSettings("recentFileList")
		while self.fileNames[self.ind] in files:
			files.remove(self.fileNames[self.ind])
		files.insert(0, self.fileNames[self.ind])
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.editBoxes[self.ind].textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFuntion(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFuntion(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i in range(self.tabWidget.count()):
			if self.fileNames[i] == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.fileNames[self.ind] or
				self.editBoxes[self.ind].toPlainText() or
				self.editBoxes[self.ind].document().isModified()
			)
			if noEmptyTab:
				self.tabWidget.addTab(self.createTab(fileName), "")
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.fileNames[self.ind] = fileName
			self.openFileMain()

	def openFileMain(self, encoding=None):
		openfile = QFile(self.fileNames[self.ind])
		openfile.open(QIODevice.ReadOnly)
		stream = QTextStream(openfile)
		if encoding:
			stream.setCodec(encoding)
		elif globalSettings.defaultCodec:
			stream.setCodec(globalSettings.defaultCodec)
		text = stream.readAll()
		openfile.close()
		markupClass = markups.get_markup_for_file_name(
			self.fileNames[self.ind], return_class=True)
		self.highlighters[self.ind].docType = (markupClass.name if markupClass else '')
		self.markups[self.ind] = self.getMarkup()
		if self.defaultMarkup:
			self.highlighters[self.ind].docType = self.defaultMarkup.name
		editBox = self.editBoxes[self.ind]
		modified = bool(encoding) and (editBox.toPlainText() != text)
		editBox.setPlainText(text)
		self.setCurrentFile()
		editBox.document().setModified(modified)
		self.setWindowModified(modified)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.openFileMain(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		oldind = self.ind
		for self.ind in range(self.tabWidget.count()):
			if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable():
				self.saveFileCore(self.fileNames[self.ind])
				self.editBoxes[self.ind].document().setModified(False)
		self.ind = oldind

	def saveFileMain(self, dlg):
		if (not self.fileNames[self.ind]) or dlg:
			markupClass = self.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.fileNames[self.ind]:
					self.fileSystemWatcher.removePath(self.fileNames[self.ind])
				self.fileNames[self.ind] = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.fileNames[self.ind]:
			result = self.saveFileCore(self.fileNames[self.ind])
			if result:
				self.setCurrentFile()
				self.editBoxes[self.ind].document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveFileCore(self, fn, addToWatcher=True):
		self.fileSystemWatcher.removePath(fn)
		savefile = QFile(fn)
		result = savefile.open(QIODevice.WriteOnly)
		if result:
			savestream = QTextStream(savefile)
			if globalSettings.defaultCodec:
				savestream.setCodec(globalSettings.defaultCodec)
			savestream << self.editBoxes[self.ind].toPlainText()
			savefile.close()
		if result and addToWatcher:
			self.fileSystemWatcher.addPath(fn)
		return result

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True,
			webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.getHtml())
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat("odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.previewBoxes[self.ind]
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.getDocumentTitle())
		printer.setCreator('ReText %s' % app_version)
		return printer

	def savePdf(self):
		self.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename+self.getMarkupClass().default_extension
			self.saveFileCore(tmpname, addToWatcher=False)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def getDocumentTitle(self, baseName=False):
		markup = self.markups[self.ind]
		realTitle = ''
		if markup and not baseName:
			text = self.editBoxes[self.ind].toPlainText()
			try:
				realTitle = markup.get_document_title(text)
			except Exception:
				self.printError()
		if realTitle:
			return realTitle
		elif self.fileNames[self.ind]:
			fileinfo = QFileInfo(self.fileNames[self.ind])
			basename = fileinfo.completeBaseName()
			return (basename if basename else fileinfo.fileName())
		return self.tr("New document")

	def autoSaveActive(self):
		return self.autoSaveEnabled and self.fileNames[self.ind] and \
		QFileInfo(self.fileNames[self.ind]).isWritable()

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertChars(self, chars):
		tc = self.editBoxes[self.ind].textCursor()
		if tc.hasSelection():
			selection = tc.selectedText()
			if selection.startswith(chars) and selection.endswith(chars):
				if len(selection) > 2*len(chars):
					selection = selection[len(chars):-len(chars)]
					tc.insertText(selection)
			else:
				tc.insertText(chars+tc.selectedText()+chars)
		else:
			tc.insertText(chars)

	def insertTag(self, ut):
		if not ut:
			return
		if isinstance(ut, int):
			ut = self.usefulTags[ut - 1]
		arg = ' style=""' if ut == 'span' else ''
		tc = self.editBoxes[self.ind].textCursor()
		if ut == 'img':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank"><img src="' + tc.selectedText() + '"/></a>')
		elif ut == 'a':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank">' + tc.selectedText() + '</a>')
		else:
			toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>'
		tc.insertText(toinsert)
		self.tagsBox.setCurrentIndex(0)

	def insertSymbol(self, num):
		if num:
			self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = self.fileNames.index(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.editBoxes[ind].document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.editBoxes[ind].document().isModified():
			# File was not modified in ReText, reload silently
			self.openFileMain()
			self.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.openFileMain()
				self.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.editBoxes[ind].document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		if self.autoSaveActive():
			self.saveFileCore(self.fileNames[self.ind])
			return True
		if not self.editBoxes[ind].document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for self.ind in range(self.tabWidget.count()):
			if not self.maybeSave(self.ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False)
		except Exception:
			return self.printError()
		winTitle = self.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markup):
		self.defaultMarkup = markup
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markup.name, defaultName)
		oldind = self.ind
		for self.ind in range(len(self.previewBoxes)):
			self.docTypeChanged()
		self.ind = oldind
Esempio n. 24
0
class MainWindowActions(AttrDict):
    """Holds all QActions for the main window"""
    def __init__(self, parent):
        super().__init__()
        self.parent = parent

        self.create_file_actions()
        self.create_edit_actions()
        self.create_view_actions()
        self.create_format_actions()
        self.create_macro_actions()
        self.create_help_actions()

        self.disable_unavailable()

    def create_file_actions(self):
        """actions for File menu"""

        self.new = Action(self.parent,
                          "&New",
                          self.parent.workflows.file_new,
                          icon=Icon.new,
                          shortcut='Ctrl+n',
                          statustip='Create a new, empty spreadsheet')

        self.open = Action(self.parent,
                           "&Open",
                           self.parent.workflows.file_open,
                           icon=Icon.open,
                           statustip='Open spreadsheet from file')

        self.save = Action(self.parent,
                           "&Save",
                           self.parent.workflows.file_save,
                           icon=Icon.save,
                           shortcut='Ctrl+s',
                           statustip='Save spreadsheet')

        self.save_as = Action(self.parent,
                              "Save &As",
                              self.parent.workflows.file_save_as,
                              icon=Icon.save_as,
                              shortcut='Shift+Ctrl+s',
                              statustip='Save spreadsheet to a new file')

        self.imprt = Action(self.parent,
                            "&Import",
                            self.parent.workflows.file_import,
                            icon=Icon.imprt,
                            statustip='Import a file and paste it into the '
                            'current grid')

        self.export = Action(self.parent,
                             "&Export",
                             self.parent.workflows.file_export,
                             icon=Icon.export,
                             statustip="Export selection to a file")

        self.approve = Action(self.parent,
                              "&Approve file",
                              self.parent.on_approve,
                              icon=Icon.approve,
                              statustip='Approve, unfreeze and sign the '
                              'current file')

        self.clear_globals = Action(self.parent,
                                    "&Clear globals",
                                    self.parent.on_clear_globals,
                                    icon=Icon.clear_globals,
                                    statustip='Deletes global variables '
                                    'and reloads base modules')

        self.print_preview = Action(self.parent,
                                    "Print preview",
                                    self.parent.on_preview,
                                    icon=Icon.print_preview,
                                    statustip='Print preview')

        self.print = Action(self.parent,
                            "Print",
                            self.parent.on_print,
                            icon=Icon.print,
                            shortcut='Ctrl+p',
                            statustip='Print current spreadsheet')

        self.preferences = Action(self.parent,
                                  "Preferences...",
                                  self.parent.on_preferences,
                                  icon=Icon.preferences,
                                  statustip='Pyspread setup parameters')

        self.quit = Action(self.parent,
                           "&Quit",
                           self.parent.closeEvent,
                           icon=Icon.quit,
                           shortcut='Ctrl+Q',
                           statustip='Exit pyspread')

    def create_edit_actions(self):
        """actions for Edit menu"""

        self.undo = Action(self.parent,
                           "&Undo",
                           self.parent.on_undo,
                           icon=Icon.undo,
                           shortcut='Ctrl+z',
                           statustip='Undo last step')

        self.redo = Action(self.parent,
                           "&Redo",
                           self.parent.on_redo,
                           icon=Icon.redo,
                           shortcut='Shift+Ctrl+z',
                           statustip='Redo last undone step')

        self.cut = Action(self.parent,
                          "Cut",
                          self.parent.workflows.edit_cut,
                          icon=Icon.cut,
                          shortcut='Ctrl+x',
                          statustip='Cut cell to the clipboard')

        self.copy = Action(self.parent,
                           "&Copy",
                           self.parent.workflows.edit_copy,
                           icon=Icon.copy,
                           shortcut='Ctrl+c',
                           statustip='Copy the input strings of the cells '
                           'to the clipboard')

        self.copy_results = Action(self.parent,
                                   "Copy results",
                                   self.parent.workflows.edit_copy_results,
                                   icon=Icon.copy_results,
                                   shortcut='Shift+Ctrl+c',
                                   statustip='Copy the result strings of '
                                   'the cells to the clipboard')

        self.paste = Action(self.parent,
                            "&Paste",
                            self.parent.workflows.edit_paste,
                            icon=Icon.paste,
                            shortcut='Ctrl+v',
                            statustip='Paste cells from the clipboard')

        self.paste_as = Action(self.parent,
                               "Paste as...",
                               self.parent.workflows.edit_paste_as,
                               icon=Icon.paste_as,
                               shortcut='Shift+Ctrl+v',
                               statustip='Transform clipboard and paste '
                               'results')

        self.find = Action(self.parent,
                           "&Find...",
                           self.parent.workflows.edit_find,
                           icon=Icon.find,
                           shortcut='Ctrl+f',
                           statustip='Find dialog')

        self.find_next = Action(self.parent,
                                "&Find next",
                                self.parent.workflows.edit_find_next,
                                icon=Icon.find_next,
                                shortcut='F3',
                                statustip='Find next matching cell')

        self.replace = Action(self.parent,
                              "&Replace...",
                              self.parent.workflows.edit_replace,
                              icon=Icon.replace,
                              shortcut='Shift+Ctrl+f',
                              statustip='Replace sub-strings in cells')

        self.quote = Action(self.parent,
                            "&Quote",
                            self.parent.grid.on_quote,
                            icon=Icon.quote,
                            shortcut='Ctrl+Return',
                            statustip="Convert cells' code to strings by "
                            "addding quotes")

        self.insert_rows = Action(self.parent,
                                  "Insert rows",
                                  self.parent.grid.on_insert_rows,
                                  icon=Icon.insert_row,
                                  statustip='Insert max(1, no. selected '
                                  'rows) rows at cursor')

        self.insert_columns = Action(self.parent,
                                     "Insert columns",
                                     self.parent.grid.on_insert_columns,
                                     icon=Icon.insert_column,
                                     statustip='Insert max(1, no. selected '
                                     'columns) columns at cursor')

        self.insert_table = Action(self.parent,
                                   "Insert table",
                                   self.parent.grid.on_insert_table,
                                   icon=Icon.insert_table,
                                   statustip='Insert table before current '
                                   'table')

        self.delete_rows = Action(self.parent,
                                  "Delete rows",
                                  self.parent.grid.on_delete_rows,
                                  icon=Icon.delete_row,
                                  statustip='Delete max(1, no. selected '
                                  'rows) rows at cursor')

        self.delete_columns = Action(self.parent,
                                     "Delete columns",
                                     self.parent.grid.on_delete_columns,
                                     icon=Icon.delete_column,
                                     statustip='Delete max(1, no. selected '
                                     'columns) columns at cursor')

        self.delete_table = Action(self.parent,
                                   "Delete table",
                                   self.parent.grid.on_delete_table,
                                   icon=Icon.delete_table,
                                   statustip='Delete current table')

        self.resize_grid = Action(self.parent,
                                  "Resize grid",
                                  self.parent.workflows.edit_resize,
                                  icon=Icon.resize_grid,
                                  statustip='Resizes the current grid')

    def create_view_actions(self):
        """actions for View menu"""

        self.fullscreen = Action(self.parent,
                                 "Fullscreen",
                                 self.parent.on_fullscreen,
                                 icon=Icon.fullscreen,
                                 shortcut='F11',
                                 statustip='Show grid in fullscreen mode '
                                 '(press <F11> to leave)')

        self.toggle_main_toolbar = Action(self.parent,
                                          "Main toolbar",
                                          self.parent.on_toggle_main_toolbar,
                                          checkable=True,
                                          statustip='Show/hide the main '
                                          'toolbar')

        self.toggle_macro_toolbar = Action(self.parent,
                                           "Macro toolbar",
                                           self.parent.on_toggle_macro_toolbar,
                                           checkable=True,
                                           statustip='Show/hide the macro '
                                           'toolbar')

        self.toggle_format_toolbar = \
            Action(self.parent, "Format toolbar",
                   self.parent.on_toggle_format_toolbar,
                   checkable=True,
                   statustip='Show/hide the format toolbar')

        self.toggle_find_toolbar = Action(self.parent,
                                          "Find toolbar",
                                          self.parent.on_toggle_find_toolbar,
                                          checkable=True,
                                          statustip='Show/hide the find '
                                          'toolbar')

        self.toggle_entry_line = Action(self.parent,
                                        "Entry line",
                                        self.parent.on_toggle_entry_line,
                                        checkable=True,
                                        statustip='Show/hide the entry line')

        self.toggle_macro_panel = Action(self.parent,
                                         "Macro panel",
                                         self.parent.on_toggle_macro_panel,
                                         checkable=True,
                                         shortcut='F4',
                                         statustip='Show/hide the macro panel')

        self.goto_cell = Action(self.parent,
                                "Go to cell",
                                self.parent.workflows.view_goto_cell,
                                icon=Icon.goto_cell,
                                shortcut='Ctrl+g',
                                statustip='Select a cell and put it into view')

        self.toggle_spell_checker = \
            Action(self.parent, "Toggle spell checker",
                   self.parent.entry_line.on_toggle_spell_check,
                   icon=Icon.check_spelling,
                   checkable=True,
                   statustip='Turn the spell checker in the entry line on/off')

        self.zoom_in = Action(self.parent,
                              "Zoom in",
                              self.parent.grid.on_zoom_in,
                              icon=Icon.zoom_in,
                              shortcut='Ctrl++',
                              statustip='Zoom in the grid')

        self.zoom_out = Action(self.parent,
                               "Zoom out",
                               self.parent.grid.on_zoom_out,
                               icon=Icon.zoom_out,
                               shortcut='Ctrl+-',
                               statustip='Zoom out the grid')

        self.zoom_1 = Action(self.parent,
                             "Original size",
                             self.parent.grid.on_zoom_1,
                             icon=Icon.zoom_1,
                             shortcut='Ctrl+0',
                             statustip='Show grid on standard zoom level')

        self.refresh_cells = \
            Action(self.parent, "Refresh selected cells",
                   self.parent.grid.refresh_selected_frozen_cells,
                   icon=Icon.refresh, shortcut=QKeySequence.Refresh,
                   statustip='Refresh selected cells even when frozen')

        self.toggle_periodic_updates = \
            Action(self.parent, "Toggle periodic updates",
                   self.parent.on_toggle_refresh_timer,
                   icon=Icon.toggle_periodic_updates, checkable=True,
                   statustip='Toggles periodic updates for frozen cells')

        self.show_frozen = Action(self.parent,
                                  "Show frozen",
                                  self.parent.grid.on_show_frozen_pressed,
                                  icon=Icon.show_frozen,
                                  checkable=True,
                                  statustip='Indicates frozen cells with a '
                                  'background crosshatch')

    def create_format_actions(self):
        """actions for Format menu"""

        self.copy_format = Action(self.parent,
                                  "&Copy format",
                                  self.parent.workflows.format_copy_format,
                                  icon=Icon.copy_format,
                                  statustip='Copy format of selection to '
                                  'the clipboard')

        self.paste_format = \
            Action(self.parent, "&Paste format",
                   self.parent.workflows.format_paste_format,
                   icon=Icon.paste_format,
                   statustip='Apply format from the clipboard to the selected '
                             'cells')

        self.font = Action(self.parent,
                           "&Font...",
                           self.parent.grid.on_font_dialog,
                           icon=Icon.font_dialog,
                           shortcut='Ctrl+n',
                           statustip='Lauch font dialog')

        self.bold = Action(self.parent,
                           "&Bold",
                           self.parent.grid.on_bold_pressed,
                           icon=Icon.bold,
                           shortcut='Ctrl+b',
                           checkable=True,
                           statustip='Toggle bold font weight for the '
                           'selected cells')

        self.italics = Action(self.parent,
                              "&Italics",
                              self.parent.grid.on_italics_pressed,
                              icon=Icon.italics,
                              shortcut='Ctrl+i',
                              checkable=True,
                              statustip='Toggle italics font style for the '
                              'selected cells')

        self.underline = Action(self.parent,
                                "&Underline",
                                self.parent.grid.on_underline_pressed,
                                icon=Icon.underline,
                                shortcut='Ctrl+u',
                                checkable=True,
                                statustip='Toggle underline for the '
                                'selected cells')

        self.strikethrough = Action(self.parent,
                                    "&Strikethrough",
                                    self.parent.grid.on_strikethrough_pressed,
                                    icon=Icon.strikethrough,
                                    checkable=True,
                                    statustip='Toggle strikethrough for the '
                                    'selected cells')

        self.text = Action(self.parent,
                           "Text renderer",
                           self.parent.grid.on_text_renderer_pressed,
                           icon=Icon.text,
                           checkable=True,
                           statustip='Show cell results as text (default). '
                           'Formats affect the whole cell')

        self.markup = Action(self.parent,
                             "Markup renderer",
                             self.parent.grid.on_markup_renderer_pressed,
                             icon=Icon.markup,
                             checkable=True,
                             statustip='Show cell results as markup, which '
                             'allows partly formatted output')

        self.image = Action(self.parent,
                            "Image renderer",
                            self.parent.grid.on_image_renderer_pressed,
                            icon=Icon.image,
                            checkable=True,
                            statustip='Show cell results as image. A numpy '
                            'array of shape (x, y, 3) '
                            'is expected')
        if matplotlib_figure is not None:
            self.matplotlib = \
                Action(self.parent, "Matplotlib chart renderer",
                       self.parent.grid.on_matplotlib_renderer_pressed,
                       icon=Icon.matplotlib,
                       checkable=True,
                       statustip='Show cell results as matplotlib chart. A '
                                 'numpy array of shape (x, y, 3) is expected')

        renderer_group = QActionGroup(self.parent)
        renderer_group.addAction(self.text)
        renderer_group.addAction(self.markup)
        renderer_group.addAction(self.image)
        if matplotlib_figure is not None:
            renderer_group.addAction(self.matplotlib)

        self.text_color = Action(
            self.parent,
            "Text color...",
            self.parent.widgets.text_color_button.on_pressed,
            icon=Icon.text_color,
            statustip='Lauch text color dialog')

        self.line_color = Action(
            self.parent,
            "Line color...",
            self.parent.widgets.line_color_button.on_pressed,
            icon=Icon.line_color,
            statustip='Lauch line color dialog')

        self.background_color = Action(
            self.parent,
            "Background color...",
            self.parent.widgets.background_color_button.on_pressed,
            icon=Icon.background_color,
            statustip='Lauch background color dialog')

        self.freeze_cell = Action(self.parent,
                                  "Freeze cell",
                                  self.parent.grid.on_freeze_pressed,
                                  icon=Icon.freeze,
                                  checkable=True,
                                  statustip='Freeze the selected cell so that '
                                  'is is only updated when <F5> is '
                                  'pressed')

        self.lock_cell = Action(self.parent,
                                "Lock cell",
                                self.parent.grid.on_lock_pressed,
                                icon=Icon.lock,
                                checkable=True,
                                statustip='Lock cell so that its code '
                                'cannot be changed')

        self.button_cell = Action(self.parent,
                                  "Button cell",
                                  self.parent.grid.on_button_cell_pressed,
                                  icon=Icon.button,
                                  checkable=True,
                                  statustip='Make cell a button cell that is '
                                  'executed only when pressed')

        self.merge_cells = Action(self.parent,
                                  "Merge cells",
                                  self.parent.grid.on_merge_pressed,
                                  icon=Icon.merge_cells,
                                  checkable=True,
                                  statustip='Merge/unmerge selected cells')

        self.rotate_0 = Action(self.parent,
                               "0°",
                               self.parent.grid.on_rotate_0,
                               icon=Icon.rotate_0,
                               checkable=True,
                               statustip='Set text rotation to 0°')

        self.rotate_90 = Action(self.parent,
                                "90°",
                                self.parent.grid.on_rotate_90,
                                icon=Icon.rotate_90,
                                checkable=True,
                                statustip='Set text rotation to 90°')

        self.rotate_180 = Action(self.parent,
                                 "180°",
                                 self.parent.grid.on_rotate_180,
                                 icon=Icon.rotate_180,
                                 checkable=True,
                                 statustip='Set text rotation to 180°')

        self.rotate_270 = Action(self.parent,
                                 "270°",
                                 self.parent.grid.on_rotate_270,
                                 icon=Icon.rotate_270,
                                 checkable=True,
                                 statustip='Set text rotation to 270°')

        rotate_group = QActionGroup(self.parent)
        rotate_group.addAction(self.rotate_0)
        rotate_group.addAction(self.rotate_90)
        rotate_group.addAction(self.rotate_180)
        rotate_group.addAction(self.rotate_270)

        self.justify_left = Action(self.parent,
                                   "Left",
                                   self.parent.grid.on_justify_left,
                                   icon=Icon.justify_left,
                                   checkable=True,
                                   statustip='Display cell result text '
                                   'left justified')

        self.justify_center = Action(self.parent,
                                     "Center",
                                     self.parent.grid.on_justify_center,
                                     checkable=True,
                                     icon=Icon.justify_center,
                                     statustip='Display cell result text '
                                     'centered')

        self.justify_right = Action(self.parent,
                                    "Right",
                                    self.parent.grid.on_justify_right,
                                    checkable=True,
                                    icon=Icon.justify_right,
                                    statustip='Display cell result text '
                                    'right justified')

        self.justify_fill = Action(self.parent,
                                   "Fill",
                                   self.parent.grid.on_justify_fill,
                                   icon=Icon.justify_fill,
                                   checkable=True,
                                   statustip='Display cell result text '
                                   'filled into the cell')

        justify_group = QActionGroup(self.parent)
        justify_group.addAction(self.justify_left)
        justify_group.addAction(self.justify_center)
        justify_group.addAction(self.justify_right)
        justify_group.addAction(self.justify_fill)

        self.align_top = Action(self.parent,
                                "Top",
                                self.parent.grid.on_align_top,
                                icon=Icon.align_top,
                                checkable=True,
                                statustip='Align cell result at the top of '
                                'the cell')

        self.align_center = Action(self.parent,
                                   "Center",
                                   self.parent.grid.on_align_middle,
                                   icon=Icon.align_center,
                                   checkable=True,
                                   statustip='Center cell result within '
                                   'the cell')

        self.align_bottom = Action(self.parent,
                                   "Bottom",
                                   self.parent.grid.on_align_bottom,
                                   icon=Icon.align_bottom,
                                   checkable=True,
                                   statustip='Align cell result at the '
                                   'bottom of the cell')

        align_group = QActionGroup(self.parent)
        align_group.addAction(self.align_top)
        align_group.addAction(self.align_center)
        align_group.addAction(self.align_bottom)

        self.format_borders_all = \
            Action(self.parent, "All borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_all, checkable=True,
                   statustip='Format all borders of selection')

        self.format_borders_top = \
            Action(self.parent, "Top border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_top, checkable=True,
                   statustip='Format top border of selection')

        self.format_borders_bottom = \
            Action(self.parent, "Bottom border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_bottom, checkable=True,
                   statustip='Format bottom border of selection')

        self.format_borders_left = \
            Action(self.parent, "Left border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_left, checkable=True,
                   statustip='Format left border of selection')

        self.format_borders_right = \
            Action(self.parent, "Right border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_right, checkable=True,
                   statustip='Format right border of selection')

        self.format_borders_outer = \
            Action(self.parent, "Outer borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_outer, checkable=True,
                   statustip='Format outer borders of selection')

        self.format_borders_inner = \
            Action(self.parent, "Inner borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_inner, checkable=True,
                   statustip='Format inner borders of selection')

        self.format_borders_top_bottom = \
            Action(self.parent, "Top and bottom borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_top_bottom, checkable=True,
                   statustip='Format top and bottom borders of selection')

        self.border_group = QActionGroup(self.parent)
        self.border_group.addAction(self.format_borders_all)
        self.border_group.addAction(self.format_borders_top)
        self.border_group.addAction(self.format_borders_bottom)
        self.border_group.addAction(self.format_borders_left)
        self.border_group.addAction(self.format_borders_right)
        self.border_group.addAction(self.format_borders_outer)
        self.border_group.addAction(self.format_borders_inner)
        self.border_group.addAction(self.format_borders_top_bottom)
        self.format_borders_all.setChecked(True)

        self.format_borders_0 = Action(self.parent,
                                       "Border width 0",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_0,
                                       statustip='Set border width to 0')

        self.format_borders_1 = Action(self.parent,
                                       "Border width 1",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_1,
                                       statustip='Set border width to 1')

        self.format_borders_2 = Action(self.parent,
                                       "Border width 2",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_2,
                                       statustip='Set border width to 2')

        self.format_borders_4 = Action(self.parent,
                                       "Border width 4",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_4,
                                       statustip='Set border width to 4')

        self.format_borders_8 = Action(self.parent,
                                       "Border width 8",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_8,
                                       statustip='Set border width to 8')

        self.format_borders_16 = Action(self.parent,
                                        "Border width 16",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_16,
                                        statustip='Set border width to 16')

        self.format_borders_32 = Action(self.parent,
                                        "Border width 32",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_32,
                                        statustip='Set border width to 32')

        self.format_borders_64 = Action(self.parent,
                                        "Border width 64",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_64,
                                        statustip='Set border width to 64')

        self.border_width_group = QActionGroup(self.parent)
        self.border_width_group.addAction(self.format_borders_0)
        self.border_width_group.addAction(self.format_borders_1)
        self.border_width_group.addAction(self.format_borders_2)
        self.border_width_group.addAction(self.format_borders_4)
        self.border_width_group.addAction(self.format_borders_8)
        self.border_width_group.addAction(self.format_borders_16)
        self.border_width_group.addAction(self.format_borders_32)
        self.border_width_group.addAction(self.format_borders_64)
        self.format_borders_1.setChecked(True)

    def create_macro_actions(self):
        """Create actions for Macro menu"""

        self.insert_image = Action(self.parent,
                                   "Insert image...",
                                   self.parent.workflows.macro_insert_image,
                                   icon=Icon.insert_image,
                                   statustip='Load an image from a file '
                                   'into a cell')

        self.insert_chart = Action(self.parent,
                                   "Insert chart...",
                                   self.parent.workflows.macro_insert_chart,
                                   icon=Icon.insert_chart,
                                   statustip='Create and display matplotlib '
                                   'chart')

    def create_help_actions(self):
        """actions for Help menu"""

        self.manual = Action(self.parent,
                             "Manual...",
                             self.parent.on_manual,
                             icon=Icon.help,
                             shortcut='F1',
                             statustip='Display the pyspread manual')

        self.tutorial = Action(self.parent,
                               "Tutorial...",
                               self.parent.on_tutorial,
                               icon=Icon.tutorial,
                               statustip='Display a pyspread tutorial')

        self.dependencies = Action(self.parent,
                                   "Dependencies...",
                                   self.parent.on_dependencies,
                                   icon=Icon.dependencies,
                                   statustip='List and install dependencies')

        self.about = Action(self.parent,
                            "About pyspread...",
                            self.parent.on_about,
                            icon=Icon.pyspread,
                            statustip='About pyspread')

    def disable_unavailable(self):
        """Disables unavailable menu items e.g. due to missing dependencies"""

        if get_enchant_version() is None:
            self.toggle_spell_checker.setEnabled(False)
Esempio n. 25
0
class MyPlot(QObject):
    def __init__(self, fig, plt, parent):
        QtCore.QObject.__init__(self, parent)
        self.fig = fig
        self.plt = plt
        self.fig.canvas.mpl_connect('scroll_event', self.onScroll)
        self.fig.canvas.mpl_connect('button_press_event', self.onPress)
        self.fig.canvas.mpl_connect('button_release_event', self.onRelease)

        self.pan = None
        self.sel = None  # Derived classes should initialize to Rectangle
        self.fieldfilterX = None  # Derived classes should intialize if manageX
        self.fieldfilterY = None  # Derived classes should intialize if manageY
        self.selp = None
        self.cb = None  # Colorbar

        self.manageX = False
        self.manageY = False

        self.menu = QMenu()
        saveAct = self.menu.addAction("Save PNG")
        saveAct.triggered.connect(parent.onSave)
        saveAct = self.menu.addAction("Save SVG")
        saveAct.triggered.connect(parent.onSaveSVG)
        resetAct = self.menu.addAction("Reset")
        resetAct.triggered.connect(self.onReset)

        drawActionGroup = QActionGroup(self)
        self.drawAll = drawActionGroup.addAction("Draw All (Ignore Selection)")
        self.drawAll.triggered.connect(self.mydraw)
        self.drawAll.setCheckable(True)
        self.drawBoth = drawActionGroup.addAction(
            "Draw All Semi-transparent; Selection Full Color")
        self.drawBoth.triggered.connect(self.mydraw)
        self.drawBoth.setCheckable(True)
        self.drawBoth.setChecked(True)
        self.drawSelection = drawActionGroup.addAction("Draw Selection Only")
        self.drawSelection.triggered.connect(self.mydraw)
        self.drawSelection.setCheckable(True)
        self.datamenu = self.menu.addMenu("Data")
        self.datamenu.addAction(self.drawAll)
        self.datamenu.addAction(self.drawBoth)
        self.datamenu.addAction(self.drawSelection)
        self.legendActionGroup = None

        self.range = None
        self.origRange = None

    def legendMenu(self):
        self.legendActionGroup = QActionGroup(self)
        legMenu = self.menu.addMenu("Legend")
        legInit = None

        legOpt = self.legendActionGroup.addAction("None")
        legOpt.triggered.connect(self.mydraw)
        legOpt.setCheckable(True)
        legOpt.setChecked(legInit is None)
        legOpt.setData(None)
        legMenu.addAction(legOpt)

        legendOpts = [
            'best', 'upper right', 'upper left', 'lower left', 'lower right',
            'right', 'center left', 'center right', 'lower center',
            'upper center', 'center'
        ]
        for i, opt in enumerate(legendOpts):
            legOpt = self.legendActionGroup.addAction(opt)
            legOpt.triggered.connect(self.mydraw)
            legOpt.setCheckable(True)
            legOpt.setChecked(i == legInit)
            legOpt.setData(i)
            legMenu.addAction(legOpt)

    def initRange(self, xrange, yrange):
        if self.manageX and self.manageY:
            self.origRange = (tuple(xrange), tuple(yrange))
            rangeAct = self.menu.addAction("Set Range to Current Limits")
            rangeAct.triggered.connect(self.onSetRange)

            xrangeAct = self.menu.addAction("Set X Range to Current Limits")
            xrangeAct.triggered.connect(self.onSetXRange)

            yrangeAct = self.menu.addAction("Set Y Range to Current Limits")
            yrangeAct.triggered.connect(self.onSetYRange)
        elif self.manageX:
            self.origRange = tuple(xrange)
            rangeAct = self.menu.addAction("Set Range to Current Limits")
            rangeAct.triggered.connect(self.onSetXRange)
        elif self.manageY:
            self.origRange = tuple(yrange)
            rangeAct = self.menu.addAction("Set Range to Current Limits")
            rangeAct.triggered.connect(self.onSetYRange)
        self.range = self.origRange

    def onSetRange(self):
        self.range = (tuple(self.plt.get_xlim()), tuple(self.plt.get_ylim()))
        self.mydraw()

    def onSetXRange(self):
        if self.manageY:
            self.range = (tuple(self.plt.get_xlim()), self.range[1])
        else:
            self.range = tuple(self.plt.get_xlim())
        self.mydraw(False)

    def onSetYRange(self):
        if self.manageX:
            self.range = (self.range[0], tuple(self.plt.get_ylim()))
        else:
            self.range = tuple(self.plt.get_ylim())
        self.mydraw(False)

    def onReset(self):
        self.range = self.origRange
        self.mydraw(False)

    def datadraw(self):
        pass

    def mydraw(self, keeplimits=True):
        if keeplimits:
            xlim = self.plt.get_xlim()
            ylim = self.plt.get_ylim()
        self.plt.cla()
        self.datadraw()
        if self.sel is not None:
            self.plt.add_patch(self.sel)
        if keeplimits and self.manageX:
            self.plt.set_xlim(xlim)
        if keeplimits and self.manageY:
            self.plt.set_ylim(ylim)
        self.parent().draw()

    def onScroll(self, event):
        xcenter = event.xdata
        ycenter = event.ydata
        x, y, axis = event.x, event.y, event.inaxes

        # Axis scroll
        xAxes, yAxes = self.plt.transAxes.inverted().transform([x, y])
        if xAxes < 0 and xAxes >= -0.2 and yAxes >= 0 and yAxes <= 1:  # Hover over y axis
            ycenter = self.plt.transData.inverted().transform([0, y])[1]
            axis = self.plt
        if yAxes < 0 and yAxes >= -0.2 and xAxes >= 0 and xAxes <= 1:  # Hover over x axis
            xcenter = self.plt.transData.inverted().transform([x, 0])[0]
            axis = self.plt

        if xcenter is None and ycenter is None or axis != self.plt:
            return
        scale = 1
        factor = 1.5
        if event.button == 'up':
            scale = factor
        else:
            scale = 1.0 / factor
        if scale != 1:
            if self.manageX and xcenter is not None:
                cur_xlim = self.plt.get_xlim()
                self.plt.set_xlim([
                    xcenter - (xcenter - cur_xlim[0]) / scale,
                    xcenter + (cur_xlim[1] - xcenter) / scale
                ])
            if self.manageY and ycenter is not None:
                cur_ylim = self.plt.get_ylim()
                self.plt.set_ylim([
                    ycenter - (ycenter - cur_ylim[0]) / scale,
                    ycenter + (cur_ylim[1] - ycenter) / scale
                ])
            if (self.manageX
                    and xcenter is not None) or (self.manageY
                                                 and ycenter is not None):
                self.parent().draw()

    def onPress(self, event):
        if event.button == 1 and event.xdata is not None and event.ydata is not None and event.inaxes == self.plt:
            if event.key == 'shift' or QtCore.Qt.ShiftModifier & QApplication.keyboardModifiers(
            ):
                self.selp = (event.xdata, event.ydata)
                if self.manageX and self.fieldfilterX is not None:
                    self.sel.set_x(event.xdata)
                    self.sel.set_width(0)
                if self.manageY and self.fieldfilterY is not None:
                    self.sel.set_y(event.ydata)
                    self.sel.set_height(0)
                if (self.manageX and self.fieldfilterX is not None) or \
                   (self.manageY and self.fieldfilterY is not None):
                    self.sel.set_visible(True)
                    self.parent().draw()
            else:
                self.pan = (event.xdata, event.ydata)
        elif event.button == 3 and event.inaxes == self.plt:
            self.menu.popup(QCursor.pos())

    def onRelease(self, event):
        if self.pan is not None:
            self.pan = None
        elif self.selp is not None:
            if not self.manageX or not self.manageY:
                self.selp = None  # Only managing 0 or  1 axis, clear right away
            if self.manageX and self.fieldfilterX is not None:
                if self.sel.get_width() == 0:
                    self.sel.set_visible(False)
                    self.fieldfilterX.setActive(False)
                else:
                    self.fieldfilterX.setRange(
                        self.sel.get_x(),
                        self.sel.get_width() + self.sel.get_x())
                    self.fieldfilterX.setActive(True)
                self.selp = None  # In case we were managing two axis, clear here
            if self.manageY and self.fieldfilterY is not None:
                if self.sel.get_height() == 0:
                    self.sel.set_visible(False)
                    self.fieldfilterY.setActive(False)
                else:
                    self.fieldfilterY.setRange(
                        self.sel.get_y(),
                        self.sel.get_height() + self.sel.get_y())
                    self.fieldfilterY.setActive(True)
            self.selp = None  # Probably unneccessary but make sure this does get cleared
            if self.manageX or self.manageY:
                self.mydraw()

    def onMotion(self, event):
        handled = False
        if event.xdata and event.ydata and event.inaxes == self.plt:
            if self.pan is not None:
                handled = True
                if self.manageX:
                    xlim = self.plt.get_xlim()
                    xlim -= (event.xdata - self.pan[0])
                    self.plt.set_xlim(xlim)
                if self.manageY:
                    ylim = self.plt.get_ylim()
                    ylim -= (event.ydata - self.pan[1])
                    self.plt.set_ylim(ylim)
                if self.manageX or self.manageY:
                    self.parent().draw()
            elif self.selp is not None:
                handled = True
                if self.manageX:
                    if self.selp[0] <= event.xdata:
                        self.sel.set_width(event.xdata - self.selp[0])
                    else:
                        self.sel.set_x(event.xdata)
                        self.sel.set_width(self.selp[0] - event.xdata)
                if self.manageY:
                    if self.selp[1] <= event.ydata:
                        self.sel.set_height(event.ydata - self.selp[1])
                    else:
                        self.sel.set_y(event.ydata)
                        self.sel.set_height(self.selp[1] - event.ydata)
                if self.manageX or self.manageY:
                    self.parent().draw()

    def onFilterChange(self):
        if self.selp is not None:
            return  # Selecting is from this plot, wait for both filters to update so we don't clear variables
        ylim = self.plt.get_ylim()
        xlim = self.plt.get_xlim()
        self.sel.set_x(xlim[0])
        self.sel.set_y(ylim[0])
        self.sel.set_width(xlim[1] - xlim[0])
        self.sel.set_height(ylim[1] - ylim[0])

        if self.manageX and self.fieldfilterX is not None:
            self.sel.set_x(self.fieldfilterX.minimum)
            self.sel.set_width(self.fieldfilterX.maximum -
                               self.fieldfilterX.minimum)
            self.sel.set_visible(self.fieldfilterX.isActive())
        if self.manageY and self.fieldfilterY is not None:
            self.sel.set_y(self.fieldfilterY.minimum)
            self.sel.set_height(self.fieldfilterY.maximum -
                                self.fieldfilterY.minimum)
            self.sel.set_visible(self.fieldfilterY.isActive()
                                 or (self.fieldfilterX is not None
                                     and self.fieldfilterX.isActive()))
        self.parent().draw()

    def xlabels(self, model, field, distribute=False):
        if model.isCategorical(field):
            lbls = model.labels(field)
            ticks = model.labelints(field)
            if distribute:
                ticks = ticks.astype(float) + 0.5
            self.plt.set_xticks(ticks)
            self.plt.set_xticklabels(lbls)
        else:
            self.plt.locator_params(axis='x', nbins=model.cfg.numXticks)

    def ylabels(self, model, field, distribute=False):
        if model.isCategorical(field):
            lbls = model.labels(field)
            ticks = model.labelints(field)
            if distribute:
                ticks = ticks.astype(float) + 0.5
            self.plt.set_yticks(ticks)
            self.plt.set_yticklabels(lbls)
Esempio n. 26
0
class UserAgentMenu(QMenu):
    """
    Class implementing a menu to select the user agent string.
    """
    def __init__(self, title, url=None, parent=None):
        """
        Constructor
        
        @param title title of the menu (string)
        @param url URL to set user agent for (QUrl)
        @param parent reference to the parent widget (QWidget)
        """
        super(UserAgentMenu, self).__init__(title, parent)
        
        self.__manager = None
        self.__url = url
        if self.__url:
            if self.__url.isValid():
                import Helpviewer.HelpWindow
                self.__manager = \
                    Helpviewer.HelpWindow.HelpWindow.userAgentsManager()
            else:
                self.__url = None
        
        self.aboutToShow.connect(self.__populateMenu)
    
    def __populateMenu(self):
        """
        Private slot to populate the menu.
        """
        self.aboutToShow.disconnect(self.__populateMenu)
        
        self.__actionGroup = QActionGroup(self)
        
        # add default action
        self.__defaultUserAgent = QAction(self)
        self.__defaultUserAgent.setText(self.tr("Default"))
        self.__defaultUserAgent.setCheckable(True)
        self.__defaultUserAgent.triggered.connect(
            self.__switchToDefaultUserAgent)
        if self.__url:
            self.__defaultUserAgent.setChecked(
                self.__manager.userAgentForUrl(self.__url) == "")
        else:
            from Helpviewer.HelpBrowserWV import HelpWebPage
            self.__defaultUserAgent.setChecked(HelpWebPage().userAgent() == "")
        self.addAction(self.__defaultUserAgent)
        self.__actionGroup.addAction(self.__defaultUserAgent)
        isChecked = self.__defaultUserAgent.isChecked()
        
        # add default extra user agents
        isChecked = self.__addDefaultActions() or isChecked
        
        # add other action
        self.addSeparator()
        self.__otherUserAgent = QAction(self)
        self.__otherUserAgent.setText(self.tr("Other..."))
        self.__otherUserAgent.setCheckable(True)
        self.__otherUserAgent.triggered.connect(
            self.__switchToOtherUserAgent)
        self.addAction(self.__otherUserAgent)
        self.__actionGroup.addAction(self.__otherUserAgent)
        self.__otherUserAgent.setChecked(not isChecked)
    
    def __switchToDefaultUserAgent(self):
        """
        Private slot to set the default user agent.
        """
        if self.__url:
            self.__manager.removeUserAgent(self.__url.host())
        else:
            from Helpviewer.HelpBrowserWV import HelpWebPage
            HelpWebPage().setUserAgent("")
    
    def __switchToOtherUserAgent(self):
        """
        Private slot to set a custom user agent string.
        """
        from Helpviewer.HelpBrowserWV import HelpWebPage
        userAgent, ok = QInputDialog.getText(
            self,
            self.tr("Custom user agent"),
            self.tr("User agent:"),
            QLineEdit.Normal,
            HelpWebPage().userAgent(resolveEmpty=True))
        if ok:
            if self.__url:
                self.__manager.setUserAgentForUrl(self.__url, userAgent)
            else:
                HelpWebPage().setUserAgent(userAgent)
    
    def __changeUserAgent(self):
        """
        Private slot to change the user agent.
        """
        act = self.sender()
        if self.__url:
            self.__manager.setUserAgentForUrl(self.__url, act.data())
        else:
            from Helpviewer.HelpBrowserWV import HelpWebPage
            HelpWebPage().setUserAgent(act.data())
    
    def __addDefaultActions(self):
        """
        Private slot to add the default user agent entries.
        
        @return flag indicating that a user agent entry is checked (boolean)
        """
        from . import UserAgentDefaults_rc              # __IGNORE_WARNING__
        defaultUserAgents = QFile(":/UserAgentDefaults.xml")
        defaultUserAgents.open(QIODevice.ReadOnly)
        
        menuStack = []
        isChecked = False
        
        if self.__url:
            currentUserAgentString = self.__manager.userAgentForUrl(self.__url)
        else:
            from Helpviewer.HelpBrowserWV import HelpWebPage
            currentUserAgentString = HelpWebPage().userAgent()
        xml = QXmlStreamReader(defaultUserAgents)
        while not xml.atEnd():
            xml.readNext()
            if xml.isStartElement() and xml.name() == "separator":
                if menuStack:
                    menuStack[-1].addSeparator()
                else:
                    self.addSeparator()
                continue
            
            if xml.isStartElement() and xml.name() == "useragent":
                attributes = xml.attributes()
                title = attributes.value("description")
                userAgent = attributes.value("useragent")
                
                act = QAction(self)
                act.setText(title)
                act.setData(userAgent)
                act.setToolTip(userAgent)
                act.setCheckable(True)
                act.setChecked(userAgent == currentUserAgentString)
                act.triggered.connect(self.__changeUserAgent)
                if menuStack:
                    menuStack[-1].addAction(act)
                else:
                    self.addAction(act)
                self.__actionGroup.addAction(act)
                isChecked = isChecked or act.isChecked()
            
            if xml.isStartElement() and xml.name() == "useragentmenu":
                attributes = xml.attributes()
                title = attributes.value("title")
                if title == "v_a_r_i_o_u_s":
                    title = self.tr("Various")
                
                menu = QMenu(self)
                menu.setTitle(title)
                self.addMenu(menu)
                menuStack.append(menu)
            
            if xml.isEndElement() and xml.name() == "useragentmenu":
                menuStack.pop()
        
        if xml.hasError():
            E5MessageBox.critical(
                self,
                self.tr("Parsing default user agents"),
                self.tr(
                    """<p>Error parsing default user agents.</p><p>{0}</p>""")
                .format(xml.errorString()))
        
        return isChecked
Esempio n. 27
0
class WMain(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ctrl = QApplication.instance().ctrl
        self.ctrl.switch_language.connect(self.on_switch_language)
        self.ctrl.switch_configuration.connect(self.on_switch_configuration)
        self.window_title = (_("Metadata Publishing Tool"))
        self.paras = self.ctrl.paras
        self.config = GuiConf()
        self.init_ui()
        self.on_switch_language()
        self.on_switch_configuration()

    def init_ui(self):
        self.create_menus()
        self.tabframe = TabbedFrame(self)
        self.setCentralWidget(self.tabframe)
        self.about_widget = None
        self.resize(self.ctrl.config.window_width(),
                    self.ctrl.config.window_height())

    ####### menu bar ######################################
    def create_menus(self):
        self.menubar = self.menuBar()
        self.menubar.setNativeMenuBar(False)

        self.menu_file = QMenu(_("File"), self)
        self.menubar.addMenu(self.menu_file)

        self.menu_open_config = QMenu(_("Load configuration"), self)
        self.menu_file.addMenu(self.menu_open_config)
        self.config_action_group = QActionGroup(self)
        self.config_action_group.triggered.connect(
            self.on_action_switch_config_triggered)
        self.menu_open_config.aboutToShow.connect(
            self.on_menu_open_config_about_to_show)

        self.action_save_configuration_as = QAction(
            _("Save configuration as..."), self)
        self.menu_file.addAction(self.action_save_configuration_as)
        self.action_save_configuration_as.triggered.connect(
            self.on_action_save_configuration_as_triggered)

        self.action_configurations = QAction(_("Configurations..."), self)
        self.menu_file.addAction(self.action_configurations)
        self.action_configurations.triggered.connect(
            self.on_action_configurations_triggered)

        self.menu_file.addSeparator()
        self.action_exit = QAction(_("Exit"), self)
        self.action_exit.setShortcut("Ctrl+Q")
        self.action_exit.triggered.connect(self.on_action_exit_triggered)
        self.menu_file.addAction(self.action_exit)

        self.menu_settings = self.create_menu_settings()
        self.menubar.addMenu(self.menu_settings)

        self.menu_window = QMenu(_("Window"), self)
        self.menu_window.aboutToShow.connect(self.on_menu_window_about_to_show)
        self.menubar.addMenu(self.menu_window)
        self.window_action_group = QActionGroup(self)
        self.window_action_group.triggered.connect(
            self.on_action_switch_window_triggered)
        self.action_about = QAction(_("About..."), self)
        self.action_about.triggered.connect(self.on_action_about_triggered)

    ####### menus ######################################
    def create_menu_settings(self):
        menu_settings = QMenu(_("Preferences"), self)
        self.menu_language = self.create_menu_language()
        menu_settings.addMenu(self.menu_language)

        return menu_settings

    def create_menu_language(self):
        language_menu = QMenu(_("Language"), self)
        action_group = QActionGroup(self)
        action_group.triggered.connect(
            self.on_action_language_switch_triggered)
        iso_idx = self.ctrl.iso_lang()
        current_locale = self.ctrl.current_language()

        for locale in self.ctrl.locales():
            short_locale = locale.split("-")[0]
            if short_locale in iso_idx:
                iso = iso_idx[short_locale]
            else:
                iso = {'nativeName': '-', 'name': '-'}
            action = QAction(
                "%s - %s  [%s]" % (iso["nativeName"], iso["name"], locale),
                self)
            action.setCheckable(True)
            action.setData(locale)
            language_menu.addAction(action)
            action_group.addAction(action)
            if locale == current_locale:
                action.setChecked(True)
        return language_menu

    def on_menu_window_about_to_show(self):
        self.menu_window.clear()
        for child in self.window_action_group.children():
            self.window_action_group.removeAction(child)
        for window in QApplication.instance().topLevelWindows():
            if window.type() == 1 and not window.title() == self.windowTitle():
                action = QAction(window.title(), self)
                action.setData(window.title())
                action.setCheckable(True)
                self.window_action_group.addAction(action)
                self.menu_window.addAction(action)

        self.menu_window.addSeparator()
        self.menu_window.addAction(self.action_about)

    ####### functions #######################################
    def on_switch_language(self, code=None):
        LOG.debug("Switch language: %s" % code)
        self.menu_file.setTitle(_("File"))
        self.action_exit.setText(_("Exit"))
        self.action_exit.setStatusTip(_("Exit application"))
        self.menu_open_config.setTitle(_("Load configuration"))
        self.action_save_configuration_as.setText(
            _("Save configuration as..."))
        self.action_configurations.setText(_("Configurations..."))
        self.action_about.setText(_("About..."))
        self.menu_settings.setTitle(_("Preferences"))
        self.menu_language.setTitle(_("Language"))
        self.menu_window.setTitle(_("Window"))
        self.window_title = (_("Metadata Publishing Tool"))
        self.setWindowTitle(self.window_title + " [" +
                            self.paras.configuration_name() + "]")

    def on_switch_configuration(self, name=None):
        LOG.debug("Switch configuration: %s" % name)
        self.paras = self.ctrl.paras
        self.setWindowTitle(self.window_title + " [" +
                            self.paras.configuration_name() + "]")

    def on_menu_open_config_about_to_show(self):
        self.menu_open_config.clear()
        for child in self.config_action_group.children():
            self.config_action_group.removeAction(child)
        current_name = Configurations.current_configuration_name()
        for name in Configurations.list_configurations():
            action = QAction(name, self)
            action.setCheckable(True)
            action.setData(name)
            self.config_action_group.addAction(action)
            self.menu_open_config.addAction(action)
            if name == current_name:
                action.setChecked(True)

    def on_action_switch_config_triggered(self):
        action_group = self.sender()
        name = action_group.checkedAction().data()
        if self.tabframe.about_to_change(_("Switch configuration...")):
            self.ctrl.load_configuration(name)

    def on_action_save_configuration_as_triggered(self):
        text = _("Save configuration as...")
        if self.tabframe.about_to_change(text):
            dlg = QInputDialog(self)
            dlg.setInputMode(QInputDialog.TextInput)
            dlg.setWindowTitle(text)
            dlg.setLabelText(_("Configuration name:"))
            dlg.setTextValue(self.paras.configuration_name())
            dlg.resize(300, 100)
            if dlg.exec_():
                self.ctrl.save_configuration_as(dlg.textValue())

    def on_action_configurations_triggered(self):
        ConfigurationsDialog(self).exec_()

    def on_action_language_switch_triggered(self):
        action_group = self.sender()
        locale = action_group.checkedAction().data()
        self.ctrl.set_language(locale)

    def on_action_switch_window_triggered(self):
        action_group = self.sender()
        window_title = action_group.checkedAction().data()
        for window in QApplication.instance().topLevelWindows():
            if window_title == window.title():
                window.show()
                window.raise_()
                window.requestActivate()

    def on_action_about_triggered(self):
        if self.about_widget is None:
            LOG.debug("Creating About window")
            self.about_widget = AboutWidget(self)
        else:
            self.about_widget.show()
            self.about_widget.setWindowState(Qt.WindowActive)
            self.about_widget.activateWindow()
            self.about_widget.raise_()

    def on_action_exit_triggered(self):
        LOG.debug("action_exit_triggered")
        if self.tabframe.about_to_change(_("Closing application...")):
            qApp.quit()

    def closeEvent(self, event):
        LOG.debug("closeEvent was triggered")
        if self.tabframe.about_to_change(_("Closing application...")):
            LOG.debug("Accepting closeEvent")
            event.accept()
        else:
            LOG.debug("Ignoring closeEvent")
            event.ignore()

    def close(self):
        LOG.debug("window closing")
        self.ctrl.config.set_window_height(self.height())
        self.ctrl.config.set_window_width(self.width())
        self.ctrl.config.set_last_configuration_name(
            self.paras.configuration_name())
        self.ctrl.config.persist()
        self.tabframe.close()
        if self.about_widget:
            self.about_widget.close()
Esempio n. 28
0
def main():
    exitcode = 0
    try:
        menu = QMenu()
        # Proxy
        m_proxy = QAction("Proxy: Disabled")
        m_proxy.setShortcut('Ctrl+P')
        m_proxy.setCheckable(True)
        m_proxy.setDisabled(True)
        m_proxy.triggered.connect(lambda: current['proxy'].disable(m_proxy))
        menu.addAction(m_proxy)
        proxy_dict = {}
        proxy_group = QActionGroup(menu)
        for proxy in profile.get_items('Proxy'):
            proxyname = proxy[0]
            proxy_dict[proxyname] = Proxy(proxy, m_proxy)
            proxy_group.addAction(proxy_dict[proxyname].QAction)
            menu.addAction(proxy_dict[proxyname].QAction)

        # Bypass
        menu.addSeparator()
        m_bypass = QAction("Bypass: Disabled")
        m_bypass.setShortcut('Ctrl+B')
        m_bypass.setCheckable(True)
        m_bypass.setDisabled(True)
        m_bypass.triggered.connect(lambda: current['bypass'].disable(m_bypass))
        menu.addAction(m_bypass)
        bypass_dict = {}
        bypass_group = QActionGroup(menu)
        for bypass in profile.get_items('Bypass'):
            bypassname = bypass[0]
            bypass_dict[bypassname] = Bypass(bypass, m_bypass)
            bypass_group.addAction(bypass_dict[bypassname].QAction)
            menu.addAction(bypass_dict[bypassname].QAction)

        # Capture
        menu.addSeparator()
        m_capture = QAction("Capture: Disabled")
        m_capture.setShortcut('Ctrl+C')
        m_capture.setCheckable(True)
        m_capture.setDisabled(True)
        m_dashboard = QAction("Open Dashboard...")
        m_dashboard.setShortcut('Ctrl+D')
        m_dashboard.setDisabled(True)
        m_capture.triggered.connect(
            lambda: current['capture'].disable(m_capture, m_dashboard))
        menu.addAction(m_capture)
        capture_dict = {}
        capture_group = QActionGroup(menu)
        for capture in profile.get_items('Capture'):
            capturename = capture[0]
            capture_dict[capturename] = Capture(capture, m_capture,
                                                m_dashboard)
            capture_group.addAction(capture_dict[capturename].QAction)
            menu.addAction(capture_dict[capturename].QAction)
        menu.addAction(m_dashboard)

        # Common
        m_log = QAction("Log Folder")
        m_log.setShortcut('Ctrl+L')
        m_log.triggered.connect(lambda: subprocess.call(["open", log_path]))
        m_profile = QAction("Profile Folder")
        m_profile.setShortcut('Ctrl+F')
        m_profile.triggered.connect(
            lambda: subprocess.call(["open", profile_path]))
        m_extension = QAction("Extension Folder")
        m_extension.setShortcut('Ctrl+E')
        m_extension.triggered.connect(
            lambda: subprocess.call(["open", ext_path]))
        m_copy_shell = QAction("Copy Shell Command")
        m_copy_shell.setShortcut('Ctrl+S')
        m_set_system = QAction("As System Proxy: " + user_port)
        m_set_system.setShortcut('Ctrl+A')
        m_set_system.triggered.connect(lambda: setproxy_menu(m_set_system))
        m_copy_shell.triggered.connect(copy_shell)
        m_set_system.setCheckable(True)
        if system:
            m_set_system.setChecked(True)
            setproxy()
        menu.addSeparator()
        menu.addAction(m_set_system)
        menu.addSeparator()
        menu.addAction(m_log)
        menu.addAction(m_profile)
        menu.addAction(m_extension)
        menu.addAction(m_copy_shell)
        menu.addSeparator()
        m_quit = QAction('Quit V2Net (' + version + ')')
        m_quit.setShortcut('Ctrl+Q')
        m_quit.triggered.connect(APP.quit)
        menu.addAction(m_quit)

        # Add Tray
        tray = QSystemTrayIcon()
        tray.setIcon(QIcon("icon.png"))
        tray.setVisible(True)
        tray.setContextMenu(menu)
        # sys.exit(app.exec_())
        exitcode = APP.exec_()
    finally:
        quitapp(exitcode)
Esempio n. 29
0
class Gui(QMainWindow, Ui_MainWindow):
    NOTEON = 0x9
    NOTEOFF = 0x8
    MIDICTRL = 11

    GREEN = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(125,242,0);}")
    BLUE = ("#cell_frame { border: 0px; border-radius: 10px; "
            "background-color: rgb(0, 130, 240);}")
    RED = ("#cell_frame { border: 0px; border-radius: 10px; "
           "background-color: rgb(255, 21, 65);}")
    AMBER = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(255, 102, 0);}")
    PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; "
              "background-color: rgb(130, 0, 240);}")
    DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; "
               "background-color: rgb(217, 217, 217);}")

    RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}"
                    "QPushButton:pressed {background-color: "
                    "rgb(98, 98, 98);}")

    RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}"
                      "QPushButton:pressed {background-color: "
                      "rgb(98, 98, 98);}")

    STATE_COLORS = {Clip.STOP: RED,
                    Clip.STARTING: GREEN,
                    Clip.START: GREEN,
                    Clip.STOPPING: RED,
                    Clip.PREPARE_RECORD: AMBER,
                    Clip.RECORDING: AMBER}
    STATE_BLINK = {Clip.STOP: False,
                   Clip.STARTING: True,
                   Clip.START: False,
                   Clip.STOPPING: True,
                   Clip.PREPARE_RECORD: True,
                   Clip.RECORDING: False}

    BLINK_DURATION = 200
    PROGRESS_PERIOD = 300

    ADD_PORT_LABEL = 'Add new Port...'

    updateUi = pyqtSignal()
    readQueueIn = pyqtSignal()
    updatePorts = pyqtSignal()
    songLoad = pyqtSignal()

    def __init__(self, song, jack_client):
        QObject.__init__(self)
        super(Gui, self).__init__()
        self._jack_client = jack_client
        self.setupUi(self)
        self.clip_volume.knobRadius = 3
        self.is_learn_device_mode = False
        self.queue_out, self.queue_in = Queue(), Queue()
        self.updateUi.connect(self.update)
        self.readQueueIn.connect(self.readQueue)
        self.current_vol_block = 0
        self.last_clip = None

        # Load devices
        self.deviceGroup = QActionGroup(self.menuDevice)
        self.devices = []
        device_settings = QSettings('superboucle', 'devices')
        if ((device_settings.contains('devices')
             and device_settings.value('devices'))):
            for raw_device in device_settings.value('devices'):
                self.devices.append(Device(pickle.loads(raw_device)))
        else:
            self.devices.append(Device({'name': 'No Device',}))
        self.updateDevices()
        self.deviceGroup.triggered.connect(self.onDeviceSelect)

        self.settings = QSettings('superboucle', 'session')
        # Qsetting appear to serialize empty lists as @QInvalid
        # which is then read as None :(

        # Load playlist
        self.playlist = self.settings.value('playlist', []) or []
        # Load paths
        self.paths_used = self.settings.value('paths_used', {})

        self.auto_connect = self.settings.value('auto_connect',
                                                'true') == "true"

        # Load song
        self.port_by_name = {}
        self.initUI(song)

        self.actionNew.triggered.connect(self.onActionNew)
        self.actionOpen.triggered.connect(self.onActionOpen)
        self.actionSave.triggered.connect(self.onActionSave)
        self.actionSave_As.triggered.connect(self.onActionSaveAs)
        self.actionAdd_Device.triggered.connect(self.onAddDevice)
        self.actionManage_Devices.triggered.connect(self.onManageDevice)
        self.actionPlaylist_Editor.triggered.connect(self.onPlaylistEditor)
        self.actionScene_Manager.triggered.connect(self.onSceneManager)
        self.actionPort_Manager.triggered.connect(self.onPortManager)
        self.actionFullScreen.triggered.connect(self.onActionFullScreen)
        self.master_volume.valueChanged.connect(self.onMasterVolumeChange)
        self.bpm.valueChanged.connect(self.onBpmChange)
        self.beat_per_bar.valueChanged.connect(self.onBeatPerBarChange)
        self.rewindButton.clicked.connect(self.onRewindClicked)
        self.playButton.clicked.connect(self._jack_client.transport_start)
        self.pauseButton.clicked.connect(self._jack_client.transport_stop)
        self.gotoButton.clicked.connect(self.onGotoClicked)
        self.recordButton.clicked.connect(self.onRecord)
        self.clip_name.textChanged.connect(self.onClipNameChange)
        self.clip_volume.valueChanged.connect(self.onClipVolumeChange)
        self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange)
        self.output.activated.connect(self.onOutputChange)
        self.mute_group.valueChanged.connect(self.onMuteGroupChange)
        self.frame_offset.valueChanged.connect(self.onFrameOffsetChange)
        self.beat_offset.valueChanged.connect(self.onBeatOffsetChange)
        self.revertButton.clicked.connect(self.onRevertClip)
        self.normalizeButton.clicked.connect(self.onNormalizeClip)
        self.exportButton.clicked.connect(self.onExportClip)
        self.deleteButton.clicked.connect(self.onDeleteClipClicked)

        self.blktimer = QTimer()
        self.blktimer.state = False
        self.blktimer.timeout.connect(self.toggleBlinkButton)
        self.blktimer.start(self.BLINK_DURATION)

        self.disptimer = QTimer()
        self.disptimer.start(self.PROGRESS_PERIOD)
        self.disptimer.timeout.connect(self.updateProgress)

        self._jack_client.set_timebase_callback(self.timebase_callback)
        self.show()

    def initUI(self, song):

        # remove old buttons
        self.btn_matrix = [[None for y in range(song.height)]
                           for x in range(song.width)]
        self.state_matrix = [[-1 for y in range(song.height)]
                             for x in range(song.width)]

        for i in reversed(range(self.gridLayout.count())):
            self.gridLayout.itemAt(i).widget().close()
            self.gridLayout.itemAt(i).widget().setParent(None)

        # first pass without removing old ports
        self.updateJackPorts(song, remove_ports=False)
        self.song = song
        # second pass with removing
        self.updateJackPorts(song, remove_ports=True)

        self.frame_clip.setEnabled(False)
        self.output.clear()
        self.output.addItems(song.outputsPorts)
        self.output.addItem(Gui.ADD_PORT_LABEL)
        self.master_volume.setValue(song.volume * 256)
        self.bpm.setValue(song.bpm)
        self.beat_per_bar.setValue(song.beat_per_bar)
        for x in range(song.width):
            for y in range(song.height):
                clip = song.clips_matrix[x][y]
                cell = Cell(self, clip, x, y)
                self.btn_matrix[x][y] = cell
                self.gridLayout.addWidget(cell, y, x)

        # send init command
        for init_cmd in self.device.init_command:
            self.queue_out.put(init_cmd)

        self.setWindowTitle("Super Boucle - {}"
                            .format(song.file_name or "Empty Song"))

        if self.song.initial_scene in self.song.scenes:
            self.song.loadScene(self.song.initial_scene)
        self.update()
        self.songLoad.emit()

        timer = QTimer()
        timer.singleShot(1000,self.send_clip_state_feedback)

    def openSongFromDisk(self, file_name):
        self._jack_client.transport_stop()
        self._jack_client.transport_locate(0)

        self.setEnabled(False)
        message = QMessageBox(self)
        message.setWindowTitle("Loading ....")
        message.setText("Reading Files, please wait ...")
        message.show()
        self.initUI(load_song_from_file(file_name))
        message.close()
        self.setEnabled(True)

    def closeEvent(self, event):
        device_settings = QSettings('superboucle', 'devices')
        device_settings.setValue('devices',
                                 [pickle.dumps(x.mapping)
                                  for x in self.devices])
        self.settings.setValue('playlist', self.playlist)
        self.settings.setValue('paths_used', self.paths_used)
        self.settings.setValue('auto_connect', self.auto_connect)

    def onStartStopClicked(self):
        clip = self.sender().parent().parent().clip
        self.startStop(clip.x, clip.y)

    def startStop(self, x, y):
        clip = self.btn_matrix[x][y].clip
        if clip is None:
            return
        if self.song.is_record:
            self.song.is_record = False
            self.updateRecordBtn()
            # calculate buffer size
            state, position = self._jack_client.transport_query()
            bps = position['beats_per_minute'] / 60
            fps = position['frame_rate']
            size = (1 / bps) * clip.beat_diviser * fps
            self.song.init_record_buffer(clip, 2, size, fps)
            # set frame offset based on jack block size
            clip.frame_offset = self._jack_client.blocksize
            clip.state = Clip.PREPARE_RECORD
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        else:
            self.song.toggle(clip.x, clip.y)
        self.update()

    def onEdit(self):
        self.last_clip = self.sender().parent().parent().clip
        if self.last_clip:
            self.frame_clip.setEnabled(True)
            self.clip_name.setText(self.last_clip.name)
            self.frame_offset.setValue(self.last_clip.frame_offset)
            self.beat_offset.setValue(self.last_clip.beat_offset)
            self.beat_diviser.setValue(self.last_clip.beat_diviser)
            self.output.setCurrentText(self.last_clip.output)
            self.mute_group.setValue(self.last_clip.mute_group)
            self.clip_volume.setValue(self.last_clip.volume * 256)
            state, position = self._jack_client.transport_query()
            fps = position['frame_rate']
            bps = self.bpm.value() / 60
            if self.bpm.value() and fps:
                size_in_beat = (bps / fps) * self.song.length(self.last_clip)
            else:
                size_in_beat = "No BPM info"
            clip_description = ("Size in sample : %s\nSize in beat : %s"
                                % (self.song.length(self.last_clip),
                                   round(size_in_beat, 1)))

            self.clip_description.setText(clip_description)

    def onAddClipClicked(self):
        cell = self.sender().parent().parent()
        if QApplication.keyboardModifiers() == Qt.ControlModifier:
            cell.setClip(cell.openClip())
        else:
            AddClipDialog(self, cell)

    def onRevertClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            self.song.data[audio_file] = self.song.data[audio_file][::-1]

    def onNormalizeClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            absolute_val = np.absolute(self.song.data[audio_file])
            current_level = np.ndarray.max(absolute_val)
            self.song.data[audio_file][:] *= (1 / current_level)

    def onExportClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            file_name, a = self.getSaveFileName(
                'Export Clip : %s' % self.last_clip.name, 'WAVE (*.wav)')

            if file_name:
                file_name = verify_ext(file_name, 'wav')
                sf.write(self.song.data[audio_file], file_name,
                         self.song.samplerate[audio_file],
                         subtype=sf.default_subtype('WAV'),
                         format='WAV')

    def onDeleteClipClicked(self):
        if self.last_clip:
            response = QMessageBox.question(self,
                                            "Delete Clip ?",
                                            ("Are you sure "
                                             "to delete the clip ?"))
            if response == QMessageBox.Yes:
                self.frame_clip.setEnabled(False)
                self.song.removeClip(self.last_clip)
                self.initUI(self.song)

    def onMasterVolumeChange(self):
        self.song.volume = (self.master_volume.value() / 256)

    def onBpmChange(self):
        self.song.bpm = self.bpm.value()

    def onBeatPerBarChange(self):
        self.song.beat_per_bar = self.beat_per_bar.value()

    def onGotoClicked(self):
        state, position = self._jack_client.transport_query()
        new_position = (position['beats_per_bar']
                        * (self.gotoTarget.value() - 1)
                        * position['frame_rate']
                        * (60 / position['beats_per_minute']))
        self._jack_client.transport_locate(int(round(new_position, 0)))

    def onRecord(self):
        self.song.is_record = not self.song.is_record
        self.updateRecordBtn()

    def updateRecordBtn(self):
        if not self.song.is_record:
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        if self.device.record_btn:
            (msg_type, channel, pitch, velocity) = self.device.record_btn
            if self.song.is_record:
                color = self.device.blink_amber_vel
            else:
                color = self.device.black_vel
            self.queue_out.put(((msg_type << 4) + channel, pitch, color))

    def onRewindClicked(self):
        self._jack_client.transport_locate(0)

    def onClipNameChange(self):
        self.last_clip.name = self.clip_name.text()
        cell = self.btn_matrix[self.last_clip.x][self.last_clip.y]
        cell.clip_name.setText(self.last_clip.name)

    def onClipVolumeChange(self):
        self.last_clip.volume = (self.clip_volume.value() / 256)

    def onBeatDiviserChange(self):
        self.last_clip.beat_diviser = self.beat_diviser.value()

    def onOutputChange(self):
        new_port = self.output.currentText()
        if new_port == Gui.ADD_PORT_LABEL:
            AddPortDialog(self)
        else:
            self.last_clip.output = new_port

    def addPort(self, name):
        self.song.outputsPorts.add(name)
        self.updateJackPorts(self.song)
        if self.output.findText(name) == -1:
            self.output.insertItem(self.output.count() - 1, name)
        if self.last_clip:
            self.last_clip.output = name
            self.output.setCurrentText(name)

    def removePort(self, name):
        if name != Clip.DEFAULT_OUTPUT:
            self.song.outputsPorts.remove(name)
            for c in self.song.clips:
                if c.output == name:
                    c.output = Clip.DEFAULT_OUTPUT
            self.updateJackPorts(self.song)
            self.output.removeItem(self.output.findText(name))
            if self.last_clip:
                self.output.setCurrentText(self.last_clip.output)

    def updateJackPorts(self, song, remove_ports=True):
        '''Update jack port based on clip output settings
        update dict containing ports with shortname as key'''

        current_ports = set()
        for port in self._jack_client.outports:
            current_ports.add(port.shortname)

        wanted_ports = set()
        for port_basename in song.outputsPorts:
            for ch in Song.CHANNEL_NAMES:
                port = Song.CHANNEL_NAME_PATTERN.format(port=port_basename,
                                                        channel=ch)
                wanted_ports.add(port)

        # remove unwanted ports
        if remove_ports:
            port_to_remove = []
            for port in self._jack_client.outports:
                if port.shortname not in wanted_ports:
                    current_ports.remove(port.shortname)
                    port_to_remove.append(port)
            for port in port_to_remove:
                port.unregister()

        # create new ports
        for new_port_name in wanted_ports - current_ports:
            self._jack_client.outports.register(new_port_name)

        self.port_by_name = {port.shortname: port
                             for port in self._jack_client.outports}

        self.updatePorts.emit()

    def onMuteGroupChange(self):
        self.last_clip.mute_group = self.mute_group.value()

    def onFrameOffsetChange(self):
        self.last_clip.frame_offset = self.frame_offset.value()

    def onBeatOffsetChange(self):
        self.last_clip.beat_offset = self.beat_offset.value()

    def onActionNew(self):
        NewSongDialog(self)

    def getOpenFileName(self, title, file_type, parent=None,
                        dialog=QFileDialog.getOpenFileName):
        path = self.paths_used.get(file_type, expanduser('~'))
        file_name, a = dialog(parent or self, title, path, file_type)
        if a and file_name:
            if isinstance(file_name, list):
                self.paths_used[file_type] = dirname(file_name[0])
            else:
                self.paths_used[file_type] = dirname(file_name)
        return file_name, a

    def getSaveFileName(self, *args):
        return self.getOpenFileName(*args, dialog=QFileDialog.getSaveFileName)

    def onActionOpen(self):
        file_name, a = self.getOpenFileName('Open Song',
                                            'Super Boucle Song (*.sbs)')
        if a and file_name:
            self.openSongFromDisk(file_name)

    def onActionSave(self):
        if self.song.file_name:
            self.song.save()
        else:
            self.onActionSaveAs()

    def onActionSaveAs(self):
        file_name, a = self.getSaveFileName('Save Song',
                                            'Super Boucle Song (*.sbs)')

        if file_name:
            file_name = verify_ext(file_name, 'sbs')
            self.song.file_name = file_name
            self.song.save()
            print("File saved to : {}".format(self.song.file_name))

    def onAddDevice(self):
        self.learn_device = LearnDialog(self, self.addDevice)
        self.is_learn_device_mode = True

    def onManageDevice(self):
        ManageDialog(self)

    def onPlaylistEditor(self):
        PlaylistDialog(self)

    def onSceneManager(self):
        SceneManager(self)

    def onPortManager(self):
        PortManager(self)

    def onActionFullScreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()
        self.show()

    def send_clip_state_feedback(self):
        for x in range(self.song.width):
            for y in range(self.song.height):
                clip = self.song.clips_matrix[x][y]
                state = clip.state if clip else None
                self._update_clip_state(x, y, state)

    def _update_clip_state(self, x, y, state):
        clip = self.song.clips_matrix[x][y]
        if clip:
            self.btn_matrix[x][y].setColor(state)
        try:
            self.queue_out.put(self.device.generateNote(x, y, state))
        except IndexError:
            # print("No cell associated to %s x %s"
            # % (clp.x, clp.y))
            pass
        self.state_matrix[x][y] = state

    def update(self):
        for x in range(len(self.song.clips_matrix)):
            line = self.song.clips_matrix[x]
            for y in range(len(line)):
                clp = line[y]
                if clp is None:
                    state = None
                else:
                    state = clp.state
                if state != self.state_matrix[x][y]:
                    self._update_clip_state(x, y, state)

    def redraw(self):
        self.state_matrix = [[-1 for x in range(self.song.height)]
                             for x in range(self.song.width)]
        self.update()

    def readQueue(self):
        try:
            while True:
                note = self.queue_in.get(block=False)
                if len(note) == 3:
                    status, pitch, vel = struct.unpack('3B', note)
                    channel = status & 0xF
                    msg_type = status >> 4
                    self.processNote(msg_type, channel, pitch, vel)
                    # else:
                    # print("Invalid message length")
        except Empty:
            pass

    def processNote(self, msg_type, channel, pitch, vel):

        btn_id = (msg_type,
                  channel,
                  pitch,
                  vel)
        btn_id_vel = (msg_type, channel, pitch, -1)
        ctrl_key = (msg_type, channel, pitch)

        # master volume
        if ctrl_key == self.device.master_volume_ctrl:
            self.song.master_volume = vel / 127
            (self.master_volume
             .setValue(self.song.master_volume * 256))
        elif self.device.play_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_start()
        elif self.device.pause_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_stop()
        elif self.device.rewind_btn in [btn_id, btn_id_vel]:
            self.onRewindClicked()
        elif self.device.goto_btn in [btn_id, btn_id_vel]:
            self.onGotoClicked()
        elif self.device.record_btn in [btn_id, btn_id_vel]:
            self.onRecord()
        elif ctrl_key in self.device.ctrls:
            try:
                ctrl_index = self.device.ctrls.index(ctrl_key)
                clip = (self.song.clips_matrix
                        [ctrl_index]
                        [self.current_vol_block])
                if clip:
                    clip.volume = vel / 127
                    if self.last_clip == clip:
                        self.clip_volume.setValue(self.last_clip.volume * 256)
            except KeyError:
                pass
        elif (btn_id in self.device.scene_buttons
              or btn_id_vel in self.device.scene_buttons):
            try:
                scene_id = self.device.scene_buttons.index(btn_id)
            except ValueError:
                scene_id = self.device.scene_buttons.index(btn_id_vel)

            try:
                self.song.loadSceneId(scene_id)
                self.update()
            except IndexError:
                print('cannot load scene {} - there are only {} scenes.'
                      ''.format(scene_id, len(self.song.scenes)))

        elif (btn_id in self.device.block_buttons
              or btn_id_vel in self.device.block_buttons):
            try:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id))
            except ValueError:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id_vel))
            for i in range(len(self.device.block_buttons)):
                (a, b_channel, b_pitch, b) = self.device.block_buttons[i]
                if i == self.current_vol_block:
                    color = self.device.red_vel
                else:
                    color = self.device.black_vel
                self.queue_out.put(((self.NOTEON << 4) + b_channel,
                                    b_pitch,
                                    color))
        else:
            x, y = -1, -1
            try:
                x, y = self.device.getXY(btn_id)
            except IndexError:
                pass
            except KeyError:
                try:
                    x, y = self.device.getXY(btn_id_vel)
                except KeyError:
                    pass

            if (x >= 0 and y >= 0):
                self.startStop(x, y)

    def toggleBlinkButton(self):
        for line in self.btn_matrix:
            for btn in line:
                if btn.blink:
                    if self.blktimer.state:
                        btn.setStyleSheet(btn.color)
                    else:
                        btn.setStyleSheet(self.DEFAULT)
        if self.song.is_record:
            if self.blktimer.state:
                self.recordButton.setStyleSheet(self.RECORD_BLINK)
            else:
                self.recordButton.setStyleSheet(self.RECORD_DEFAULT)

        self.blktimer.state = not self.blktimer.state

    def updateProgress(self):
        state, pos = self._jack_client.transport_query()
        if 'bar' in pos:
            bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick'])
        else:
            bbt = "-|-|-"
        seconds = int(pos['frame'] / pos['frame_rate'])
        (minutes, second) = divmod(seconds, 60)
        (hour, minute) = divmod(minutes, 60)
        time = "%d:%02d:%02d" % (hour, minute, second)
        self.bbtLabel.setText("%s\n%s" % (bbt, time))
        for line in self.btn_matrix:
            for btn in line:
                if btn.clip and btn.clip.audio_file:
                    value = ((btn.clip.last_offset
                              / self.song.length(btn.clip))
                             * 97)
                    btn.clip_position.setValue(value)
                    btn.clip_position.repaint()

    def updateDevices(self):
        for action in self.deviceGroup.actions():
            self.deviceGroup.removeAction(action)
            self.menuDevice.removeAction(action)
        for device in self.devices:
            action = QAction(device.name, self.menuDevice)
            action.setCheckable(True)
            action.setData(device)
            self.menuDevice.addAction(action)
            self.deviceGroup.addAction(action)
        action.setChecked(True)
        self.device = device

    def addDevice(self, device):
        self.devices.append(device)
        self.updateDevices()
        self.is_learn_device_mode = False

    def onDeviceSelect(self):
        self.device = self.deviceGroup.checkedAction().data()
        if self.device:
            if self.device.init_command:
                for note in self.device.init_command:
                    self.queue_out.put(note)
            self.redraw()

    def timebase_callback(self, state, nframes, pos, new_pos):
        if pos.frame_rate == 0:
            return None
        pos.valid = 0x10
        pos.bar_start_tick = BAR_START_TICK
        pos.beats_per_bar = self.beat_per_bar.value()
        pos.beat_type = BEAT_TYPE
        pos.ticks_per_beat = TICKS_PER_BEAT
        pos.beats_per_minute = self.bpm.value()
        ticks_per_second = (pos.beats_per_minute *
                            pos.ticks_per_beat) / 60
        ticks = (ticks_per_second * pos.frame) / pos.frame_rate
        (beats, pos.tick) = divmod(int(round(ticks, 0)),
                                   int(round(pos.ticks_per_beat, 0)))
        (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0)))
        (pos.bar, pos.beat) = (bar + 1, beat + 1)
        return None
Esempio n. 30
0
class FrameZoomMenu(QMenu):
    def __init__(self, main_win, zoom_ctrl_mngr, listplayer, media_player):
        super().__init__(parent=main_win)
        self.main_win = main_win
        self.zoom_ctrl_mngr = zoom_ctrl_mngr
        self.listplayer = listplayer
        self.mp = media_player

        self.setTitle("Zoom")
        self.setIcon(icons.get("zoom_menu_button"))

        self.quarter = QAction("1:4 Quarter", self)
        self.quarter.triggered.connect(
            lambda: self.zoom_ctrl_mngr.set_scale(0.25))
        self.quarter.setCheckable(True)

        self.half = QAction("1:2 Half", self)
        self.half.triggered.connect(lambda: self.zoom_ctrl_mngr.set_scale(0.5))
        self.half.setCheckable(True)

        self.original = QAction("1:1 Original", self)
        self.original.triggered.connect(
            lambda: self.zoom_ctrl_mngr.set_scale(1))
        self.original.setCheckable(True)

        self.double = QAction("1:2 Double", self)
        self.double.triggered.connect(lambda: self.zoom_ctrl_mngr.set_scale(2))
        self.double.setCheckable(True)

        self.explicit_zooms = QActionGroup(self)
        self.explicit_zooms.addAction(self.quarter)
        self.explicit_zooms.addAction(self.half)
        self.explicit_zooms.addAction(self.original)
        self.explicit_zooms.addAction(self.double)

        self.addActions(self.explicit_zooms.actions())

        self.option_action_map = {
            0.25: self.quarter,
            0.5: self.half,
            1: self.original,
            2: self.double,
        }

        self.zoom_in = ZoomInAction(self, zoom_ctrl_mngr=self.zoom_ctrl_mngr)
        self.zoom_in.setIcon(icons.get("zoom_in_menu_item"))
        self.addAction(self.zoom_in)

        self.zoom_out = ZoomOutAction(self, zoom_ctrl_mngr=self.zoom_ctrl_mngr)
        self.zoom_out.setIcon(icons.get("zoom_out_menu_item"))
        self.addAction(self.zoom_out)

        self.addSeparator()
        self.addAction(AutoResizeAction(self))

        self.conform_to_media()

        self.zoom_ctrl_mngr.zoomchanged.connect(self.on_zoomchanged)
        self.listplayer.mediachanged.connect(self.on_mediachanged)

    def on_zoomchanged(self, value):
        self.option_action_map[value].setChecked(True)

    def on_mediachanged(self):
        self.conform_to_media()

    def conform_to_media(self):
        self.option_action_map[config.state.view_scale].setChecked(True)

    def enable_zoom_actions(self):
        """Enable zoom actions if media is loaded and disable them if not."""
        has_media = self.mp.has_media()
        for a in self.actions():
            a.setEnabled(has_media)
Esempio n. 31
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.initConfig()
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.editBoxes = []
		self.previewBoxes = []
		self.highlighters = []
		self.markups = []
		self.fileNames = []
		self.actionPreviewChecked = []
		self.actionLivePreviewChecked = []
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh', trig=self.openFileMain)
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeFont = self.act(self.tr('Change default font'), trig=self.changeFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.editBoxes[self.ind].enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.editBoxes[self.ind].undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.editBoxes[self.ind].redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.editBoxes[self.ind].copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.editBoxes[self.ind].cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.editBoxes[self.ind].paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About %s', 'Example of final string: About ReText')
		self.aboutWindowTitle = self.aboutWindowTitle % app_name
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertChars('**'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertChars('*'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertTag('u'))
		self.usefulTags = ('a', 'big', 'center', 'img', 's', 'small', 'span',
			'table', 'td', 'tr', 'u')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.tagsBox = QComboBox(self.editBar)
		self.tagsBox.addItem(self.tr('Tags'))
		self.tagsBox.addItems(self.usefulTags)
		self.tagsBox.activated.connect(self.insertTag)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = QMenuBar(self)
		menubar.setGeometry(QRect(0, 0, 800, 25))
		self.setMenuBar(menubar)
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addMenu(self.menuRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionLivePreview)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		menubar.addMenu(menuFile)
		menubar.addMenu(menuEdit)
		menubar.addMenu(menuHelp)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.tagsBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
				self.enableSpellCheck(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def initConfig(self):
		self.font = None
		if globalSettings.font:
			self.font = QFont(globalSettings.font)
		if self.font and globalSettings.fontSize:
			self.font.setPointSize(globalSettings.fontSize)

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			self.ss = ''

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def getSplitter(self, index):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBoxes[index], self.previewBoxes[index]:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter

	def getWebView(self):
		webView = QWebView()
		if not globalSettings.handleWebLinks:
			webView.page().setLinkDelegationPolicy(QWebPage.DelegateExternalLinks)
			webView.page().linkClicked.connect(QDesktopServices.openUrl)
		webView.settings().setAttribute(QWebSettings.LocalContentCanAccessFileUrls, False)
		return webView

	def createTab(self, fileName):
		self.previewBlocked = False
		self.editBoxes.append(ReTextEdit(self))
		self.highlighters.append(ReTextHighlighter(self.editBoxes[-1].document()))
		if enchant_available and self.actionEnableSC.isChecked():
			self.highlighters[-1].dictionary = \
			enchant.Dict(self.sl) if self.sl else enchant.Dict()
			self.highlighters[-1].rehighlight()
		if globalSettings.useWebKit:
			self.previewBoxes.append(self.getWebView())
		else:
			self.previewBoxes.append(QTextBrowser())
			self.previewBoxes[-1].setOpenExternalLinks(True)
		self.previewBoxes[-1].setVisible(False)
		self.fileNames.append(fileName)
		markupClass = self.getMarkupClass(fileName)
		self.markups.append(self.getMarkup(fileName))
		self.highlighters[-1].docType = (markupClass.name if markupClass else '')
		liveMode = globalSettings.restorePreviewState and globalSettings.previewState
		self.actionPreviewChecked.append(liveMode)
		self.actionLivePreviewChecked.append(liveMode)
		metrics = QFontMetrics(self.editBoxes[-1].font())
		self.editBoxes[-1].setTabStopWidth(globalSettings.tabWidth * metrics.width(' '))
		self.editBoxes[-1].textChanged.connect(self.updateLivePreviewBox)
		self.editBoxes[-1].undoAvailable.connect(self.actionUndo.setEnabled)
		self.editBoxes[-1].redoAvailable.connect(self.actionRedo.setEnabled)
		self.editBoxes[-1].copyAvailable.connect(self.enableCopy)
		self.editBoxes[-1].document().modificationChanged.connect(self.modificationChanged)
		if globalSettings.useFakeVim:
			self.installFakeVimHandler(self.editBoxes[-1])
		return self.getSplitter(-1)

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
			if self.fileNames[ind]:
				self.fileSystemWatcher.removePath(self.fileNames[ind])
			del self.editBoxes[ind]
			del self.previewBoxes[ind]
			del self.highlighters[ind]
			del self.markups[ind]
			del self.fileNames[ind]
			del self.actionPreviewChecked[ind]
			del self.actionLivePreviewChecked[ind]
			self.tabWidget.removeTab(ind)

	def getMarkupClass(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		if fileName:
			markupClass = markups.get_markup_for_file_name(
				fileName, return_class=True)
			if markupClass:
				return markupClass
		return self.defaultMarkup

	def getMarkup(self, fileName=None):
		if fileName is None:
			fileName = self.fileNames[self.ind]
		markupClass = self.getMarkupClass(fileName=fileName)
		if markupClass and markupClass.available():
			return markupClass(filename=fileName)

	def docTypeChanged(self):
		oldType = self.highlighters[self.ind].docType
		markupClass = self.getMarkupClass()
		newType = markupClass.name if markupClass else ''
		if oldType != newType:
			self.markups[self.ind] = self.getMarkup()
			self.updatePreviewBox()
			self.highlighters[self.ind].docType = newType
			self.highlighters[self.ind].rehighlight()
		dtMarkdown = (newType == DOCTYPE_MARKDOWN)
		dtMkdOrReST = (newType in (DOCTYPE_MARKDOWN, DOCTYPE_REST))
		self.tagsBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.fileNames[self.ind]) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		if ind > -1:
			self.actionUndo.setEnabled(self.editBoxes[ind].document().isUndoAvailable())
			self.actionRedo.setEnabled(self.editBoxes[ind].document().isRedoAvailable())
			self.actionCopy.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionCut.setEnabled(self.editBoxes[ind].textCursor().hasSelection())
			self.actionPreview.setChecked(self.actionPreviewChecked[ind])
			self.actionLivePreview.setChecked(self.actionLivePreviewChecked[ind])
			self.actionTableMode.setChecked(self.editBoxes[ind].tableModeEnabled)
			self.editBar.setDisabled(self.actionPreviewChecked[ind])
		self.ind = ind
		if self.fileNames[ind]:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(self.editBoxes[ind].document().isModified())
		if globalSettings.restorePreviewState:
			globalSettings.previewState = self.actionLivePreviewChecked[ind]
		if self.actionLivePreviewChecked[ind]:
			self.enableLivePreview(True)
		self.editBoxes[self.ind].setFocus(Qt.OtherFocusReason)

	def changeFont(self):
		if not self.font:
			self.font = QFont()
		fd = QFontDialog.getFont(self.font, self)
		if fd[1]:
			self.font = QFont()
			self.font.setFamily(fd[0].family())
			settings.setValue('font', fd[0].family())
			self.font.setPointSize(fd[0].pointSize())
			settings.setValue('fontSize', fd[0].pointSize())
			self.updatePreviewBox()

	def preview(self, viewmode):
		self.actionPreviewChecked[self.ind] = viewmode
		if self.actionLivePreview.isChecked():
			self.actionLivePreview.setChecked(False)
			return self.enableLivePreview(False)
		self.editBar.setDisabled(viewmode)
		self.editBoxes[self.ind].setVisible(not viewmode)
		self.previewBoxes[self.ind].setVisible(viewmode)
		if viewmode:
			self.updatePreviewBox()

	def enableLivePreview(self, livemode):
		if globalSettings.restorePreviewState:
			globalSettings.previewState = livemode
		self.actionLivePreviewChecked[self.ind] = livemode
		self.actionPreviewChecked[self.ind] = livemode
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.previewBoxes[self.ind].setVisible(livemode)
		self.editBoxes[self.ind].setVisible(True)
		if livemode:
			self.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		oldind = self.ind
		self.tabWidget.clear()
		for self.ind in range(len(self.editBoxes)):
			if enable:
				self.previewBoxes[self.ind] = self.getWebView()
			else:
				self.previewBoxes[self.ind] = QTextBrowser()
				self.previewBoxes[self.ind].setOpenExternalLinks(True)
			splitter = self.getSplitter(self.ind)
			self.tabWidget.addTab(splitter, self.getDocumentTitle(baseName=True))
			self.updatePreviewBox()
			self.previewBoxes[self.ind].setVisible(self.actionPreviewChecked[self.ind])
		self.ind = oldind
		self.tabWidget.setCurrentIndex(self.ind)

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def installFakeVimHandler(self, editor):
		if ReTextFakeVimHandler:
			fakeVimEditor = ReTextFakeVimHandler(editor, self)
			fakeVimEditor.setSaveAction(self.actionSave)
			fakeVimEditor.setQuitAction(self.actionQuit)
			self.actionFakeVimMode.triggered.connect(fakeVimEditor.remove)

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for editor in self.editBoxes:
				self.installFakeVimHandler(editor)
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			if self.sl:
				self.setAllDictionaries(enchant.Dict(self.sl))
			else:
				self.setAllDictionaries(enchant.Dict())
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for hl in self.highlighters:
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.editBoxes[self.ind]
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def getHtml(self, includeStyleSheet=True, includeTitle=True,
	            includeMeta=False, styleForWebKit=False, webenv=False):
		if self.markups[self.ind] is None:
			markupClass = self.getMarkupClass()
			errMsg = self.tr('Could not parse file contents, check if '
			'you have the <a href="%s">necessary module</a> installed!')
			try:
				errMsg %= markupClass.attributes[MODULE_HOME_PAGE]
			except (AttributeError, KeyError):
				# Remove the link if markupClass doesn't have the needed attribute
				errMsg = errMsg.replace('<a href="%s">', '')
				errMsg = errMsg.replace('</a>', '')
			return '<p style="color: red">%s</p>' % errMsg
		text = self.editBoxes[self.ind].toPlainText()
		headers = ''
		if includeStyleSheet:
			fontline = ''
			if styleForWebKit:
				fontname = self.font.family() if self.font else 'Sans'
				fontsize = (self.font if self.font else QFont()).pointSize()
				fontline = 'body { font-family: %s; font-size: %spt }\n' % \
					(fontname, fontsize)
			headers += '<style type="text/css">\n' + fontline + self.ss + '</style>\n'
		cssFileName = self.getDocumentTitle(baseName=True)+'.css'
		if QFile(cssFileName).exists():
			headers += '<link rel="stylesheet" type="text/css" href="%s">\n' \
			% cssFileName
		if includeMeta:
			headers += '<meta name="generator" content="%s %s">\n' % \
			(app_name, app_version)
		fallbackTitle = self.getDocumentTitle() if includeTitle else ''
		return self.markups[self.ind].get_whole_html(text,
			custom_headers=headers, include_stylesheet=includeStyleSheet,
			fallback_title=fallbackTitle, webenv=webenv)

	def updatePreviewBox(self):
		self.previewBlocked = False
		pb = self.previewBoxes[self.ind]
		textedit = isinstance(pb, QTextEdit)
		if textedit:
			scrollbar = pb.verticalScrollBar()
			disttobottom = scrollbar.maximum() - scrollbar.value()
		else:
			frame = pb.page().mainFrame()
			scrollpos = frame.scrollPosition()
		try:
			html = self.getHtml(styleForWebKit=(not textedit))
		except Exception:
			return self.printError()
		if textedit:
			pb.setHtml(html)
		else:
			pb.setHtml(html, QUrl.fromLocalFile(self.fileNames[self.ind]))
		if self.font and textedit:
			pb.document().setDefaultFont(self.font)
		if textedit:
			scrollbar.setValue(scrollbar.maximum() - disttobottom)
		else:
			frame.setScrollPosition(scrollpos)

	def updateLivePreviewBox(self):
		if self.actionLivePreview.isChecked() and self.previewBlocked == False:
			self.previewBlocked = True
			QTimer.singleShot(1000, self.updatePreviewBox)

	def showInDir(self):
		if self.fileNames[self.ind]:
			path = QFileInfo(self.fileNames[self.ind]).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.getDocumentTitle(baseName=True))
		self.setWindowFilePath(self.fileNames[self.ind])
		files = readListFromSettings("recentFileList")
		while self.fileNames[self.ind] in files:
			files.remove(self.fileNames[self.ind])
		files.insert(0, self.fileNames[self.ind])
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.fileNames[self.ind]).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.tabWidget.addTab(self.createTab(""), self.tr("New document"))
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.editBoxes[self.ind].textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFuntion(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFuntion(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i in range(self.tabWidget.count()):
			if self.fileNames[i] == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.fileNames[self.ind] or
				self.editBoxes[self.ind].toPlainText() or
				self.editBoxes[self.ind].document().isModified()
			)
			if noEmptyTab:
				self.tabWidget.addTab(self.createTab(fileName), "")
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.fileNames[self.ind] = fileName
			self.openFileMain()

	def openFileMain(self, encoding=None):
		openfile = QFile(self.fileNames[self.ind])
		openfile.open(QIODevice.ReadOnly)
		stream = QTextStream(openfile)
		if encoding:
			stream.setCodec(encoding)
		elif globalSettings.defaultCodec:
			stream.setCodec(globalSettings.defaultCodec)
		text = stream.readAll()
		openfile.close()
		markupClass = markups.get_markup_for_file_name(
			self.fileNames[self.ind], return_class=True)
		self.highlighters[self.ind].docType = (markupClass.name if markupClass else '')
		self.markups[self.ind] = self.getMarkup()
		if self.defaultMarkup:
			self.highlighters[self.ind].docType = self.defaultMarkup.name
		editBox = self.editBoxes[self.ind]
		modified = bool(encoding) and (editBox.toPlainText() != text)
		editBox.setPlainText(text)
		self.setCurrentFile()
		editBox.document().setModified(modified)
		self.setWindowModified(modified)

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.openFileMain(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		oldind = self.ind
		for self.ind in range(self.tabWidget.count()):
			if self.fileNames[self.ind] and QFileInfo(self.fileNames[self.ind]).isWritable():
				self.saveFileCore(self.fileNames[self.ind])
				self.editBoxes[self.ind].document().setModified(False)
		self.ind = oldind

	def saveFileMain(self, dlg):
		if (not self.fileNames[self.ind]) or dlg:
			markupClass = self.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.fileNames[self.ind]:
					self.fileSystemWatcher.removePath(self.fileNames[self.ind])
				self.fileNames[self.ind] = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.fileNames[self.ind]:
			result = self.saveFileCore(self.fileNames[self.ind])
			if result:
				self.setCurrentFile()
				self.editBoxes[self.ind].document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveFileCore(self, fn):
		self.fileSystemWatcher.removePath(fn)
		savefile = QFile(fn)
		result = savefile.open(QIODevice.WriteOnly)
		if result:
			savestream = QTextStream(savefile)
			if globalSettings.defaultCodec:
				savestream.setCodec(globalSettings.defaultCodec)
			savestream << self.editBoxes[self.ind].toPlainText()
			savefile.close()
		self.fileSystemWatcher.addPath(fn)
		return result

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeMeta=True,
			webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle, self.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.getHtml())
		if self.font:
			td.setDefaultFont(self.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat("odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.previewBoxes[self.ind]
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.getDocumentTitle())
		printer.setCreator(app_name+" "+app_version)
		return printer

	def savePdf(self):
		self.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename+self.getMarkupClass().default_extension
			self.saveFileCore(tmpname)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def getDocumentTitle(self, baseName=False):
		markup = self.markups[self.ind]
		realTitle = ''
		if markup and not baseName:
			text = self.editBoxes[self.ind].toPlainText()
			try:
				realTitle = markup.get_document_title(text)
			except Exception:
				self.printError()
		if realTitle:
			return realTitle
		elif self.fileNames[self.ind]:
			fileinfo = QFileInfo(self.fileNames[self.ind])
			basename = fileinfo.completeBaseName()
			return (basename if basename else fileinfo.fileName())
		return self.tr("New document")

	def autoSaveActive(self):
		return self.autoSaveEnabled and self.fileNames[self.ind] and \
		QFileInfo(self.fileNames[self.ind]).isWritable()

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertChars(self, chars):
		tc = self.editBoxes[self.ind].textCursor()
		if tc.hasSelection():
			selection = tc.selectedText()
			if selection.startswith(chars) and selection.endswith(chars):
				if len(selection) > 2*len(chars):
					selection = selection[len(chars):-len(chars)]
					tc.insertText(selection)
			else:
				tc.insertText(chars+tc.selectedText()+chars)
		else:
			tc.insertText(chars)

	def insertTag(self, ut):
		if not ut:
			return
		if isinstance(ut, int):
			ut = self.usefulTags[ut - 1]
		arg = ' style=""' if ut == 'span' else ''
		tc = self.editBoxes[self.ind].textCursor()
		if ut == 'img':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank"><img src="' + tc.selectedText() + '"/></a>')
		elif ut == 'a':
			toinsert = ('<a href="' + tc.selectedText() +
			'" target="_blank">' + tc.selectedText() + '</a>')
		else:
			toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>'
		tc.insertText(toinsert)
		self.tagsBox.setCurrentIndex(0)

	def insertSymbol(self, num):
		if num:
			self.editBoxes[self.ind].insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = self.fileNames.index(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.editBoxes[ind].document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.editBoxes[ind].document().isModified():
			# File was not modified in ReText, reload silently
			self.openFileMain()
			self.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.openFileMain()
				self.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.editBoxes[ind].document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://sourceforge.net/p/retext/tickets/137/
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		if self.autoSaveActive():
			self.saveFileCore(self.fileNames[self.ind])
			return True
		if not self.editBoxes[ind].document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for self.ind in range(self.tabWidget.count()):
			if not self.maybeSave(self.ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.getHtml(includeStyleSheet=False, includeTitle=False)
		except Exception:
			return self.printError()
		winTitle = self.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('http://sourceforge.net/p/retext/home/Help and Support'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011\u2013' '2015')
		+'<br><a href="http://sourceforge.net/p/retext/">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markup):
		self.defaultMarkup = markup
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markup.name, defaultName)
		oldind = self.ind
		for self.ind in range(len(self.previewBoxes)):
			self.docTypeChanged()
		self.ind = oldind
Esempio n. 32
0
    def device_discovery(self, devs):
        """Called when new devices have been added"""
        for menu in self._all_role_menus:
            role_menu = menu["rolemenu"]
            mux_menu = menu["muxmenu"]
            dev_group = QActionGroup(role_menu, exclusive=True)
            for d in devs:
                dev_node = QAction(d.name,
                                   role_menu,
                                   checkable=True,
                                   enabled=True)
                role_menu.addAction(dev_node)
                dev_group.addAction(dev_node)
                dev_node.toggled.connect(self._inputdevice_selected)

                map_node = None
                if d.supports_mapping:
                    map_node = QMenu("    Input map", role_menu, enabled=False)
                    map_group = QActionGroup(role_menu, exclusive=True)
                    # Connect device node to map node for easy
                    # enabling/disabling when selection changes and device
                    # to easily enable it
                    dev_node.setData((map_node, d))
                    for c in ConfigManager().get_list_of_configs():
                        node = QAction(c,
                                       map_node,
                                       checkable=True,
                                       enabled=True)
                        node.toggled.connect(self._inputconfig_selected)
                        map_node.addAction(node)
                        # Connect all the map nodes back to the device
                        # action node where we can access the raw device
                        node.setData(dev_node)
                        map_group.addAction(node)
                        # If this device hasn't been found before, then
                        # select the default mapping for it.
                        if d not in self._available_devices:
                            last_map = Config().get("device_config_mapping")
                            if d.name in last_map and last_map[d.name] == c:
                                node.setChecked(True)
                    role_menu.addMenu(map_node)
                dev_node.setData((map_node, d, mux_menu))

        # Update the list of what devices we found
        # to avoid selecting default mapping for all devices when
        # a new one is inserted
        self._available_devices = ()
        for d in devs:
            self._available_devices += (d, )

        # Only enable MUX nodes if we have enough devies to cover
        # the roles
        for mux_node in self._all_mux_nodes:
            (mux, sub_nodes) = mux_node.data()
            if len(mux.supported_roles()) <= len(self._available_devices):
                mux_node.setEnabled(True)

        # TODO: Currently only supports selecting default mux
        if self._all_mux_nodes[0].isEnabled():
            self._all_mux_nodes[0].setChecked(True)

        # If the previous length of the available devies was 0, then select
        # the default on. If that's not available then select the first
        # on in the list.
        # TODO: This will only work for the "Normal" mux so this will be
        #       selected by default
        if Config().get("input_device") in [d.name for d in devs]:
            for dev_menu in self._all_role_menus[0]["rolemenu"].actions():
                if dev_menu.text() == Config().get("input_device"):
                    dev_menu.setChecked(True)
        else:
            # Select the first device in the first mux (will always be "Normal"
            # mux)
            self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True)
            logger.info("Select first device")

        self._update_input_device_footer()
Esempio n. 33
0
class ReciteGui(QMainWindow):
    test_version_signal = pyqtSignal()
    read_cables_signal = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.sm = serial_manager.SerialManager()
        self.serial_thread = QThread()
        self.sm.moveToThread(self.serial_thread)
        self.serial_thread.start()

        self.test_version_signal.connect(self.sm.check_version)
        self.read_cables_signal.connect(self.sm.read_cables)
        self.sm.version_check_signal.connect(self.version_check)
        self.sm.port_unavailable_signal.connect(self.port_unavailable)
        self.sm.serial_error_signal.connect(self.serial_error)
        self.sm.cable_values_signal.connect(self.display_cables)
        self.sm.no_sensors_signal.connect(self.no_sensors)

        self.port_name = None

        self.system_font = QApplication.font().family()
        self.label_font = QFont(self.system_font, 14)
        self.sensor_font = QFont(self.system_font, 12)

        self.quit = QAction("Quit/退出;結束", self)
        self.quit.setShortcut("Ctrl+Q")
        self.quit.setStatusTip("Exit Program/退出;結束")
        self.quit.triggered.connect(self.close)

        self.about_tu = QAction("About Test Utility", self)
        self.about_tu.setShortcut("Ctrl+U")
        self.about_tu.setStatusTip("About Program")
        self.about_tu.triggered.connect(self.about_program)

        self.aboutqt = QAction("About Qt", self)
        self.aboutqt.setShortcut("Ctrl+I")
        self.aboutqt.setStatusTip("About Qt")
        self.aboutqt.triggered.connect(self.about_qt)

        # Create menubar
        self.menubar = self.menuBar()
        self.file_menu = self.menubar.addMenu("&File/文件")
        self.file_menu.addAction(self.quit)

        self.serial_menu = self.menubar.addMenu("&Serial/串行端口")
        self.serial_menu.installEventFilter(self)
        self.ports_menu = QMenu("&Ports/串行端口", self)
        self.serial_menu.addMenu(self.ports_menu)
        self.ports_menu.aboutToShow.connect(self.populate_ports)
        self.ports_group = QActionGroup(self)
        self.ports_group.triggered.connect(self.connect_port)

        self.help_menu = self.menubar.addMenu("&Help/帮助")
        self.help_menu.addAction(self.about_tu)
        self.help_menu.addAction(self.aboutqt)

        self.center()

        self.initUI()

    # Get logo path
    def resource_path(self, relative_path):
        if hasattr(sys, '_MEIPASS'):
            return os.path.join(sys._MEIPASS, relative_path)
        return os.path.join(os.path.abspath("."), relative_path)

    def initUI(self):
        RIGHT_SPACING = 350
        LINE_EDIT_WIDTH = 200
        self.central_widget = QWidget()

        self.start_btn = QPushButton("Start Cable Test / 开始测试")
        self.start_btn.setFixedWidth(350)
        self.start_btn.setAutoDefault(True)
        self.start_btn.setFont(self.label_font)
        self.start_btn.clicked.connect(self.start)

        self.logo_img = QPixmap(self.resource_path("h_logo.png"))
        self.logo_img = self.logo_img.scaledToWidth(600)
        self.logo = QLabel()
        self.logo.setPixmap(self.logo_img)

        hbox_logo = QHBoxLayout()
        hbox_logo.addStretch()
        hbox_logo.addWidget(self.logo)
        hbox_logo.addStretch()

        hbox_start_btn = QHBoxLayout()
        hbox_start_btn.addStretch()
        hbox_start_btn.addWidget(self.start_btn)
        hbox_start_btn.addStretch()

        vbox = QVBoxLayout()
        vbox.addStretch()
        vbox.addLayout(hbox_logo)
        vbox.addSpacing(100)
        vbox.addLayout(hbox_start_btn)
        vbox.addStretch()

        self.central_widget.setLayout(vbox)
        self.setCentralWidget(self.central_widget)
        self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.setWindowTitle("BeadedStream Cable Test Utility")

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def about_program(self):
        QMessageBox.about(self, "About TestUtility", ABOUT_TEXT)

    def about_qt(self):
        QMessageBox.aboutQt(self, "About Qt")

    def populate_ports(self):
        ports = serial_manager.SerialManager.scan_ports()
        self.ports_menu.clear()

        if not ports:
            self.ports_menu.addAction("None/无连接")
            self.sm.close_port()

        for port in ports:
            port_description = str(port)[:-6]
            action = self.ports_menu.addAction(port_description)
            self.port_name = port_description[0:4]
            if self.sm.is_connected(self.port_name):
                action.setCheckable(True)
                action.setChecked(True)
            self.ports_group.addAction(action)

    def connect_port(self, action):
        p = "COM[0-9]+"
        self.port_name = re.search(p, action.text()).group()
        if (self.sm.is_connected(self.port_name)):
            action.setChecked

        self.sm.open_port(self.port_name)

    def port_unavailable(self):
        QMessageBox.warning(self, "Warning", "Port unavailable!\n" "没有串行端口")

    def serial_error(self):
        QMessageBox.warning(
            self, "Serial Error/串行端口错误", "Serial error! "
            "Please try the operation again.\n"
            "串行端口错误")
        self.setup_page2()

    def closeEvent(self, event):
        event.accept()

        quit_msg = "Are you sure you want to exit the program?"
        confirmation = QMessageBox.question(self, 'Message', quit_msg,
                                            QMessageBox.Yes, QMessageBox.No)

        if confirmation == QMessageBox.Yes:
            self.serial_thread.quit()
            self.serial_thread.wait()
            event.accept()
        else:
            event.ignore()

    def start(self):
        if not self.sm.is_connected(self.port_name):
            QMessageBox.warning(self, "Warning", "No serial port selected!\n"
                                "未选串行端口")
        else:
            self.test_version_signal.emit()

    def version_check(self, result):
        if result:
            self.setup_page2()
        else:
            QMessageBox.warning(
                self, "Serial Error/串行端口错误", "No communication. "
                "Please make sure the Recite is powered and the cables are "
                "properly connected to computer.")

    def setup_page2(self):
        central_widget = QWidget()

        self.lbl = QLabel("Read sensors/读传感器")
        self.lbl.setFont(self.label_font)

        self.read_cables_btn = QPushButton("Read sensors/读传感器")
        self.read_cables_btn.clicked.connect(self.test_cables)

        self.hbox = QHBoxLayout()
        self.hbox.addWidget(self.lbl)
        self.hbox.addWidget(self.read_cables_btn)

        self.sensors = QLineEdit()
        self.sensors.setReadOnly(True)
        self.sensors.setFont(self.sensor_font)

        self.box1 = QTextEdit()
        self.box1.setReadOnly(True)
        self.box1.setFont(self.sensor_font)
        self.box2 = QTextEdit()
        self.box2.setReadOnly(True)
        self.box2.setFont(self.sensor_font)
        self.box3 = QTextEdit()
        self.box3.setReadOnly(True)
        self.box3.setFont(self.sensor_font)
        self.box4 = QTextEdit()
        self.box4.setReadOnly(True)
        self.box4.setFont(self.sensor_font)

        self.grid = QGridLayout()
        self.grid.setVerticalSpacing(10)
        self.grid.setHorizontalSpacing(15)

        self.grid.addLayout(self.hbox, 0, 0)
        self.grid.addWidget(self.sensors, 0, 1, 1, 2)
        self.grid.addWidget(self.box1, 1, 0, 8, 1)
        self.grid.addWidget(self.box2, 1, 1, 8, 1)
        self.grid.addWidget(self.box3, 1, 2, 8, 1)
        self.grid.addWidget(self.box4, 0, 3, 9, 1)

        central_widget.setLayout(self.grid)

        self.setCentralWidget(central_widget)

    def test_cables(self):
        self.lbl.setText("Reading.../读取...")
        self.read_cables_btn.setEnabled(False)
        self.sensors.clear()
        self.box1.clear()
        self.box2.clear()
        self.box3.clear()
        self.box4.clear()
        self.read_cables_signal.emit()

    def display_cables(self, boards_list, sensor_num, temps_dict):

        self.lbl.setText("Finished./完毕")
        self.read_cables_btn.setEnabled(True)
        self.sensors.setText(f"{sensor_num} sensors found")

        failed_sensors = self.check_cable_temps(temps_dict)

        # Display 30 boards each in first three box
        # remaining boards in the last one. There should be no more than 125
        # sensors.
        boxes = [self.box1, self.box2, self.box3, self.box4]
        box_capacities = [30, 30, 30, 35]
        boards_list.reverse()

        box_num = 0
        list_num = 1
        temp = None

        while len(boards_list):
            for i in range(0, box_capacities[box_num]):
                try:
                    sensor_id = boards_list.pop()
                except IndexError:
                    break

                # Trim it to six bytes to match the ids from the temps
                sensor_id_temp = sensor_id[3:-3]

                if sensor_id_temp in failed_sensors:
                    temp = failed_sensors[sensor_id_temp]
                    sensor_text = (f" {list_num}) {sensor_id} FAILED {temp}")
                    boxes[box_num].setTextColor(Qt.red)
                else:
                    sensor_text = f" {list_num}) {sensor_id}"
                    boxes[box_num].setTextColor(Qt.black)

                # Add a blank line every ten sensors
                if not list_num % 10:
                    sensor_text += "\n"

                boxes[box_num].append(sensor_text)
                list_num += 1

            box_num += 1

    def check_cable_temps(self, temps):
        failed = {}
        for sensor, temp in temps.items():
            if temp > 75.0:
                failed[sensor] = temp
        return failed

    def no_sensors(self):
        self.setup_page2()
        self.sensors.setText("0 sensors found")
Esempio n. 34
0
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.device_username = self.settings.value("device_username", "", str)
            self.device_password = self.settings.value("device_password", "", str)
            self.backup = bytes()
            self.dl = self.nam.get(QNetworkRequest(QUrl("http://{}:{}@{}/dl".format( \
                self.device_username, urllib.parse.quote(self.device_password),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()
Esempio n. 35
0
 def make_menu_bar(self):
     '''
     Initialises the menu bar at the top. '''
     menubar = self.menuBar()
     # Create a File menu and add an open button
     self.file_menu = menubar.addMenu('&File')
     open_action = QtGui.QAction('&Open', self)
     open_action.setShortcut('Ctrl+o')
     open_action.triggered.connect(self.choose_file)
     self.file_menu.addAction(open_action)
     # Add an exit button to the file menu
     exit_action = QtGui.QAction('&Exit', self)
     exit_action.setShortcut('Ctrl+Z')
     exit_action.setStatusTip('Exit application')
     exit_action.triggered.connect(QtGui.qApp.quit)
     self.file_menu.addAction(exit_action)
     ## Create a view manu
     self.view_menu = menubar.addMenu('&View')
     #self.view_menu.setShortcut('Alt+v')
     self.image_plot_options_menu = self.view_menu.addMenu(
         '&Image Plot Options')
     self.colormap_options_menu = self.image_plot_options_menu.addMenu(
         '&Colormap')
     group = QActionGroup(self.colormap_options_menu)
     texts = [
         "default",
         "jet",
         'jet_extended',
         'albula',
         'albula_r',
         'goldish',
         "viridis",
         'spectrum',
         'vge',
         'vge_hdr',
     ]
     for text in texts:
         action = QAction(text,
                          self.colormap_options_menu,
                          checkable=True,
                          checked=text == texts[1])
         self.colormap_options_menu.addAction(action)
         group.addAction(action)
     group.setExclusive(True)
     group.triggered.connect(self.onTriggered_colormap)
     self.colorscale_options_menu = self.image_plot_options_menu.addMenu(
         '&ColorScale')
     group = QActionGroup(self.colorscale_options_menu)
     texts = ["linear", "log"]
     for text in texts:
         action = QAction(text,
                          self.colormap_options_menu,
                          checkable=True,
                          checked=text == texts[1])
         self.colorscale_options_menu.addAction(action)
         group.addAction(action)
     group.setExclusive(True)
     group.triggered.connect(self.onTriggered_colorscale)
     self.display_image_data_options_menu = self.view_menu.addMenu(
         '&Display Image Data')
     show_image_data_action = QAction('show data',
                                      self,
                                      checkable=True,
                                      checked=False)
     show_image_data_action.triggered.connect(
         self.onTriggered_show_image_data)
     self.display_image_data_options_menu.addAction(show_image_data_action)
     # Create a Help menu and add an about button
     help_menu = menubar.addMenu('&Help')
     about_action = QtGui.QAction('About XSH5FView', self)
     about_action.setStatusTip('About this program')
     about_action.triggered.connect(self.show_about_menu)
     help_menu.addAction(about_action)
Esempio n. 36
0
class NoteEditor(QPlainTextEdit):
    """The note editing class used uses a plain text editing window to edit markdown files (notes)."""

    def __init__(self, parent):
        super(NoteEditor, self).__init__(parent)
        self.highlighter = Highlighter( self.document() )

        # the language dictionary to be used
        self.dictionary = None
        self.highlighter.setDictionary(self.dictionary)

        # create dictionary selector menu
        self.dictionarySelector = QMenu("&Dictionary")
        self.dictionaryActions = QActionGroup(self)

        self.noDicitionaryAction = self.dictionaryActions.addAction("None")
        self.noDicitionaryAction.setCheckable(True)
        self.dictionarySelector.addAction(self.noDicitionaryAction)

        self.defaultDicitionaryAction = self.dictionaryActions.addAction("Default")
        self.defaultDicitionaryAction.setCheckable(True)
        self.dictionarySelector.addAction(self.defaultDicitionaryAction)

        self.dictionarySelector.addSeparator()

        for lang in enchant.list_languages():
            langAction = self.dictionaryActions.addAction(lang)
            langAction.setCheckable(True)
            self.dictionarySelector.addAction(langAction)

        # connect signal to change language dictionary and set the default language
        self.dictionaryActions.triggered.connect(self.changeDictionary)
        self.defaultDicitionaryAction.trigger()

        # configure the custom context menu for spelling suggestions
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.showContextMenu)

        # settings
        self.noteFilePath = ""                  #stores path to the file being edited
        self.setFont(QFont("Monospace"))
        tabWidth = QFontMetrics( self.currentCharFormat().font() ).width("    ")#get the width of four spaces
        self.setTabStopWidth(tabWidth)                                          #set the default tab width to be that of four spaces

    #%%% To do: use 'keyPressEvent' to implement auto-indent %%%


    def setWrapMode(self, wrapMode):
        self.setWordWrapMode(wrapMode)


    def getDictionarySelector(self):
        return self.dictionarySelector


    def changeDictionary(self, action):
        """Change the spell check dictionary (language) based on the languaged selected from the 'Dictionary' menu."""

        # change the language dictionary
        if action is self.noDicitionaryAction:
            self.dictionary = None
        elif action is self.defaultDicitionaryAction:
            self.dictionary = enchant.Dict(locale.getdefaultlocale()[0])
        else:
            self.dictionary = enchant.Dict(action.text())

        # rehighlight the text to find spelling errors
        self.highlighter.setDictionary(self.dictionary)
        self.highlighter.rehighlight()


    def showContextMenu(self, position):
        """Shows an appropriate context menu for the area of the editor that was right-clicked."""

        # create the context menu that will be displayed
        contextMenu = QMenu()

        # select the word that was right-clicked
        textCursor = self.cursorForPosition(position)
        textCursor.select(QTextCursor.WordUnderCursor)
        word = textCursor.selectedText()

        # if a word was selected and it's misspelled, show a suggestion menu
        if word and not len(word) is 0:
            if not self.dictionary.check(word):

                # create the suggestion menu
                for suggestion in self.dictionary.suggest(word):
                    a = contextMenu.addAction(suggestion)
                    a.setData("custom")

                contextMenu.addSeparator()
                a = contextMenu.addAction("Add to dictionary")
                a.setData("custom")
            else:
                a = contextMenu.addAction("Remove from dictionary")
                a.setData("custom")

            contextMenu.addSeparator()

        # add standard context menu actions
        standardMenu = self.createStandardContextMenu(position);
        isFirst = True
        for a in standardMenu.children():
            if isFirst:
                isFirst = False
            else:
                contextMenu.addAction(a)

        # show context menu
        action = contextMenu.exec(self.mapToGlobal(position))

        # if a suggestion was selected, replace the current word with it
        if action and action.data() == "custom":
            if action.text() == "Add to dictionary":
                self.dictionary.add(word)
            elif action.text() == "Remove from dictionary":
                self.dictionary.remove(word)
            else:
                textCursor.removeSelectedText()
                textCursor.insertText(action.text())


    def openFileRequest(self, filePath):
        """Open a file using its path (string) in the editor.  Return 'True' if successful, 'False' otherwise."""

        noErrors = True                 #set return state
        noteFile = open(filePath, "r")
        if noteFile:                    #if the file was opened successfully
            self.setPlainText( noteFile.read() )    #set its contents in the editor
            self.noteFilePath = filePath            #save the file path to the internal variable
            self.noteFileChanged.emit( os.path.abspath(self.noteFilePath) ) #emit signal to notify other objects of file change
        else:                           #else, return an error
            noErrors = False

        noteFile.close()
        return noErrors


    def writeToFile(self, filePath):
        """Writes text in editor to file 'filePath'.  Return 'True' if successful, 'False' otherwise."""

        noErrors = True
        if os.path.exists(filePath):    #check if file exists
            filePath = os.path.abspath(filePath)    #make path absolute just in case...
            noteFile = open(filePath, "w")
            if noteFile:                            #if file was opened successfully
                noteFile.write( self.toPlainText() )    #write text to file
            else:
                noErrors = False
        else:
            noErrors = False

        return noErrors


    def saveFileRequest(self):
        """Saves text in editor to note file.  Return 'True' if successful, 'False' otherwise."""

        noErrors = True
        if self.noteFilePath != "":                     #if note path is set (the editor knows which file it should save to)
            noErrors = self.writeToFile( self.noteFilePath )#write text to note file
        else:
            noErrors = False

        return noErrors


    def saveAsRequested(self, filePath):
        """Save text in editor to a new file ('filePath') and set it as the new note file.  Return 'True' if successful, 'False' otherwise."""

        noErrors = True

        newFile = open(filePath, "w+")          #create the new file
        newFile.close()

        noErrors = self.writeToFile( filePath ) #write text to the new file
        if noErrors:                            #if no errors occured
            self.noteFilePath = filePath            #set the new file as the internal note file
            self.noteFileChanged.emit( os.path.abspath(self.noteFilePath) ) #emit signal to notify other objects of file change

        return noErrors


    def saveCopyAsRequested(self, filePath):
        """Save note to a new file without loading it.  Return 'True' if successful, 'False' otherwise."""

        noErrors = True

        newFile = open(filePath, "w+")          #create the new file
        newFile.close()

        noErrors = self.writeToFile( filePath ) #wrtie text to the new file

        ########################################################################################
        ### Note that 'self.noteFilePath' (path to the note file being edited) is not changed ##
        ########################################################################################

        return noErrors


    def getNoteFileName(self):
        """Returns the file name of the note being edited."""

        return os.path.basename(self.noteFilePath)


    def getNotePath(self):
        """Returns the path to the note being edited."""

        return self.noteFilePath


    #~signals~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    noteFileChanged = pyqtSignal([str]) #a signal to be emitted when 'self.noteFilePath' (path to the note file being edited) is changed
Esempio n. 37
0
class Menu(QMainWindow):
    def __init__(self):
        super().__init__()

        self.colors = COLORS
        self.qp = QPainter()
        self.init_game()
        self.size2handler = {}
        self.previous_size = "small"
        self.setMouseTracking(True)
        self.setFixedSize(800, 600)
        self.setWindowTitle('Cubes')
        self.setWindowIcon(QIcon('icons/cube.png'))
        self.menu = self.menuBar()
        self.statusBar()
        self.file = self.menu.addMenu('&File')
        self.settings = self.menu.addMenu('&Settings')
        self.exit = QAction('Exit', self)
        self.table_of_records = QAction('Table of records', self)
        self.table_of_records.setStatusTip('Show table of records')
        self.exit.setShortcut('Ctrl+Q')
        self.exit.setStatusTip('Exit application')
        self.file.addAction(self.table_of_records)
        self.file.addAction(self.exit)
        self.init_settings()
        self.exit.triggered.connect(self.close)
        self.table_of_records.triggered.connect(self.show_table_of_records)

    def init_settings(self):
        self.sizes = QActionGroup(self)
        self.small = QAction('Small', self, checkable=True)
        self.small.setChecked(True)
        self.small.triggered.connect(lambda: self.set_size('small'))
        self.sizes.addAction(self.small)
        self.settings.addAction(self.small)
        self.size2handler['small'] = self.small
        self.middle1 = QAction('Middle 1', self, checkable=True)
        self.middle1.triggered.connect(lambda: self.set_size('middle1'))
        self.sizes.addAction(self.middle1)
        self.settings.addAction(self.middle1)
        self.size2handler['middle1'] = self.middle1
        self.middle2 = QAction('Middle 2', self, checkable=True)
        self.middle2.triggered.connect(lambda: self.set_size('middle2'))
        self.sizes.addAction(self.middle2)
        self.settings.addAction(self.middle2)
        self.size2handler['middle2'] = self.middle2
        self.big = QAction('Big', self, checkable=True)
        self.big.triggered.connect(lambda: self.set_size('big'))
        self.sizes.addAction(self.big)
        self.settings.addAction(self.big)
        self.size2handler['big'] = self.big

        self.small.setStatusTip('A small field. Has 10x10 size and 4 colors')
        self.middle1.setStatusTip('A middle field. '
                                  'Has 14x14 size and 5 colors')
        self.middle2.setStatusTip('A middle field. '
                                  'Has 14x14 size and 6 colors')
        self.big.setStatusTip('A big field. Has 25x25 size and 7 colors')

    def set_size(self, size):
        if self.game.total_score != 0:
            if not self.game.exit():
                if self.previous_size != size:
                    self.size2handler[self.previous_size].setChecked(True)
                    self.size2handler[size].setChecked(False)
                return
        self.size2handler[size].setChecked(True)
        self.init_game(size)
        self.previous_size = size

    def closeEvent(self, event):
        self.game.exit()
        event.accept()

    def init_game(self, size='small'):
        self.game = Game(self.colors, size)
        self.setCentralWidget(self.game)

    def show_table_of_records(self):
        w = QWidget(self, Qt.Window)
        w.setWindowModality(Qt.WindowModal)
        w.setFixedSize(300, 200)
        w.setWindowTitle('Table of records')
        w.setWindowIcon(QIcon('icons/trophy.png'))
        w.move(self.geometry().center() - w.rect().center() - QPoint(0, 30))
        table = QTableWidget(self)
        vbox = QVBoxLayout()
        table.setColumnCount(4)
        table.setRowCount(10)
        sizes = ["small", "middle1", "middle2", "big"]
        table.setHorizontalHeaderLabels(sizes)
        with open('texts/best_results.txt') as f:
            best_results = eval(f.read())
        for size in best_results:
            for item in best_results[size]:
                row = best_results[size].index(item)
                column = sizes.index(size)
                item = item[0], str(item[1])
                table.setItem(row, column, QTableWidgetItem(', '.join(item)))
        vbox.addWidget(table)
        vbox.addStretch(1)
        w.setLayout(vbox)
        w.show()

    def paintEvent(self, QPaintEvent):
        self.qp.begin(self)
        self.draw_rectangles(QPaintEvent)
        self.drawText(QPaintEvent)
        self.qp.end()

    def drawText(self, QPaintEvent):
        x, y = self.game.width * 22 + 10, 40
        self.qp.setPen(QColor(0, 0, 0))
        self.qp.setFont(QFont('fonts/ARCADE.ttf', 15))
        self.qp.drawText(x, y, "Score: {}".format(self.game.total_score))
        self.qp.setFont(QFont('fonts/ARCADE.ttf', 10))
        for i in self.game.information_about_colors:
            y = y + 25
            self.qp.drawText(x, y, i)

    def mousePressEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            pos = event.pos().x(), event.pos().y()
            for (x, y) in self.game.object.blocks2coordinates:
                if x * 22 < pos[0] < 23 + x * 22 and \
                        y * 22 < pos[1] < y * 22 + 23:
                    score, color, is_exit = self.game.object.remove((x, y))
                    self.game.information_about_colors = \
                        self.game.set_information_about_colors(color, score)
                    self.game.update_score(score)
                    if is_exit:
                        self.game.exit()
        self.update()

    def draw_rectangles(self, QPaintEvent):
        for (x, y) in self.game.object.blocks2coordinates:
            color = self.game.object.blocks2coordinates[(x, y)][0].color
            self.qp.setPen(QColor(color[0], color[1], color[2]))
            self.qp.setBrush(QColor(color[0], color[1], color[2]))
            self.qp.drawRect(x * 22, y * 22, 20, 20)
class AnalysisMainWindow(QMainWindow):
    DEFAULT_INTERVAL = 0.1
    CMD_BIT_LENGTH = uavcan.get_uavcan_data_type(uavcan.equipment.esc.RawCommand().cmd).value_type.bitlen
    CMD_MAX = 2 ** (CMD_BIT_LENGTH - 1) - 1
    CMD_MIN = -(2 ** (CMD_BIT_LENGTH - 1))

    def __init__(self, parent, node):
        super(AnalysisMainWindow, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)  # This is required to stop background timers
        self._inferiors = None
        self._hook_handle = None

        # self.getTransfer=get_transfer_callback
        self._node = node
        self._initUI()
        node.add_handler(uavcan.equipment.esc.Status, self._updateEscInfo)
        self._bcast_timer = QTimer(self)
        self._bcast_timer.start(self.DEFAULT_INTERVAL * 1e3)
        self._bcast_timer.timeout.connect(self._do_broadcast)

        # self._transfer_timer = QTimer(self)
        # self._transfer_timer.setSingleShot(False)
        # self._transfer_timer.start(self.DEFAULT_INTERVAL * 1e3)
        # self._transfer_timer.timeout.connect(self._update)
        self._thrust_timer = QTimer(self)
        self._thrust_timer.start(self.DEFAULT_INTERVAL * 1e3)
        self._thrust_timer.timeout.connect(self._updateThrust)
        self._thrust_port = None
        self._thrust_queue = None
        self._refreshSerialPortsList()

    def _initUI(self):

        #
        # init layout
        #

        self.setGeometry(560, 240, 1000, 500)
        # self.setFixedSize(500, 500
        self.setWindowTitle('Motor Analysis')
        self._mainWidget = QWidget(self)
        # init box layout
        mainHbox = QHBoxLayout(self._mainWidget)
        vboxLeft = QVBoxLayout(self._mainWidget)
        vboxRight = QVBoxLayout(self._mainWidget)
        mainHbox.addLayout(vboxLeft)
        mainHbox.addLayout(vboxRight)

        self._statusGroup = QGroupBox("Sensor Status")
        vboxStatus = QVBoxLayout(self._statusGroup)
        self._statusGroup.setLayout(vboxStatus)
        vboxLeft.addWidget(self._statusGroup)
        self._statusLabel = QLabel('Load info failed.')
        vboxStatus.addWidget(self._statusLabel)

        # fill right column

        self._controlTypeTabs = QTabWidget(self._mainWidget)
        self._manualControlPanel = ManualControlPanel(self._controlTypeTabs)
        self._manualControlPanel._recordWidget._getEscInfoCallback = self._getEscInfo
        self._manualControlPanel._recordWidget._getThrustCallback = self._getThrust

        self._automaticControlPanel = AutomaticControlPanel(self._controlTypeTabs,
                                                            self._manualControlPanel._escSlider.set_value)
        self._automaticControlPanel._recordWidget._getEscInfoCallback = self._getEscInfo
        self._automaticControlPanel._recordWidget._getThrustCallback = self._getThrust


        self._controlTypeTabs.addTab(self._manualControlPanel, "Manual Control")
        self._controlTypeTabs.addTab(self._automaticControlPanel, "Automatic Control")
        vboxRight.addWidget(self._controlTypeTabs)

        self._plotWidget = PlotsWidget(self, self._getTransfer)
        vboxRight.addWidget(self._plotWidget)
        self._automaticControlPanel._recordWidget._setPlotStyle = self._plotWidget.setPlotStyle
        self.setCentralWidget(self._mainWidget)

        #
        # Control menu
        #
        control_menu = self.menuBar().addMenu('Plot &control')

        self._stop_action = QAction(get_icon('stop'), '&Stop Updates', self)
        self._stop_action.setStatusTip('While stopped, all new data will be discarded')
        self._stop_action.setShortcut(QKeySequence('Ctrl+Shift+S'))
        self._stop_action.setCheckable(True)
        self._stop_action.toggled.connect(self._on_stop_toggled)
        control_menu.addAction(self._stop_action)

        self._pause_action = QAction(get_icon('pause'), '&Pause Updates', self)
        self._pause_action.setStatusTip('While paused, new data will be accumulated in memory '
                                        'to be processed once un-paused')
        self._pause_action.setShortcut(QKeySequence('Ctrl+Shift+P'))
        self._pause_action.setCheckable(True)
        self._pause_action.toggled.connect(self._on_pause_toggled)
        control_menu.addAction(self._pause_action)

        control_menu.addSeparator()

        self._reset_time_action = QAction(get_icon('history'), '&Reset', self)
        self._reset_time_action.setStatusTip('Base time will be reset; all plots will be reset')
        self._reset_time_action.setShortcut(QKeySequence('Ctrl+Shift+R'))
        self._reset_time_action.triggered.connect(self._plotWidget._do_reset)
        control_menu.addAction(self._reset_time_action)
        self.setWindowTitle("Motor Status Plots")

        #
        # Config Menu
        #
        config_menu = self.menuBar().addMenu('Con&fig')
        self._thrust_ports_menu = QMenu("Thrust Sensor Ports")
        self._thrust_port_lists = QActionGroup(self)
        config_menu.addMenu(self._thrust_ports_menu)



    def _on_stop_toggled(self, checked):
        self._pause_action.setChecked(False)
        self.statusBar().showMessage('Stopped' if checked else 'Un-stopped')

    def _on_pause_toggled(self, checked):
        self.statusBar().showMessage('Paused' if checked else 'Un-paused')


    def _do_broadcast(self):
        try:
            msg = uavcan.equipment.esc.RPMCommand()
            raw_value = self._manualControlPanel._escSlider.get_value()
            value = raw_value
            msg.rpm.append(int(value))
            self._node.broadcast(msg)
        except Exception as ex:
            logger.info("RPM Message publishing failed:" + str(ex))
            return

    def _updateEscInfo(self, tr):
        self._tr=tr
        self._plotWidget._update()
        self._statusLabel.setText('''
    Voltage   :{}
    Current   :{}
    Thrust    :{}
    Torque    :{}
    Weight    :{}
    MotorSpeed:{}
        '''.format(str(tr.message.voltage)[0:5],
                   str(tr.message.current)[0:5],
                   self._getThrust(),
                   "N/A",
                   "N/A",
                   tr.message.rpm))

    def _getThrust(self):
        return self._plotWidget._plc_thrust._value
    def _getEscInfo(self):
        return self._tr.message

    def _getTransfer(self):
        return self._tr

    def _refreshSerialPortsList(self):
        print("refresh serial ports list")
        ports = list(serial.tools.list_ports.comports())
        self._thrust_ports_menu.clear()
        if(len(ports)==0):
            action=QAction("No Serial Device Found.",self)
            self._thrust_ports_menu.addAction(action)
            return
        for p in ports:
            print("{}:{}".format(p[0],p[1]))
            action=QAction("{}:{}".format(p[0],p[1]),self)
            action.setCheckable(True)
            action.triggered.connect(partial(self._setThrustSerialPort,port=p[0]))
            self._thrust_port_lists.addAction(action)
            self._thrust_ports_menu.addAction(action)

    def _updateThrust(self):
        if (self._thrust_queue != None):
            try:
                thrust = self._thrust_queue.get_nowait()
            except Exception:
                return
            self._plotWidget._plc_thrust.setValue(thrust)

    def _setThrustSerialPort(self,port):
        print(port)
        self._thrust_port=port
        self._plotWidget._plc_thrust.setHowToLabel("Thrust data source successfully set to {}".format(port)
                                                   , "success")
        self._startThrustAcquireLoop()

    def _startThrustAcquireLoop(self):
        def handle_data(data,q):
            if(data!=b'' and data!=b'-' and data!=b'\n'):
                q.put(data.decode().rstrip())


        def read_from_port(ser,q):
            while ser.isOpen():
                msg = ser.read(ser.inWaiting())
                handle_data(msg,q)
                time.sleep(0.1)

        if(self._thrust_port!=None):
            print("set thrust port:{}".format(self._thrust_port))
            arduino = serial.Serial(self._thrust_port, 9600, timeout=5)
            self._thrust_queue=Queue()
            self._thrust_read_thread=Thread(target=read_from_port,args=(arduino,self._thrust_queue))
            self._thrust_read_thread.start()

        else:
            msgbox = QMessageBox(self)
            msgbox.setText("ERROR: Can not configure the thrust device automatically."
                           + "Please select it manually in <Thrust Sensor Ports> menu.")
            msgbox.exec()
Esempio n. 39
0
class Gui(QMainWindow, Ui_MainWindow):

    NOTEON = 0x9
    NOTEOFF = 0x8
    MIDICTRL = 11

    GREEN = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(125,242,0);}")
    BLUE = ("#cell_frame { border: 0px; border-radius: 10px; "
            "background-color: rgb(0, 130, 240);}")
    RED = ("#cell_frame { border: 0px; border-radius: 10px; "
           "background-color: rgb(255, 21, 65);}")
    AMBER = ("#cell_frame { border: 0px; border-radius: 10px; "
             "background-color: rgb(255, 102, 0);}")
    PURPLE = ("#cell_frame { border: 0px; border-radius: 10px; "
              "background-color: rgb(130, 0, 240);}")
    DEFAULT = ("#cell_frame { border: 0px; border-radius: 10px; "
               "background-color: rgb(217, 217, 217);}")

    RECORD_BLINK = ("QPushButton {background-color: rgb(255, 255, 255);}"
                    "QPushButton:pressed {background-color: "
                    "rgb(98, 98, 98);}")

    RECORD_DEFAULT = ("QPushButton {background-color: rgb(0, 0, 0);}"
                      "QPushButton:pressed {background-color: "
                      "rgb(98, 98, 98);}")

    STATE_COLORS = {Clip.STOP: RED,
                    Clip.STARTING: GREEN,
                    Clip.START: GREEN,
                    Clip.STOPPING: RED,
                    Clip.PREPARE_RECORD: AMBER,
                    Clip.RECORDING: AMBER}
    STATE_BLINK = {Clip.STOP: False,
                   Clip.STARTING: True,
                   Clip.START: False,
                   Clip.STOPPING: True,
                   Clip.PREPARE_RECORD: True,
                   Clip.RECORDING: False}

    BLINK_DURATION = 200
    PROGRESS_PERIOD = 300

    updateUi = pyqtSignal()
    readQueueIn = pyqtSignal()

    def __init__(self, song, jack_client):
        QObject.__init__(self)
        super(Gui, self).__init__()
        self._jack_client = jack_client
        self.setupUi(self)
        self.clip_volume.knobRadius = 3
        self.is_learn_device_mode = False
        self.queue_out, self.queue_in = Queue(), Queue()
        self.updateUi.connect(self.update)
        self.readQueueIn.connect(self.readQueue)
        self.current_vol_block = 0

        # Load devices
        self.deviceGroup = QActionGroup(self.menuDevice)
        self.devices = []
        settings = QSettings('superboucle', 'devices')
        if settings.contains('devices') and settings.value('devices'):
            for raw_device in settings.value('devices'):
                self.devices.append(Device(pickle.loads(raw_device)))
        else:
            self.devices.append(Device({'name': 'No Device', }))
        self.updateDevices()
        self.deviceGroup.triggered.connect(self.onDeviceSelect)

        # Load song
        self.initUI(song)

        self.actionNew.triggered.connect(self.onActionNew)
        self.actionOpen.triggered.connect(self.onActionOpen)
        self.actionSave.triggered.connect(self.onActionSave)
        self.actionSave_As.triggered.connect(self.onActionSaveAs)
        self.actionAdd_Device.triggered.connect(self.onAddDevice)
        self.actionManage_Devices.triggered.connect(self.onManageDevice)
        self.actionFullScreen.triggered.connect(self.onActionFullScreen)
        self.master_volume.valueChanged.connect(self.onMasterVolumeChange)
        self.rewindButton.clicked.connect(self.onRewindClicked)
        self.playButton.clicked.connect(self._jack_client.transport_start)
        self.pauseButton.clicked.connect(self._jack_client.transport_stop)
        self.gotoButton.clicked.connect(self.onGotoClicked)
        self.recordButton.clicked.connect(self.onRecord)
        self.clip_name.textChanged.connect(self.onClipNameChange)
        self.clip_volume.valueChanged.connect(self.onClipVolumeChange)
        self.beat_diviser.valueChanged.connect(self.onBeatDiviserChange)
        self.frame_offset.valueChanged.connect(self.onFrameOffsetChange)
        self.beat_offset.valueChanged.connect(self.onBeatOffsetChange)
        self.revertButton.clicked.connect(self.onRevertClip)
        self.normalizeButton.clicked.connect(self.onNormalizeClip)
        self.exportButton.clicked.connect(self.onExportClip)
        self.deleteButton.clicked.connect(self.onDeleteClipClicked)

        self.blktimer = QTimer()
        self.blktimer.state = False
        self.blktimer.timeout.connect(self.toogleBlinkButton)
        self.blktimer.start(self.BLINK_DURATION)

        self.disptimer = QTimer()
        self.disptimer.start(self.PROGRESS_PERIOD)
        self.disptimer.timeout.connect(self.updateProgress)

        self._jack_client.set_timebase_callback(self.timebase_callback)

        self.show()

    def initUI(self, song):

        # remove old buttons
        self.btn_matrix = [[None for y in range(song.height)]
                           for x in range(song.width)]
        self.state_matrix = [[-1 for y in range(song.height)]
                             for x in range(song.width)]
        for i in reversed(range(self.gridLayout.count())):
            self.gridLayout.itemAt(i).widget().close()
            self.gridLayout.itemAt(i).widget().setParent(None)

        self.song = song
        self.frame_clip.setEnabled(False)
        self.master_volume.setValue(song.volume*256)
        self.bpm.setValue(song.bpm)
        self.beat_per_bar.setValue(song.beat_per_bar)
        for x in range(song.width):
            for y in range(song.height):
                clip = song.clips_matrix[x][y]
                cell = Cell(self, clip, x, y)
                self.btn_matrix[x][y] = cell
                self.gridLayout.addWidget(cell, y, x)

        # send init command
        for init_cmd in self.device.init_command:
            self.queue_out.put(init_cmd)

        self.update()

    def closeEvent(self, event):
        settings = QSettings('superboucle', 'devices')
        settings.setValue('devices',
                          [pickle.dumps(x.mapping) for x in self.devices])

    def onStartStopClicked(self):
        clip = self.sender().parent().parent().clip
        self.startStop(clip.x, clip.y)

    def startStop(self, x, y):
        clip = self.btn_matrix[x][y].clip
        if clip is None:
            return
        if self.song.is_record:
            self.song.is_record = False
            self.updateRecordBtn()
            # calculate buffer size
            state, position = self._jack_client.transport_query()
            bps = position['beats_per_minute'] / 60
            fps = position['frame_rate']
            size = (1 / bps) * clip.beat_diviser * fps
            self.song.init_record_buffer(clip, 2, size, fps)
            # set frame offset based on jack block size
            clip.frame_offset = self._jack_client.blocksize
            clip.state = Clip.PREPARE_RECORD
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        else:
            self.song.toogle(clip.x, clip.y)
        self.update()

    def onEdit(self):
        self.last_clip = self.sender().parent().parent().clip
        if self.last_clip:
            self.frame_clip.setEnabled(True)
            self.clip_name.setText(self.last_clip.name)
            self.frame_offset.setValue(self.last_clip.frame_offset)
            self.beat_offset.setValue(self.last_clip.beat_offset)
            self.beat_diviser.setValue(self.last_clip.beat_diviser)
            self.clip_volume.setValue(self.last_clip.volume*256)
            state, position = self._jack_client.transport_query()
            fps = position['frame_rate']
            bps = self.bpm.value() / 60
            if self.bpm.value() and fps:
                size_in_beat = (bps/fps)*self.song.length(self.last_clip)
            else:
                size_in_beat = "No BPM info"
            clip_description = ("Size in sample : %s\nSize in beat : %s"
                                % (self.song.length(self.last_clip),
                                   round(size_in_beat, 1)))

            self.clip_description.setText(clip_description)

    def onAddClipClicked(self):
        AddClipDialog(self, self.sender().parent().parent())

    def onRevertClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            self.song.data[audio_file] = self.song.data[audio_file][::-1]

    def onNormalizeClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            absolute_val = np.absolute(self.song.data[audio_file])
            current_level = np.ndarray.max(absolute_val)
            self.song.data[audio_file][:] *= (1 / current_level)

    def onExportClip(self):
        if self.last_clip and self.last_clip.audio_file:
            audio_file = self.last_clip.audio_file
            file_name, a = (
                QFileDialog.getSaveFileName(self,
                                            'Export Clip : %s'
                                            % self.last_clip.name,
                                            expanduser('~'),
                                            'WAVE (*.wav)'))

            if file_name:
                file_name = verify_ext(file_name, 'wav')
                sf.write(self.song.data[audio_file], file_name,
                         self.song.samplerate[audio_file],
                         subtype=sf.default_subtype('WAV'),
                         format='WAV')

    def onDeleteClipClicked(self):
        if self.last_clip:
            response = QMessageBox.question(self,
                                            "Delete Clip ?",
                                            ("Are you sure "
                                             "to delete the clip ?"))
            if response == QMessageBox.Yes:
                self.frame_clip.setEnabled(False)
                self.song.removeClip(self.last_clip)
                self.initUI(self.song)

    def onMasterVolumeChange(self):
        self.song.volume = (self.master_volume.value() / 256)

    def onStartClicked(self):
        pass
        self._jack_client.transport_start

    def onGotoClicked(self):
        state, position = self._jack_client.transport_query()
        new_position = (position['beats_per_bar']
                        * (self.gotoTarget.value() - 1)
                        * position['frame_rate']
                        * (60 / position['beats_per_minute']))
        self._jack_client.transport_locate(int(round(new_position, 0)))

    def onRecord(self):
        self.song.is_record = not self.song.is_record
        self.updateRecordBtn()

    def updateRecordBtn(self):
        if not self.song.is_record:
            self.recordButton.setStyleSheet(self.RECORD_DEFAULT)
        if self.device.record_btn:
            (msg_type, channel, pitch, velocity) = self.device.record_btn
            if self.song.is_record:
                color = self.device.blink_amber_vel
            else:
                color = self.device.black_vel
            self.queue_out.put(((msg_type << 4) + channel, pitch, color))

    def onRewindClicked(self):
        self._jack_client.transport_locate(0)

    def onClipNameChange(self):
        self.last_clip.name = self.clip_name.text()
        tframe = self.btn_matrix[self.last_clip.x][self.last_clip.y]
        tframe.clip_name.setText(self.last_clip.name)

    def onClipVolumeChange(self):
        self.last_clip.volume = (self.clip_volume.value() / 256)

    def onBeatDiviserChange(self):
        self.last_clip.beat_diviser = self.beat_diviser.value()

    def onFrameOffsetChange(self):
        self.last_clip.frame_offset = self.frame_offset.value()

    def onBeatOffsetChange(self):
        self.last_clip.beat_offset = self.beat_offset.value()

    def onActionNew(self):
        NewSongDialog(self)

    def onActionOpen(self):
        file_name, a = (
            QFileDialog.getOpenFileName(self,
                                        'Open file',
                                        expanduser('~'),
                                        'Super Boucle Song (*.sbs)'))
        if file_name:
            self.setEnabled(False)
            message = QMessageBox(self)
            message.setWindowTitle("Loading ....")
            message.setText("Reading Files, please wait ...")
            message.show()
            self.initUI(load_song_from_file(file_name))
            message.close()
            self.setEnabled(True)

    def onActionSave(self):
        if self.song.file_name:
            self.song.save()
        else:
            self.onActionSaveAs()

    def onActionSaveAs(self):
        file_name, a = (
            QFileDialog.getSaveFileName(self,
                                        'Save As',
                                        expanduser('~'),
                                        'Super Boucle Song (*.sbs)'))

        if file_name:
            file_name = verify_ext(file_name, 'sbs')
            self.song.file_name = file_name
            self.song.save()
            print("File saved to : {}".format(self.song.file_name))

    def onAddDevice(self):
        self.learn_device = LearnDialog(self, self.addDevice)
        self.is_learn_device_mode = True

    def onManageDevice(self):
        ManageDialog(self)

    def onActionFullScreen(self):
        if self.isFullScreen():
            self.showNormal()
        else:
            self.showFullScreen()
        self.show()

    def update(self):
        for x in range(len(self.song.clips_matrix)):
            line = self.song.clips_matrix[x]
            for y in range(len(line)):
                clp = line[y]
                if clp is None:
                    state = None
                else:
                    state = clp.state
                if state != self.state_matrix[x][y]:
                    if clp:
                        self.setCellColor(x,
                                          y,
                                          self.STATE_COLORS[state],
                                          self.STATE_BLINK[state])
                    try:
                        self.queue_out.put(self.device.generateNote(x,
                                                                    y,
                                                                    state))
                    except IndexError:
                        # print("No cell associated to %s x %s"
                        # % (clp.x, clp.y))
                        pass
                self.state_matrix[x][y] = state

    def redraw(self):
        self.state_matrix = [[-1 for x in range(self.song.height)]
                             for x in range(self.song.width)]
        self.update()

    def readQueue(self):
        try:
            while True:
                note = self.queue_in.get(block=False)
                if len(note) == 3:
                    status, pitch, vel = struct.unpack('3B', note)
                    channel = status & 0xF
                    msg_type = status >> 4
                    self.processNote(msg_type, channel, pitch, vel)
                # else:
                # print("Invalid message length")
        except Empty:
            pass

    def processNote(self, msg_type, channel, pitch, vel):

        btn_id = (msg_type,
                  channel,
                  pitch,
                  vel)
        btn_id_vel = (msg_type, channel, pitch, -1)
        ctrl_key = (msg_type, channel, pitch)

        # master volume
        if ctrl_key == self.device.master_volume_ctrl:
            self.song.master_volume = vel / 127
            (self.master_volume
             .setValue(self.song.master_volume * 256))
        elif self.device.play_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_start()
        elif self.device.pause_btn in [btn_id, btn_id_vel]:
            self._jack_client.transport_stop()
        elif self.device.rewind_btn in [btn_id, btn_id_vel]:
            self.onRewindClicked()
        elif self.device.goto_btn in [btn_id, btn_id_vel]:
            self.onGotoClicked()
        elif self.device.record_btn in [btn_id, btn_id_vel]:
            self.onRecord()
        elif ctrl_key in self.device.ctrls:
            try:
                ctrl_index = self.device.ctrls.index(ctrl_key)
                clip = (self.song.clips_matrix
                        [ctrl_index]
                        [self.current_vol_block])
                if clip:
                    clip.volume = vel / 127
                    if self.last_clip == clip:
                        self.clip_volume.setValue(self.last_clip.volume * 256)
            except KeyError:
                pass
        elif (btn_id in self.device.block_buttons
              or btn_id_vel in self.device.block_buttons):
            try:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id))
            except ValueError:
                self.current_vol_block = (
                    self.device.block_buttons.index(btn_id_vel))
            for i in range(len(self.device.block_buttons)):
                (a, b_channel, b_pitch, b) = self.device.block_buttons[i]
                if i == self.current_vol_block:
                    color = self.device.red_vel
                else:
                    color = self.device.black_vel
                self.queue_out.put(((self.NOTEON << 4) + b_channel,
                                    b_pitch,
                                    color))
        else:
            x, y = -1, -1
            try:
                x, y = self.device.getXY(btn_id)
            except IndexError:
                pass
            except KeyError:
                try:
                    x, y = self.device.getXY(btn_id_vel)
                except KeyError:
                    pass

            if (x >= 0 and y >= 0):
                self.startStop(x, y)

    def setCellColor(self, x, y, color, blink=False):
        self.btn_matrix[x][y].setStyleSheet(color)
        self.btn_matrix[x][y].blink = blink
        self.btn_matrix[x][y].color = color

    def toogleBlinkButton(self):
        for line in self.btn_matrix:
            for btn in line:
                if btn.blink:
                    if self.blktimer.state:
                        btn.setStyleSheet(btn.color)
                    else:
                        btn.setStyleSheet(self.DEFAULT)
        if self.song.is_record:
            if self.blktimer.state:
                self.recordButton.setStyleSheet(self.RECORD_BLINK)
            else:
                self.recordButton.setStyleSheet(self.RECORD_DEFAULT)

        self.blktimer.state = not self.blktimer.state

    def updateProgress(self):
        state, pos = self._jack_client.transport_query()
        if 'bar' in pos:
            bbt = "%d|%d|%03d" % (pos['bar'], pos['beat'], pos['tick'])
        else:
            bbt = "-|-|-"
        seconds = int(pos['frame'] / pos['frame_rate'])
        (minutes, second) = divmod(seconds, 60)
        (hour, minute) = divmod(minutes, 60)
        time = "%d:%02d:%02d" % (hour, minute, second)
        self.bbtLabel.setText("%s\n%s" % (bbt, time))
        for line in self.btn_matrix:
            for btn in line:
                if btn.clip and btn.clip.audio_file:
                    value = ((btn.clip.last_offset
                              / self.song.length(btn.clip))
                             * 97)
                    btn.clip_position.setValue(value)
                    btn.clip_position.repaint()

    def updateDevices(self):
        for action in self.deviceGroup.actions():
            self.deviceGroup.removeAction(action)
            self.menuDevice.removeAction(action)
        for device in self.devices:
            action = QAction(device.name, self.menuDevice)
            action.setCheckable(True)
            action.setData(device)
            self.menuDevice.addAction(action)
            self.deviceGroup.addAction(action)
        action.setChecked(True)
        self.device = device

    def addDevice(self, device):
        self.devices.append(device)
        self.updateDevices()
        self.is_learn_device_mode = False

    def onDeviceSelect(self):
        self.device = self.deviceGroup.checkedAction().data()
        if self.device:
            if self.device.init_command:
                for note in self.device.init_command:
                    self.queue_out.put(note)
            self.redraw()

    def timebase_callback(self, state, nframes, pos, new_pos):
        pos.valid = 0x10
        pos.bar_start_tick = BAR_START_TICK
        pos.beats_per_bar = self.beat_per_bar.value()
        pos.beat_type = BEAT_TYPE
        pos.ticks_per_beat = TICKS_PER_BEAT
        pos.beats_per_minute = self.bpm.value()
        ticks = frame2bbt(pos.frame,
                          pos.ticks_per_beat,
                          pos.beats_per_minute,
                          pos.frame_rate)
        (beats, pos.tick) = divmod(int(round(ticks, 0)),
                                   int(round(pos.ticks_per_beat, 0)))
        (bar, beat) = divmod(beats, int(round(pos.beats_per_bar, 0)))
        (pos.bar, pos.beat) = (bar + 1, beat + 1)
        return None
Esempio n. 40
0
class MainWindow(QMainWindow, Ui_MainWindow):
    dictChanged = pyqtSignal(str)

    # Tab indexes
    TabInfos = 0
    TabSummary = 1
    TabPersos = 2
    TabPlots = 3
    TabWorld = 4
    TabOutline = 5
    TabRedac = 6

    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)
        self.currentProject = None

        self.readSettings()

        # UI
        self.setupMoreUi()

        # Welcome
        self.welcome.updateValues()
        # self.welcome.btnCreate.clicked.connect
        self.stack.setCurrentIndex(0)

        # Word count
        self.mprWordCount = QSignalMapper(self)
        for t, i in [
            (self.txtSummarySentence, 0),
            (self.txtSummaryPara, 1),
            (self.txtSummaryPage, 2),
            (self.txtSummaryFull, 3)
        ]:
            t.textChanged.connect(self.mprWordCount.map)
            self.mprWordCount.setMapping(t, i)
        self.mprWordCount.mapped.connect(self.wordCount)

        # Snowflake Method Cycle
        self.mapperCycle = QSignalMapper(self)
        for t, i in [
            (self.btnStepTwo, 0),
            (self.btnStepThree, 1),
            (self.btnStepFour, 2),
            (self.btnStepFive, 3),
            (self.btnStepSix, 4),
            (self.btnStepSeven, 5),
            (self.btnStepEight, 6)
        ]:
            t.clicked.connect(self.mapperCycle.map)
            self.mapperCycle.setMapping(t, i)

        self.mapperCycle.mapped.connect(self.clickCycle)
        self.cmbSummary.currentIndexChanged.connect(self.summaryPageChanged)
        self.cmbSummary.setCurrentIndex(0)
        self.cmbSummary.currentIndexChanged.emit(0)

        # Main Menu
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(False)

        self.actOpen.triggered.connect(self.welcome.openFile)
        self.actSave.triggered.connect(self.saveDatas)
        self.actSaveAs.triggered.connect(self.welcome.saveAsFile)
        self.actCompile.triggered.connect(self.doCompile)
        self.actLabels.triggered.connect(self.settingsLabel)
        self.actStatus.triggered.connect(self.settingsStatus)
        self.actSettings.triggered.connect(self.settingsWindow)
        self.actCloseProject.triggered.connect(self.closeProject)
        self.actQuit.triggered.connect(self.close)
        self.actToolFrequency.triggered.connect(self.frequencyAnalyzer)
        self.generateViewMenu()

        self.makeUIConnections()

        # self.loadProject(os.path.join(appPath(), "test_project.zip"))

    ###############################################################################
    # SUMMARY
    ###############################################################################

    def summaryPageChanged(self, index):
        fractalButtons = [
            self.btnStepTwo,
            self.btnStepThree,
            self.btnStepFive,
            self.btnStepSeven,
        ]
        for b in fractalButtons:
            b.setVisible(fractalButtons.index(b) == index)

    ###############################################################################
    # OUTLINE
    ###############################################################################

    def outlineRemoveItemsRedac(self):
        self.treeRedacOutline.delete()

    def outlineRemoveItemsOutline(self):
        self.treeOutlineOutline.delete()

    ###############################################################################
    # PERSOS
    ###############################################################################

    def changeCurrentPerso(self, trash=None):

        index = self.lstPersos.currentPersoIndex()

        if not index.isValid():
            self.tabPlot.setEnabled(False)
            return

        self.tabPersos.setEnabled(True)

        for w in [
            self.txtPersoName,
            self.sldPersoImportance,
            self.txtPersoMotivation,
            self.txtPersoGoal,
            self.txtPersoConflict,
            self.txtPersoEpiphany,
            self.txtPersoSummarySentence,
            self.txtPersoSummaryPara,
            self.txtPersoSummaryFull,
            self.txtPersoNotes,
        ]:
            w.setCurrentModelIndex(index)

        # Button color
        self.mdlPersos.updatePersoColor(index)

        # Perso Infos
        self.tblPersoInfos.setRootIndex(index)

        if self.mdlPersos.rowCount(index):
            self.updatePersoInfoView()

    def updatePersoInfoView(self):
        # Hide columns
        for i in range(self.mdlPersos.columnCount()):
            self.tblPersoInfos.hideColumn(i)
        self.tblPersoInfos.showColumn(Perso.infoName.value)
        self.tblPersoInfos.showColumn(Perso.infoData.value)

        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
                Perso.infoName.value, QHeaderView.ResizeToContents)
        self.tblPersoInfos.horizontalHeader().setSectionResizeMode(
                Perso.infoData.value, QHeaderView.Stretch)
        self.tblPersoInfos.verticalHeader().hide()

    ###############################################################################
    # PLOTS
    ###############################################################################

    def changeCurrentPlot(self):
        index = self.lstPlots.currentPlotIndex()

        if not index.isValid():
            self.tabPlot.setEnabled(False)
            return

        self.tabPlot.setEnabled(True)
        self.txtPlotName.setCurrentModelIndex(index)
        self.txtPlotDescription.setCurrentModelIndex(index)
        self.txtPlotResult.setCurrentModelIndex(index)
        self.sldPlotImportance.setCurrentModelIndex(index)
        self.lstPlotPerso.setRootIndex(index.sibling(index.row(),
                                                     Plot.persos.value))
        subplotindex = index.sibling(index.row(), Plot.subplots.value)
        self.lstSubPlots.setRootIndex(subplotindex)
        if self.mdlPlots.rowCount(subplotindex):
            self.updateSubPlotView()

        # self.txtSubPlotSummary.setCurrentModelIndex(QModelIndex())
        self.txtSubPlotSummary.setEnabled(False)
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText("")
        self._updatingSubPlot = False
        self.lstPlotPerso.selectionModel().clear()

    def updateSubPlotView(self):
        # Hide columns
        for i in range(self.mdlPlots.columnCount()):
            self.lstSubPlots.hideColumn(i)
        self.lstSubPlots.showColumn(Subplot.name.value)
        self.lstSubPlots.showColumn(Subplot.meta.value)

        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
                Subplot.name.value, QHeaderView.Stretch)
        self.lstSubPlots.horizontalHeader().setSectionResizeMode(
                Subplot.meta.value, QHeaderView.ResizeToContents)
        self.lstSubPlots.verticalHeader().hide()

    def changeCurrentSubPlot(self, index):
        # Got segfaults when using textEditView model system, so ad hoc stuff.
        index = index.sibling(index.row(), Subplot.summary.value)
        item = self.mdlPlots.itemFromIndex(index)
        if not item:
            self.txtSubPlotSummary.setEnabled(False)
            return
        self.txtSubPlotSummary.setEnabled(True)
        txt = item.text()
        self._updatingSubPlot = True
        self.txtSubPlotSummary.setPlainText(txt)
        self._updatingSubPlot = False

    def updateSubPlotSummary(self):
        if self._updatingSubPlot:
            return

        index = self.lstSubPlots.currentIndex()
        if not index.isValid():
            return
        index = index.sibling(index.row(), Subplot.summary.value)
        item = self.mdlPlots.itemFromIndex(index)

        self._updatingSubPlot = True
        item.setText(self.txtSubPlotSummary.toPlainText())
        self._updatingSubPlot = False

    def plotPersoSelectionChanged(self):
        "Enables or disables remove plot perso button."
        self.btnRmPlotPerso.setEnabled(
                len(self.lstPlotPerso.selectedIndexes()) != 0)

    ###############################################################################
    # WORLD
    ###############################################################################

    def changeCurrentWorld(self):
        index = self.mdlWorld.selectedIndex()

        if not index.isValid():
            self.tabWorld.setEnabled(False)
            return

        self.tabWorld.setEnabled(True)
        self.txtWorldName.setCurrentModelIndex(index)
        self.txtWorldDescription.setCurrentModelIndex(index)
        self.txtWorldPassion.setCurrentModelIndex(index)
        self.txtWorldConflict.setCurrentModelIndex(index)

    ###############################################################################
    # LOAD AND SAVE
    ###############################################################################

    def loadProject(self, project, loadFromFile=True):
        """Loads the project ``project``.

        If ``loadFromFile`` is False, then it does not load datas from file.
        It assumes that the datas have been populated in a different way."""
        if loadFromFile and not os.path.exists(project):
            print(self.tr("The file {} does not exist. Try again.").format(project))
            self.statusBar().showMessage(
                    self.tr("The file {} does not exist. Try again.").format(project),
                    5000)
            return

        if loadFromFile:
            # Load empty settings
            imp.reload(settings)

            # Load data
            self.loadEmptyDatas()
            self.loadDatas(project)

        self.makeConnections()

        # Load settings
        for i in settings.openIndexes:
            idx = self.mdlOutline.indexFromPath(i)
            self.mainEditor.setCurrentModelIndex(idx, newTab=True)
        self.generateViewMenu()
        self.mainEditor.sldCorkSizeFactor.setValue(settings.corkSizeFactor)
        self.actSpellcheck.setChecked(settings.spellcheck)
        self.toggleSpellcheck(settings.spellcheck)
        self.updateMenuDict()
        self.setDictionary()

        self.mainEditor.setFolderView(settings.folderView)
        self.mainEditor.updateFolderViewButtons(settings.folderView)
        self.tabMain.setCurrentIndex(settings.lastTab)
        # We force to emit even if it opens on the current tab
        self.tabMain.currentChanged.emit(settings.lastTab)
        self.mainEditor.updateCorkBackground()

        # Set autosave
        self.saveTimer = QTimer()
        self.saveTimer.setInterval(settings.autoSaveDelay * 60 * 1000)
        self.saveTimer.setSingleShot(False)
        self.saveTimer.timeout.connect(self.saveDatas)
        if settings.autoSave:
            self.saveTimer.start()

        # Set autosave if no changes
        self.saveTimerNoChanges = QTimer()
        self.saveTimerNoChanges.setInterval(settings.autoSaveNoChangesDelay * 1000)
        self.saveTimerNoChanges.setSingleShot(True)
        self.mdlFlatData.dataChanged.connect(self.startTimerNoChanges)
        self.mdlOutline.dataChanged.connect(self.startTimerNoChanges)
        self.mdlPersos.dataChanged.connect(self.startTimerNoChanges)
        self.mdlPlots.dataChanged.connect(self.startTimerNoChanges)
        self.mdlWorld.dataChanged.connect(self.startTimerNoChanges)
        # self.mdlPersosInfos.dataChanged.connect(self.startTimerNoChanges)
        self.mdlStatus.dataChanged.connect(self.startTimerNoChanges)
        self.mdlLabels.dataChanged.connect(self.startTimerNoChanges)

        self.saveTimerNoChanges.timeout.connect(self.saveDatas)
        self.saveTimerNoChanges.stop()

        # UI
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(True)
        # FIXME: set Window's name: project name

        # Stuff
        # self.checkPersosID()  # Should'n be necessary any longer

        self.currentProject = project
        QSettings().setValue("lastProject", project)

        # Show main Window
        self.stack.setCurrentIndex(1)

    def closeProject(self):
        # Save datas
        self.saveDatas()

        self.currentProject = None
        QSettings().setValue("lastProject", "")

        # FIXME: close all opened tabs in mainEditor

        # Clear datas
        self.loadEmptyDatas()

        self.saveTimer.stop()

        # UI
        for i in [self.actSave, self.actSaveAs, self.actCloseProject,
                  self.menuEdit, self.menuMode, self.menuView, self.menuTools,
                  self.menuHelp]:
            i.setEnabled(False)

        # Reload recent files
        self.welcome.updateValues()

        # Show welcome dialog
        self.stack.setCurrentIndex(0)

    def readSettings(self):
        # Load State and geometry
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        if sttgns.contains("geometry"):
            self.restoreGeometry(sttgns.value("geometry"))
        if sttgns.contains("windowState"):
            self.restoreState(sttgns.value("windowState"))
        else:
            self.dckCheatSheet.hide()
            self.dckSearch.hide()
        if sttgns.contains("metadataState"):
            state = [False if v == "false" else True for v in sttgns.value("metadataState")]
            self.redacMetadata.restoreState(state)
        if sttgns.contains("revisionsState"):
            state = [False if v == "false" else True for v in sttgns.value("revisionsState")]
            self.redacMetadata.revisions.restoreState(state)
        if sttgns.contains("splitterRedacH"):
            self.splitterRedacH.restoreState(sttgns.value("splitterRedacH"))
        if sttgns.contains("splitterRedacV"):
            self.splitterRedacV.restoreState(sttgns.value("splitterRedacV"))
        if sttgns.contains("toolbar"):
            # self.toolbar is not initialized yet, so we just store balue
            self._toolbarState = sttgns.value("toolbar")
        else:
            self._toolbarState = ""


    def closeEvent(self, event):
        # Save State and geometry and other things
        sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
        sttgns.setValue("geometry", self.saveGeometry())
        sttgns.setValue("windowState", self.saveState())
        sttgns.setValue("metadataState", self.redacMetadata.saveState())
        sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState())
        sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState())
        sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState())
        sttgns.setValue("toolbar", self.toolbar.saveState())

        # Specific settings to save before quitting
        settings.lastTab = self.tabMain.currentIndex()

        if self.currentProject:
            # Remembering the current items
            sel = []
            for i in range(self.mainEditor.tab.count()):
                sel.append(self.mdlOutline.pathToIndex(self.mainEditor.tab.widget(i).currentIndex))
            settings.openIndexes = sel

        # Save data from models
        if self.currentProject and settings.saveOnQuit:
            self.saveDatas()

            # closeEvent
            # QMainWindow.closeEvent(self, event)  # Causin segfaults?

    def startTimerNoChanges(self):
        if settings.autoSaveNoChanges:
            self.saveTimerNoChanges.start()

    def saveDatas(self, projectName=None):
        """Saves the current project (in self.currentProject).

        If ``projectName`` is given, currentProject becomes projectName.
        In other words, it "saves as...".
        """

        if projectName:
            self.currentProject = projectName
            QSettings().setValue("lastProject", projectName)

        # Saving
        files = []

        files.append((saveStandardItemModelXML(self.mdlFlatData),
                      "flatModel.xml"))
        files.append((saveStandardItemModelXML(self.mdlPersos),
                      "perso.xml"))
        files.append((saveStandardItemModelXML(self.mdlWorld),
                      "world.xml"))
        files.append((saveStandardItemModelXML(self.mdlLabels),
                      "labels.xml"))
        files.append((saveStandardItemModelXML(self.mdlStatus),
                      "status.xml"))
        files.append((saveStandardItemModelXML(self.mdlPlots),
                      "plots.xml"))
        files.append((self.mdlOutline.saveToXML(),
                      "outline.xml"))
        files.append((settings.save(),
                      "settings.pickle"))

        saveFilesToZip(files, self.currentProject)

        # Giving some feedback
        print(self.tr("Project {} saved.").format(self.currentProject))
        self.statusBar().showMessage(
                self.tr("Project {} saved.").format(self.currentProject), 5000)

    def loadEmptyDatas(self):
        self.mdlFlatData = QStandardItemModel(self)
        self.mdlPersos = persosModel(self)
        # self.mdlPersosProxy = persosProxyModel(self)
        # self.mdlPersosInfos = QStandardItemModel(self)
        self.mdlLabels = QStandardItemModel(self)
        self.mdlStatus = QStandardItemModel(self)
        self.mdlPlots = plotModel(self)
        self.mdlOutline = outlineModel(self)
        self.mdlWorld = worldModel(self)

    def loadDatas(self, project):
        # Loading
        files = loadFilesFromZip(project)

        errors = []

        if "flatModel.xml" in files:
            loadStandardItemModelXML(self.mdlFlatData,
                                     files["flatModel.xml"], fromString=True)
        else:
            errors.append("flatModel.xml")

        if "perso.xml" in files:
            loadStandardItemModelXML(self.mdlPersos,
                                     files["perso.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "world.xml" in files:
            loadStandardItemModelXML(self.mdlWorld,
                                     files["world.xml"], fromString=True)
        else:
            errors.append("world.xml")

        if "labels.xml" in files:
            loadStandardItemModelXML(self.mdlLabels,
                                     files["labels.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "status.xml" in files:
            loadStandardItemModelXML(self.mdlStatus,
                                     files["status.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "plots.xml" in files:
            loadStandardItemModelXML(self.mdlPlots,
                                     files["plots.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "outline.xml" in files:
            self.mdlOutline.loadFromXML(files["outline.xml"], fromString=True)
        else:
            errors.append("perso.xml")

        if "settings.pickle" in files:
            settings.load(files["settings.pickle"], fromString=True)
        else:
            errors.append("perso.xml")

        # Giving some feedback
        if not errors:
            print(self.tr("Project {} loaded.").format(project))
            self.statusBar().showMessage(
                    self.tr("Project {} loaded.").format(project), 5000)
        else:
            print(self.tr("Project {} loaded with some errors:").format(project))
            for e in errors:
                print(self.tr(" * {} wasn't found in project file.").format(e))
            self.statusBar().showMessage(
                    self.tr("Project {} loaded with some errors.").format(project), 5000)

    ###############################################################################
    # MAIN CONNECTIONS
    ###############################################################################

    def makeUIConnections(self):
        "Connections that have to be made once only, event when new project is loaded."
        self.lstPersos.currentItemChanged.connect(self.changeCurrentPerso, AUC)

        self.txtPlotFilter.textChanged.connect(self.lstPlots.setFilter, AUC)
        self.lstPlots.currentItemChanged.connect(self.changeCurrentPlot, AUC)
        self.txtSubPlotSummary.document().contentsChanged.connect(
                self.updateSubPlotSummary, AUC)
        self.lstSubPlots.activated.connect(self.changeCurrentSubPlot, AUC)

        self.btnRedacAddFolder.clicked.connect(self.treeRedacOutline.addFolder, AUC)
        self.btnOutlineAddFolder.clicked.connect(self.treeOutlineOutline.addFolder, AUC)
        self.btnRedacAddText.clicked.connect(self.treeRedacOutline.addText, AUC)
        self.btnOutlineAddText.clicked.connect(self.treeOutlineOutline.addText, AUC)
        self.btnRedacRemoveItem.clicked.connect(self.outlineRemoveItemsRedac, AUC)
        self.btnOutlineRemoveItem.clicked.connect(self.outlineRemoveItemsOutline, AUC)

        self.tabMain.currentChanged.connect(self.toolbar.setCurrentGroup)

    def makeConnections(self):

        # Flat datas (Summary and general infos)
        for widget, col in [
            (self.txtSummarySituation, 0),
            (self.txtSummarySentence, 1),
            (self.txtSummarySentence_2, 1),
            (self.txtSummaryPara, 2),
            (self.txtSummaryPara_2, 2),
            (self.txtPlotSummaryPara, 2),
            (self.txtSummaryPage, 3),
            (self.txtSummaryPage_2, 3),
            (self.txtPlotSummaryPage, 3),
            (self.txtSummaryFull, 4),
            (self.txtPlotSummaryFull, 4),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(1, col))

        for widget, col in [
            (self.txtGeneralTitle, 0),
            (self.txtGeneralSubtitle, 1),
            (self.txtGeneralSerie, 2),
            (self.txtGeneralVolume, 3),
            (self.txtGeneralGenre, 4),
            (self.txtGeneralLicense, 5),
            (self.txtGeneralAuthor, 6),
            (self.txtGeneralEmail, 7),
        ]:
            widget.setModel(self.mdlFlatData)
            widget.setColumn(col)
            widget.setCurrentModelIndex(self.mdlFlatData.index(0, col))

        # Persos
        self.lstPersos.setPersosModel(self.mdlPersos)
        self.tblPersoInfos.setModel(self.mdlPersos)

        self.btnAddPerso.clicked.connect(self.mdlPersos.addPerso, AUC)
        self.btnRmPerso.clicked.connect(self.mdlPersos.removePerso, AUC)
        self.btnPersoColor.clicked.connect(self.mdlPersos.chosePersoColor, AUC)

        self.btnPersoAddInfo.clicked.connect(self.mdlPersos.addPersoInfo, AUC)
        self.btnPersoRmInfo.clicked.connect(self.mdlPersos.removePersoInfo, AUC)

        for w, c in [
            (self.txtPersoName, Perso.name.value),
            (self.sldPersoImportance, Perso.importance.value),
            (self.txtPersoMotivation, Perso.motivation.value),
            (self.txtPersoGoal, Perso.goal.value),
            (self.txtPersoConflict, Perso.conflict.value),
            (self.txtPersoEpiphany, Perso.epiphany.value),
            (self.txtPersoSummarySentence, Perso.summarySentence.value),
            (self.txtPersoSummaryPara, Perso.summaryPara.value),
            (self.txtPersoSummaryFull, Perso.summaryFull.value),
            (self.txtPersoNotes, Perso.notes.value)
        ]:
            w.setModel(self.mdlPersos)
            w.setColumn(c)
        self.tabPersos.setEnabled(False)

        # Plots
        self.lstPlots.setPlotModel(self.mdlPlots)
        self.lstPlotPerso.setModel(self.mdlPlots)
        self.lstSubPlots.setModel(self.mdlPlots)
        self._updatingSubPlot = False
        self.btnAddPlot.clicked.connect(self.mdlPlots.addPlot, AUC)
        self.btnRmPlot.clicked.connect(lambda:
                                       self.mdlPlots.removePlot(self.lstPlots.currentPlotIndex()), AUC)
        self.btnAddSubPlot.clicked.connect(self.mdlPlots.addSubPlot, AUC)
        self.btnRmSubPlot.clicked.connect(self.mdlPlots.removeSubPlot, AUC)
        self.lstPlotPerso.selectionModel().selectionChanged.connect(self.plotPersoSelectionChanged)
        self.btnRmPlotPerso.clicked.connect(self.mdlPlots.removePlotPerso, AUC)

        for w, c in [
            (self.txtPlotName, Plot.name.value),
            (self.txtPlotDescription, Plot.description.value),
            (self.txtPlotResult, Plot.result.value),
            (self.sldPlotImportance, Plot.importance.value),
        ]:
            w.setModel(self.mdlPlots)
            w.setColumn(c)

        self.tabPlot.setEnabled(False)
        self.mdlPlots.updatePlotPersoButton()
        self.mdlPersos.dataChanged.connect(self.mdlPlots.updatePlotPersoButton)
        self.lstOutlinePlots.setPlotModel(self.mdlPlots)
        self.lstOutlinePlots.setShowSubPlot(True)
        self.plotPersoDelegate = outlinePersoDelegate(self.mdlPersos, self)
        self.lstPlotPerso.setItemDelegate(self.plotPersoDelegate)
        self.plotDelegate = plotDelegate(self)
        self.lstSubPlots.setItemDelegateForColumn(Subplot.meta.value, self.plotDelegate)

        # World
        self.treeWorld.setModel(self.mdlWorld)
        for i in range(self.mdlWorld.columnCount()):
            self.treeWorld.hideColumn(i)
        self.treeWorld.showColumn(0)
        self.btnWorldEmptyData.setMenu(self.mdlWorld.emptyDataMenu())
        self.treeWorld.selectionModel().selectionChanged.connect(self.changeCurrentWorld, AUC)
        self.btnAddWorld.clicked.connect(self.mdlWorld.addItem, AUC)
        self.btnRmWorld.clicked.connect(self.mdlWorld.removeItem, AUC)
        for w, c in [
            (self.txtWorldName, World.name.value),
            (self.txtWorldDescription, World.description.value),
            (self.txtWorldPassion, World.passion.value),
            (self.txtWorldConflict, World.conflict.value),
        ]:
            w.setModel(self.mdlWorld)
            w.setColumn(c)
        self.tabWorld.setEnabled(False)
        self.treeWorld.expandAll()

        # Outline
        self.treeRedacOutline.setModel(self.mdlOutline)
        self.treeOutlineOutline.setModelPersos(self.mdlPersos)
        self.treeOutlineOutline.setModelLabels(self.mdlLabels)
        self.treeOutlineOutline.setModelStatus(self.mdlStatus)

        self.redacMetadata.setModels(self.mdlOutline, self.mdlPersos,
                                     self.mdlLabels, self.mdlStatus)
        self.outlineItemEditor.setModels(self.mdlOutline, self.mdlPersos,
                                         self.mdlLabels, self.mdlStatus)

        self.treeOutlineOutline.setModel(self.mdlOutline)
        # self.redacEditor.setModel(self.mdlOutline)
        self.storylineView.setModels(self.mdlOutline, self.mdlPersos, self.mdlPlots)

        self.treeOutlineOutline.selectionModel().selectionChanged.connect(lambda:
                                                                          self.outlineItemEditor.selectionChanged(
                                                                                  self.treeOutlineOutline), AUC)
        self.treeOutlineOutline.clicked.connect(lambda:
                                                self.outlineItemEditor.selectionChanged(self.treeOutlineOutline), AUC)

        # Sync selection
        self.treeRedacOutline.selectionModel().selectionChanged.connect(
                lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC)
        self.treeRedacOutline.clicked.connect(
                lambda: self.redacMetadata.selectionChanged(self.treeRedacOutline), AUC)

        self.treeRedacOutline.selectionModel().selectionChanged.connect(self.mainEditor.selectionChanged, AUC)

        # Cheat Sheet
        self.cheatSheet.setModels()

        # Debug
        self.mdlFlatData.setVerticalHeaderLabels(["Infos générales", "Summary"])
        self.tblDebugFlatData.setModel(self.mdlFlatData)
        self.tblDebugPersos.setModel(self.mdlPersos)
        self.tblDebugPersosInfos.setModel(self.mdlPersos)
        self.tblDebugPersos.selectionModel().currentChanged.connect(
                lambda: self.tblDebugPersosInfos.setRootIndex(self.mdlPersos.index(
                        self.tblDebugPersos.selectionModel().currentIndex().row(),
                        Perso.name.value)), AUC)

        self.tblDebugPlots.setModel(self.mdlPlots)
        self.tblDebugPlotsPersos.setModel(self.mdlPlots)
        self.tblDebugSubPlots.setModel(self.mdlPlots)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
                lambda: self.tblDebugPlotsPersos.setRootIndex(self.mdlPlots.index(
                        self.tblDebugPlots.selectionModel().currentIndex().row(),
                        Plot.persos.value)), AUC)
        self.tblDebugPlots.selectionModel().currentChanged.connect(
                lambda: self.tblDebugSubPlots.setRootIndex(self.mdlPlots.index(
                        self.tblDebugPlots.selectionModel().currentIndex().row(),
                        Plot.subplots.value)), AUC)
        self.treeDebugWorld.setModel(self.mdlWorld)
        self.treeDebugOutline.setModel(self.mdlOutline)
        self.lstDebugLabels.setModel(self.mdlLabels)
        self.lstDebugStatus.setModel(self.mdlStatus)

    ###############################################################################
    # GENERAL AKA UNSORTED
    ###############################################################################

    def clickCycle(self, i):
        if i == 0:  # step 2 - paragraph summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(1)
        if i == 1:  # step 3 - characters summary
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(0)
        if i == 2:  # step 4 - page summary
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(2)
        if i == 3:  # step 5 - characters description
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(1)
        if i == 4:  # step 6 - four page synopsis
            self.tabMain.setCurrentIndex(self.TabSummary)
            self.tabSummary.setCurrentIndex(3)
        if i == 5:  # step 7 - full character charts
            self.tabMain.setCurrentIndex(self.TabPersos)
            self.tabPersos.setCurrentIndex(2)
        if i == 6:  # step 8 - scene list
            self.tabMain.setCurrentIndex(self.TabPlots)

    def wordCount(self, i):

        src = {
            0: self.txtSummarySentence,
            1: self.txtSummaryPara,
            2: self.txtSummaryPage,
            3: self.txtSummaryFull
        }[i]

        lbl = {
            0: self.lblSummaryWCSentence,
            1: self.lblSummaryWCPara,
            2: self.lblSummaryWCPage,
            3: self.lblSummaryWCFull
        }[i]

        wc = wordCount(src.toPlainText())
        if i in [2, 3]:
            pages = self.tr(" (~{} pages)").format(int(wc / 25) / 10.)
        else:
            pages = ""
        lbl.setText(self.tr("Words: {}{}").format(wc, pages))

    def setupMoreUi(self):

        # Tool bar on the right
        self.toolbar = collapsibleDockWidgets(Qt.RightDockWidgetArea, self)
        self.toolbar.addCustomWidget(self.tr("Book summary"), self.grpPlotSummary, self.TabPlots)
        self.toolbar.addCustomWidget(self.tr("Project tree"), self.treeRedacWidget, self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Metadata"), self.redacMetadata, self.TabRedac)
        self.toolbar.addCustomWidget(self.tr("Story line"), self.storylineView, self.TabRedac)
        if self._toolbarState:
            self.toolbar.restoreState(self._toolbarState)

        # Custom "tab" bar on the left
        self.lstTabs.setIconSize(QSize(48, 48))
        for i in range(self.tabMain.count()):
            icons = ["general-128px.png",
                     "summary-128px.png",
                     "characters-128px.png",
                     "plot-128px.png",
                     "world-128px.png",
                     "outline-128px.png",
                     "redaction-128px.png",
                     ""
                     ]
            self.tabMain.setTabIcon(i, QIcon(appPath("icons/Custom/Tabs/{}".format(icons[i]))))
            item = QListWidgetItem(self.tabMain.tabIcon(i),
                                   self.tabMain.tabText(i))
            item.setSizeHint(QSize(item.sizeHint().width(), 64))
            item.setTextAlignment(Qt.AlignCenter)
            self.lstTabs.addItem(item)
        self.tabMain.tabBar().hide()
        self.lstTabs.currentRowChanged.connect(self.tabMain.setCurrentIndex)
        self.tabMain.currentChanged.connect(self.lstTabs.setCurrentRow)

        # Splitters
        self.splitterPersos.setStretchFactor(0, 25)
        self.splitterPersos.setStretchFactor(1, 75)

        self.splitterPlot.setStretchFactor(0, 20)
        self.splitterPlot.setStretchFactor(1, 60)
        self.splitterPlot.setStretchFactor(2, 30)

        self.splitterWorld.setStretchFactor(0, 25)
        self.splitterWorld.setStretchFactor(1, 75)

        self.splitterOutlineH.setStretchFactor(0, 25)
        self.splitterOutlineH.setStretchFactor(1, 75)
        self.splitterOutlineV.setStretchFactor(0, 75)
        self.splitterOutlineV.setStretchFactor(1, 25)

        self.splitterRedacV.setStretchFactor(0, 75)
        self.splitterRedacV.setStretchFactor(1, 25)

        self.splitterRedacH.setStretchFactor(0, 30)
        self.splitterRedacH.setStretchFactor(1, 40)
        self.splitterRedacH.setStretchFactor(2, 30)

        # QFormLayout stretch
        for w in [self.txtWorldDescription, self.txtWorldPassion, self.txtWorldConflict]:
            s = w.sizePolicy()
            s.setVerticalStretch(1)
            w.setSizePolicy(s)

        # Help box
        references = [
            (self.lytTabOverview,
             self.tr("Enter infos about your book, and yourself."),
             0),
            (self.lytSituation,
             self.tr(
                     """The basic situation, in the form of a 'What if...?' question. Ex: 'What if the most dangerous
                     evil wizard could wasn't abled to kill a baby?' (Harry Potter)"""),
             1),
            (self.lytSummary,
             self.tr(
                     """Take time to think about a one sentence (~50 words) summary of your book. Then expand it to
                     a paragraph, then to a page, then to a full summary."""),
             1),
            (self.lytTabPersos,
             self.tr("Create your characters."),
             0),
            (self.lytTabPlot,
             self.tr("Develop plots."),
             0),
            (self.lytTabOutline,
             self.tr("Create the outline of your masterpiece."),
             0),
            (self.lytTabRedac,
             self.tr("Write."),
             0),
            (self.lytTabDebug,
             self.tr("Debug infos. Sometimes useful."),
             0)
        ]

        for widget, text, pos in references:
            label = helpLabel(text, self)
            self.actShowHelp.toggled.connect(label.setVisible, AUC)
            widget.layout().insertWidget(pos, label)

        self.actShowHelp.setChecked(False)

        # Spellcheck
        if enchant:
            self.menuDict = QMenu(self.tr("Dictionary"))
            self.menuDictGroup = QActionGroup(self)
            self.updateMenuDict()
            self.menuTools.addMenu(self.menuDict)

            self.actSpellcheck.toggled.connect(self.toggleSpellcheck, AUC)
            self.dictChanged.connect(self.mainEditor.setDict, AUC)
            self.dictChanged.connect(self.redacMetadata.setDict, AUC)
            self.dictChanged.connect(self.outlineItemEditor.setDict, AUC)

        else:
            # No Spell check support
            self.actSpellcheck.setVisible(False)
            a = QAction(self.tr("Install PyEnchant to use spellcheck"), self)
            a.setIcon(self.style().standardIcon(QStyle.SP_MessageBoxWarning))
            a.triggered.connect(self.openPyEnchantWebPage, AUC)
            self.menuTools.addAction(a)

    ###############################################################################
    # SPELLCHECK
    ###############################################################################

    def updateMenuDict(self):

        if not enchant:
            return

        self.menuDict.clear()
        for i in enchant.list_dicts():
            a = QAction(str(i[0]), self)
            a.setCheckable(True)
            if settings.dict is None:
                settings.dict = enchant.get_default_language()
            if str(i[0]) == settings.dict:
                a.setChecked(True)
            a.triggered.connect(self.setDictionary, AUC)
            self.menuDictGroup.addAction(a)
            self.menuDict.addAction(a)

    def setDictionary(self):
        if not enchant:
            return

        for i in self.menuDictGroup.actions():
            if i.isChecked():
                # self.dictChanged.emit(i.text().replace("&", ""))
                settings.dict = i.text().replace("&", "")

                # Find all textEditView from self, and toggle spellcheck
                for w in self.findChildren(textEditView, QRegExp(".*"),
                                           Qt.FindChildrenRecursively):
                    w.setDict(settings.dict)

    def openPyEnchantWebPage(self):
        from PyQt5.QtGui import QDesktopServices
        QDesktopServices.openUrl(QUrl("http://pythonhosted.org/pyenchant/"))

    def toggleSpellcheck(self, val):
        settings.spellcheck = val

        # Find all textEditView from self, and toggle spellcheck
        for w in self.findChildren(textEditView, QRegExp(".*"),
                                   Qt.FindChildrenRecursively):
            w.toggleSpellcheck(val)

    ###############################################################################
    # SETTINGS
    ###############################################################################

    def settingsLabel(self):
        self.settingsWindow(3)

    def settingsStatus(self):
        self.settingsWindow(4)

    def settingsWindow(self, tab=None):
        self.sw = settingsWindow(self)
        self.sw.hide()
        self.sw.setWindowModality(Qt.ApplicationModal)
        self.sw.setWindowFlags(Qt.Dialog)
        r = self.sw.geometry()
        r2 = self.geometry()
        self.sw.move(r2.center() - r.center())
        if tab:
            self.sw.setTab(tab)
        self.sw.show()

    ###############################################################################
    # TOOLS
    ###############################################################################

    def frequencyAnalyzer(self):
        self.fw = frequencyAnalyzer(self)
        self.fw.show()

    ###############################################################################
    # VIEW MENU
    ###############################################################################

    def generateViewMenu(self):

        values = [
            (self.tr("Nothing"), "Nothing"),
            (self.tr("POV"), "POV"),
            (self.tr("Label"), "Label"),
            (self.tr("Progress"), "Progress"),
            (self.tr("Compile"), "Compile"),
        ]

        menus = [
            (self.tr("Tree"), "Tree"),
            (self.tr("Index cards"), "Cork"),
            (self.tr("Outline"), "Outline")
        ]

        submenus = {
            "Tree": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
            "Cork": [
                (self.tr("Icon"), "Icon"),
                (self.tr("Text"), "Text"),
                (self.tr("Background"), "Background"),
                (self.tr("Border"), "Border"),
                (self.tr("Corner"), "Corner"),
            ],
            "Outline": [
                (self.tr("Icon color"), "Icon"),
                (self.tr("Text color"), "Text"),
                (self.tr("Background color"), "Background"),
            ],
        }

        self.menuView.clear()

        # print("Generating menus with", settings.viewSettings)

        for mnu, mnud in menus:
            m = QMenu(mnu, self.menuView)
            for s, sd in submenus[mnud]:
                m2 = QMenu(s, m)
                agp = QActionGroup(m2)
                for v, vd in values:
                    a = QAction(v, m)
                    a.setCheckable(True)
                    a.setData("{},{},{}".format(mnud, sd, vd))
                    if settings.viewSettings[mnud][sd] == vd:
                        a.setChecked(True)
                    a.triggered.connect(self.setViewSettingsAction, AUC)
                    agp.addAction(a)
                    m2.addAction(a)
                m.addMenu(m2)
            self.menuView.addMenu(m)

    def setViewSettingsAction(self):
        action = self.sender()
        item, part, element = action.data().split(",")
        self.setViewSettings(item, part, element)

    def setViewSettings(self, item, part, element):
        settings.viewSettings[item][part] = element
        if item == "Cork":
            self.mainEditor.updateCorkView()
        if item == "Outline":
            self.mainEditor.updateTreeView()
            self.treeOutlineOutline.viewport().update()
        if item == "Tree":
            self.treeRedacOutline.viewport().update()

    ###############################################################################
    # COMPILE
    ###############################################################################

    def doCompile(self):
        self.compileDialog = compileDialog()
        self.compileDialog.show()
class MainWindow(QMainWindow, WindowSystemController):

    def __init__(self, parent):
        super(MainWindow, self).__init__()
        self.init_ui()

        cfg.core.subscriber.subscribe_update_func_to_domain(
            self._clear_project,  "core.project.close")
        cfg.core.subscriber.subscribe_update_func_to_domain(
            self._enable_actions,  "core.project.load")
        cfg.core.subscriber.subscribe_update_func_to_domain(
            self.set_project_is_saved, "core.project.saved")
        cfg.core.subscriber.subscribe_update_func_to_domain(
            self.set_project_is_not_saved, "core.project.notsaved")

    def init_ui(self):
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        widget = QWidget()
        layout = QHBoxLayout()

        self.stackWidget = SubWindowStack(self)
        self.side_bar = SideBar(self)
        # self.side_bar.detach_signal.connect(self.detach_window)

        layout.addWidget(self.side_bar)
        layout.addWidget(self.stackWidget)
        # layout.addWidget(self.sub_window)

        widget.setLayout(layout)
        self.setCentralWidget(widget)

        # window system :
        self.window_system_parent_widget = self.stackWidget
        self.side_bar.window_system_controller = self

        # sub_windows :
        self._sub_window_action_group = QActionGroup(self)

        # write window
        self.write_sub_window = WritePanel(
            parent=self, parent_window_system_controller=self)
        self.attach_sub_window(self.write_sub_window)
        self.ui.actionWrite.setProperty(
            "sub_window_object_name", "write_sub_window")
        self.add_action_to_window_system(self.ui.actionWrite)
        self._sub_window_action_group.addAction(self.ui.actionWrite)

        # binder window
        self.binder_sub_window = BinderPanel(
            parent=self, parent_window_system_controller=self)
        self.attach_sub_window(self.binder_sub_window)
        self.ui.actionBinder.setProperty(
            "sub_window_object_name", "binder_sub_window")
        self.add_action_to_window_system(self.ui.actionBinder)
        self._sub_window_action_group.addAction(self.ui.actionBinder)

        self.ui.actionWrite.trigger()

        # menu bar actions
        self.ui.actionOpen_test_project.triggered.connect(
            self.launch_open_test_project)
        self.ui.actionPreferences.triggered.connect(self.launch_preferences)
        self.ui.actionStart_window.triggered.connect(self.launch_start_window)
        self.ui.actionSave.triggered.connect(cfg.core.project.save)
        self.ui.actionSave_as.triggered.connect(self.launch_save_as_dialog)
        self.ui.actionOpen.triggered.connect(self.launch_open_dialog)
        self.ui.actionClose_project.triggered.connect(self.launch_close_dialog)
        self.ui.actionExit.triggered.connect(self.launch_exit_dialog)

        self._enable_actions(False)

    @pyqtSlot()
    def launch_open_test_project(self):
        if cfg.core.project.is_open() == True:
            if self.launch_close_dialog() == QMessageBox.Cancel:
                return
        cfg.core.project.open_test_project()
        self.setWindowTitle("Plume Creator - TEST")

    @pyqtSlot()
    def launch_preferences(self):
        pref = Preferences(self)
        pref.exec_()

    @pyqtSlot()
    def launch_start_window(self):
        pref = StartWindow(self)
        pref.exec_()

    @pyqtSlot()
    def launch_save_as_dialog(self):
        working_directory = QDir.homePath()
        fileName, selectedFilter = QFileDialog.getSaveFileName(
            self,
            _("Save as"),
            working_directory,
            _("Databases (*.sqlite *.plume);;All files (*)"),
            _(".sqlite"))
        if fileName is None:
            return
        cfg.core.project.save_as(fileName,  selectedFilter)

    @pyqtSlot()
    def launch_open_dialog(self):
        working_directory = QDir.homePath()
        fileName, selectedFilter = QFileDialog.getOpenFileName(
            self,
            _("Open"),
            working_directory,
            _("Databases (*.sqlite *.plume);;All files (*)"),
            _(".sqlite"))

        if fileName is None:
            return
        if cfg.core.project.is_open() == True:
            if self.launch_close_dialog() == QMessageBox.Cancel:
                return
        cfg.core.project.open(fileName)

        self.setWindowTitle("Plume Creator - " + fileName)

    def launch_close_dialog(self):
        if cfg.core.project.is_open() == False:
            return

        result = QMessageBox.question(
            self,
            _("Close the current project"),
            _("The last changes are not yet saved. Do you really want to close the current project ?"),
            QMessageBox.StandardButtons(
                QMessageBox.Cancel |
                QMessageBox.Discard |
                QMessageBox.Save),
            QMessageBox.Cancel)

        if result == QMessageBox.Cancel:
            return QMessageBox.Cancel
        elif result == QMessageBox.Discard:
            cfg.core.project.close_project()
        elif result == QMessageBox.Save:
            cfg.core.project.save()
            cfg.core.project.close_project()

    def launch_exit_dialog(self):
        if cfg.core.project.is_open() == False:
            QApplication.quit()

        result = self.launch_close_dialog()
        if result == QMessageBox.Cancel:
            return QMessageBox.Cancel
        QApplication.quit()

    def set_project_is_saved(self):
        self.ui.actionSave.setEnabled(False)

    def set_project_is_not_saved(self):
        self.ui.actionSave.setEnabled(True)

    def _clear_project(self):
        self.setWindowTitle("Plume Creator")
        self._enable_actions(False)

    def _enable_actions(self,  value=True):
        self.ui.actionSave_as.setEnabled(value)
        self.ui.actionSave.setEnabled(value)
        self.ui.actionClose_project.setEnabled(value)
        self.ui.actionImport.setEnabled(value)
        self.ui.actionExport.setEnabled(value)
        self.ui.actionPrint.setEnabled(value)

    def closeEvent(self,  event):
        result = self.launch_exit_dialog()
        if result == QMessageBox.Cancel:
            event.ignore()
        else:
            event.accept()
Esempio n. 42
0
 def _get_menu(self):
     # main menu
     menu = QMenu()
     main_menu_action_group = QActionGroup(menu)
     main_menu_action_group.setObjectName("main")
     # character menu
     map_action = QAction(menu)
     map_action.setText("Toggle Map")
     main_menu_action_group.addAction(map_action)
     separator = QAction(menu)
     separator.setSeparator(True)
     main_menu_action_group.addAction(separator)
     characters_action = QAction(menu)
     characters_action.setText("Switch Characters")
     main_menu_action_group.addAction(characters_action)
     separator = QAction(menu)
     separator.setSeparator(True)
     main_menu_action_group.addAction(separator)
     settings_action = QAction(menu)
     settings_action.setText("Settings")
     main_menu_action_group.addAction(settings_action)
     quit_action = QAction(menu)
     quit_action.setText("Quit")
     main_menu_action_group.addAction(quit_action)
     menu.addActions(main_menu_action_group.actions())
     menu.triggered[QAction].connect(self._menu_actions)
     return menu
Esempio n. 43
0
class SpreadSheet(QMainWindow):

    dateFormats = ["dd/M/yyyy", "yyyy/M/dd", "dd.MM.yyyy"]

    currentDateFormat = dateFormats[0]

    def __init__(self, rows, cols, parent = None):
        super(SpreadSheet, self).__init__(parent)

        self.toolBar = QToolBar()
        self.addToolBar(self.toolBar)
        self.formulaInput = QLineEdit()
        self.cellLabel = QLabel(self.toolBar)
        self.cellLabel.setMinimumSize(80, 0)
        self.toolBar.addWidget(self.cellLabel)
        self.toolBar.addWidget(self.formulaInput)
        self.table = QTableWidget(rows, cols, self)
        for c in range(cols):
            character = chr(ord('A') + c)
            self.table.setHorizontalHeaderItem(c, QTableWidgetItem(character))

        self.table.setItemPrototype(self.table.item(rows - 1, cols - 1))
        self.table.setItemDelegate(SpreadSheetDelegate(self))
        self.createActions()
        self.updateColor(0)
        self.setupMenuBar()
        self.setupContents()
        self.setupContextMenu()
        self.setCentralWidget(self.table)
        self.statusBar()
        self.table.currentItemChanged.connect(self.updateStatus)
        self.table.currentItemChanged.connect(self.updateColor)
        self.table.currentItemChanged.connect(self.updateLineEdit)
        self.table.itemChanged.connect(self.updateStatus)
        self.formulaInput.returnPressed.connect(self.returnPressed)
        self.table.itemChanged.connect(self.updateLineEdit)
        self.setWindowTitle("Spreadsheet")

    def createActions(self):
        self.cell_sumAction = QAction("Sum", self)
        self.cell_sumAction.triggered.connect(self.actionSum)

        self.cell_addAction = QAction("&Add", self)
        self.cell_addAction.setShortcut(Qt.CTRL | Qt.Key_Plus)
        self.cell_addAction.triggered.connect(self.actionAdd)

        self.cell_subAction = QAction("&Subtract", self)
        self.cell_subAction.setShortcut(Qt.CTRL | Qt.Key_Minus)
        self.cell_subAction.triggered.connect(self.actionSubtract)

        self.cell_mulAction = QAction("&Multiply", self)
        self.cell_mulAction.setShortcut(Qt.CTRL | Qt.Key_multiply)
        self.cell_mulAction.triggered.connect(self.actionMultiply)

        self.cell_divAction = QAction("&Divide", self)
        self.cell_divAction.setShortcut(Qt.CTRL | Qt.Key_division)
        self.cell_divAction.triggered.connect(self.actionDivide)

        self.fontAction = QAction("Font...", self)
        self.fontAction.setShortcut(Qt.CTRL | Qt.Key_F)
        self.fontAction.triggered.connect(self.selectFont)

        self.colorAction = QAction(QIcon(QPixmap(16, 16)), "Background &Color...", self)
        self.colorAction.triggered.connect(self.selectColor)

        self.clearAction = QAction("Clear", self)
        self.clearAction.setShortcut(Qt.Key_Delete)
        self.clearAction.triggered.connect(self.clear)

        self.aboutSpreadSheet = QAction("About Spreadsheet", self)
        self.aboutSpreadSheet.triggered.connect(self.showAbout)

        self.exitAction = QAction("E&xit", self)
        self.exitAction.setShortcut(QKeySequence.Quit)
        self.exitAction.triggered.connect(QApplication.instance().quit)

        self.printAction = QAction("&Print", self)
        self.printAction.setShortcut(QKeySequence.Print)
        self.printAction.triggered.connect(self.print_)

        self.firstSeparator = QAction(self)
        self.firstSeparator.setSeparator(True)

        self.secondSeparator = QAction(self)
        self.secondSeparator.setSeparator(True)

    def setupMenuBar(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.dateFormatMenu = self.fileMenu.addMenu("&Date format")
        self.dateFormatGroup = QActionGroup(self)
        for f in self.dateFormats:
            action = QAction(f, self, checkable=True,
                    triggered=self.changeDateFormat)
            self.dateFormatGroup.addAction(action)
            self.dateFormatMenu.addAction(action)
            if f == self.currentDateFormat:
                action.setChecked(True)
                
        self.fileMenu.addAction(self.printAction)
        self.fileMenu.addAction(self.exitAction)
        self.cellMenu = self.menuBar().addMenu("&Cell")
        self.cellMenu.addAction(self.cell_addAction)
        self.cellMenu.addAction(self.cell_subAction)
        self.cellMenu.addAction(self.cell_mulAction)
        self.cellMenu.addAction(self.cell_divAction)
        self.cellMenu.addAction(self.cell_sumAction)
        self.cellMenu.addSeparator()
        self.cellMenu.addAction(self.colorAction)
        self.cellMenu.addAction(self.fontAction)
        self.menuBar().addSeparator()
        self.aboutMenu = self.menuBar().addMenu("&Help")
        self.aboutMenu.addAction(self.aboutSpreadSheet)

    def changeDateFormat(self):
        action = self.sender()
        oldFormat = self.currentDateFormat
        newFormat = self.currentDateFormat = action.text()
        for row in range(self.table.rowCount()):
            item = self.table.item(row, 1)
            date = QDate.fromString(item.text(), oldFormat)
            item.setText(date.toString(newFormat))

    def updateStatus(self, item):
        if item and item == self.table.currentItem():
            self.statusBar().showMessage(item.data(Qt.StatusTipRole), 1000)
            self.cellLabel.setText("Cell: (%s)" % encode_pos(self.table.row(item),
                                                                     self.table.column(item)))

    def updateColor(self, item):
        pixmap = QPixmap(16, 16)
        color = QColor()
        if item:
            color = item.backgroundColor()
        if not color.isValid():
            color = self.palette().base().color()
        painter = QPainter(pixmap)
        painter.fillRect(0, 0, 16, 16, color)
        lighter = color.lighter()
        painter.setPen(lighter)
        # light frame
        painter.drawPolyline(QPoint(0, 15), QPoint(0, 0), QPoint(15, 0))
        painter.setPen(color.darker())
        # dark frame
        painter.drawPolyline(QPoint(1, 15), QPoint(15, 15), QPoint(15, 1))
        painter.end()
        self.colorAction.setIcon(QIcon(pixmap))

    def updateLineEdit(self, item):
        if item != self.table.currentItem():
            return
        if item:
            self.formulaInput.setText(item.data(Qt.EditRole))
        else:
            self.formulaInput.clear()

    def returnPressed(self):
        text = self.formulaInput.text()
        row = self.table.currentRow()
        col = self.table.currentColumn()
        item = self.table.item(row, col)
        if not item:
            self.table.setItem(row, col, SpreadSheetItem(text))
        else:
            item.setData(Qt.EditRole, text)
        self.table.viewport().update()

    def selectColor(self):
        item = self.table.currentItem()
        color = item and QColor(item.background()) or self.table.palette().base().color()
        color = QColorDialog.getColor(color, self)
        if not color.isValid():
            return
        selected = self.table.selectedItems()
        if not selected:
            return
        for i in selected:
            i and i.setBackground(color)
        self.updateColor(self.table.currentItem())

    def selectFont(self):
        selected = self.table.selectedItems()
        if not selected:
            return
        font, ok = QFontDialog.getFont(self.font(), self)
        if not ok:
            return
        for i in selected:
            i and i.setFont(font)

    def runInputDialog(self, title, c1Text, c2Text, opText,
                       outText, cell1, cell2, outCell):
        rows = []
        cols = []
        for r in range(self.table.rowCount()):
            rows.append(str(r + 1))
        for c in range(self.table.columnCount()):
            cols.append(chr(ord('A') + c))
        addDialog = QDialog(self)
        addDialog.setWindowTitle(title)
        group = QGroupBox(title, addDialog)
        group.setMinimumSize(250, 100)
        cell1Label = QLabel(c1Text, group)
        cell1RowInput = QComboBox(group)
        c1Row, c1Col = decode_pos(cell1)
        cell1RowInput.addItems(rows)
        cell1RowInput.setCurrentIndex(c1Row)
        cell1ColInput = QComboBox(group)
        cell1ColInput.addItems(cols)
        cell1ColInput.setCurrentIndex(c1Col)
        operatorLabel = QLabel(opText, group)
        operatorLabel.setAlignment(Qt.AlignHCenter)
        cell2Label = QLabel(c2Text, group)
        cell2RowInput = QComboBox(group)
        c2Row, c2Col = decode_pos(cell2)
        cell2RowInput.addItems(rows)
        cell2RowInput.setCurrentIndex(c2Row)
        cell2ColInput = QComboBox(group)
        cell2ColInput.addItems(cols)
        cell2ColInput.setCurrentIndex(c2Col)
        equalsLabel = QLabel("=", group)
        equalsLabel.setAlignment(Qt.AlignHCenter)
        outLabel = QLabel(outText, group)
        outRowInput = QComboBox(group)
        outRow, outCol = decode_pos(outCell)
        outRowInput.addItems(rows)
        outRowInput.setCurrentIndex(outRow)
        outColInput = QComboBox(group)
        outColInput.addItems(cols)
        outColInput.setCurrentIndex(outCol)

        cancelButton = QPushButton("Cancel", addDialog)
        cancelButton.clicked.connect(addDialog.reject)
        okButton = QPushButton("OK", addDialog)
        okButton.setDefault(True)
        okButton.clicked.connect(addDialog.accept)
        buttonsLayout = QHBoxLayout()
        buttonsLayout.addStretch(1)
        buttonsLayout.addWidget(okButton)
        buttonsLayout.addSpacing(10)
        buttonsLayout.addWidget(cancelButton)

        dialogLayout = QVBoxLayout(addDialog)
        dialogLayout.addWidget(group)
        dialogLayout.addStretch(1)
        dialogLayout.addItem(buttonsLayout)

        cell1Layout = QHBoxLayout()
        cell1Layout.addWidget(cell1Label)
        cell1Layout.addSpacing(10)
        cell1Layout.addWidget(cell1ColInput)
        cell1Layout.addSpacing(10)
        cell1Layout.addWidget(cell1RowInput)

        cell2Layout = QHBoxLayout()
        cell2Layout.addWidget(cell2Label)
        cell2Layout.addSpacing(10)
        cell2Layout.addWidget(cell2ColInput)
        cell2Layout.addSpacing(10)
        cell2Layout.addWidget(cell2RowInput)
        outLayout = QHBoxLayout()
        outLayout.addWidget(outLabel)
        outLayout.addSpacing(10)
        outLayout.addWidget(outColInput)
        outLayout.addSpacing(10)
        outLayout.addWidget(outRowInput)
        vLayout = QVBoxLayout(group)
        vLayout.addItem(cell1Layout)
        vLayout.addWidget(operatorLabel)
        vLayout.addItem(cell2Layout)
        vLayout.addWidget(equalsLabel)
        vLayout.addStretch(1)
        vLayout.addItem(outLayout)
        if addDialog.exec_():
            cell1 = cell1ColInput.currentText() + cell1RowInput.currentText()
            cell2 = cell2ColInput.currentText() + cell2RowInput.currentText()
            outCell = outColInput.currentText() + outRowInput.currentText()
            return True, cell1, cell2, outCell

        return False, None, None, None

    def actionSum(self):
        row_first = 0
        row_last = 0
        row_cur = 0
        col_first = 0
        col_last = 0
        col_cur = 0
        selected = self.table.selectedItems()
        if selected:
            first = selected[0]
            last = selected[-1]
            row_first = self.table.row(first)
            row_last = self.table.row(last)
            col_first = self.table.column(first)
            col_last = self.table.column(last)

        current = self.table.currentItem()
        if current:
            row_cur = self.table.row(current)
            col_cur = self.table.column(current)

        cell1 = encode_pos(row_first, col_first)
        cell2 = encode_pos(row_last, col_last)
        out = encode_pos(row_cur, col_cur)
        ok, cell1, cell2, out = self.runInputDialog("Sum cells", "First cell:",
                "Last cell:", u"\N{GREEK CAPITAL LETTER SIGMA}", "Output to:",
                cell1, cell2, out)
        if ok:
            row, col = decode_pos(out)
            self.table.item(row, col).setText("sum %s %s" % (cell1, cell2))

    def actionMath_helper(self, title, op):
        cell1 = "C1"
        cell2 = "C2"
        out = "C3"
        current = self.table.currentItem()
        if current:
            out = encode_pos(self.table.currentRow(), self.table.currentColumn())
        ok, cell1, cell2, out = self.runInputDialog(title, "Cell 1", "Cell 2",
                op, "Output to:", cell1, cell2, out)
        if ok:
            row, col = decode_pos(out)
            self.table.item(row, col).setText("%s %s %s" % (op, cell1, cell2))

    def actionAdd(self):
        self.actionMath_helper("Addition", "+")

    def actionSubtract(self):
        self.actionMath_helper("Subtraction", "-")

    def actionMultiply(self):
        self.actionMath_helper("Multiplication", "*")

    def actionDivide(self):
        self.actionMath_helper("Division", "/")

    def clear(self):
        for i in self.table.selectedItems():
            i.setText("")

    def setupContextMenu(self):
        self.addAction(self.cell_addAction)
        self.addAction(self.cell_subAction)
        self.addAction(self.cell_mulAction)
        self.addAction(self.cell_divAction)
        self.addAction(self.cell_sumAction)
        self.addAction(self.firstSeparator)
        self.addAction(self.colorAction)
        self.addAction(self.fontAction)
        self.addAction(self.secondSeparator)
        self.addAction(self.clearAction)
        self.setContextMenuPolicy(Qt.ActionsContextMenu)

    def setupContents(self):
        titleBackground = QColor(Qt.lightGray)
        titleFont = self.table.font()
        titleFont.setBold(True)
        # column 0
        self.table.setItem(0, 0, SpreadSheetItem("Item"))
        self.table.item(0, 0).setBackground(titleBackground)
        self.table.item(0, 0).setToolTip("This column shows the purchased item/service")
        self.table.item(0, 0).setFont(titleFont)
        self.table.setItem(1, 0, SpreadSheetItem("AirportBus"))
        self.table.setItem(2, 0, SpreadSheetItem("Flight (Munich)"))
        self.table.setItem(3, 0, SpreadSheetItem("Lunch"))
        self.table.setItem(4, 0, SpreadSheetItem("Flight (LA)"))
        self.table.setItem(5, 0, SpreadSheetItem("Taxi"))
        self.table.setItem(6, 0, SpreadSheetItem("Dinner"))
        self.table.setItem(7, 0, SpreadSheetItem("Hotel"))
        self.table.setItem(8, 0, SpreadSheetItem("Flight (Oslo)"))
        self.table.setItem(9, 0, SpreadSheetItem("Total:"))
        self.table.item(9, 0).setFont(titleFont)
        self.table.item(9, 0).setBackground(Qt.lightGray)
        # column 1
        self.table.setItem(0, 1, SpreadSheetItem("Date"))
        self.table.item(0, 1).setBackground(titleBackground)
        self.table.item(0, 1).setToolTip("This column shows the purchase date, double click to change")
        self.table.item(0, 1).setFont(titleFont)
        self.table.setItem(1, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(2, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(3, 1, SpreadSheetItem("15/6/2006"))
        self.table.setItem(4, 1, SpreadSheetItem("21/5/2006"))
        self.table.setItem(5, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(6, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(7, 1, SpreadSheetItem("16/6/2006"))
        self.table.setItem(8, 1, SpreadSheetItem("18/6/2006"))
        self.table.setItem(9, 1, SpreadSheetItem())
        self.table.item(9, 1).setBackground(Qt.lightGray)
        # column 2
        self.table.setItem(0, 2, SpreadSheetItem("Price"))
        self.table.item(0, 2).setBackground(titleBackground)
        self.table.item(0, 2).setToolTip("This column shows the price of the purchase")
        self.table.item(0, 2).setFont(titleFont)
        self.table.setItem(1, 2, SpreadSheetItem("150"))
        self.table.setItem(2, 2, SpreadSheetItem("2350"))
        self.table.setItem(3, 2, SpreadSheetItem("-14"))
        self.table.setItem(4, 2, SpreadSheetItem("980"))
        self.table.setItem(5, 2, SpreadSheetItem("5"))
        self.table.setItem(6, 2, SpreadSheetItem("120"))
        self.table.setItem(7, 2, SpreadSheetItem("300"))
        self.table.setItem(8, 2, SpreadSheetItem("1240"))
        self.table.setItem(9, 2, SpreadSheetItem())
        self.table.item(9, 2).setBackground(Qt.lightGray)
        # column 3
        self.table.setItem(0, 3, SpreadSheetItem("Currency"))
        self.table.item(0, 3).setBackgroundColor(titleBackground)
        self.table.item(0, 3).setToolTip("This column shows the currency")
        self.table.item(0, 3).setFont(titleFont)
        self.table.setItem(1, 3, SpreadSheetItem("NOK"))
        self.table.setItem(2, 3, SpreadSheetItem("NOK"))
        self.table.setItem(3, 3, SpreadSheetItem("EUR"))
        self.table.setItem(4, 3, SpreadSheetItem("EUR"))
        self.table.setItem(5, 3, SpreadSheetItem("USD"))
        self.table.setItem(6, 3, SpreadSheetItem("USD"))
        self.table.setItem(7, 3, SpreadSheetItem("USD"))
        self.table.setItem(8, 3, SpreadSheetItem("USD"))
        self.table.setItem(9, 3, SpreadSheetItem())
        self.table.item(9,3).setBackground(Qt.lightGray)
        # column 4
        self.table.setItem(0, 4, SpreadSheetItem("Ex. Rate"))
        self.table.item(0, 4).setBackground(titleBackground)
        self.table.item(0, 4).setToolTip("This column shows the exchange rate to NOK")
        self.table.item(0, 4).setFont(titleFont)
        self.table.setItem(1, 4, SpreadSheetItem("1"))
        self.table.setItem(2, 4, SpreadSheetItem("1"))
        self.table.setItem(3, 4, SpreadSheetItem("8"))
        self.table.setItem(4, 4, SpreadSheetItem("8"))
        self.table.setItem(5, 4, SpreadSheetItem("7"))
        self.table.setItem(6, 4, SpreadSheetItem("7"))
        self.table.setItem(7, 4, SpreadSheetItem("7"))
        self.table.setItem(8, 4, SpreadSheetItem("7"))
        self.table.setItem(9, 4, SpreadSheetItem())
        self.table.item(9,4).setBackground(Qt.lightGray)
        # column 5
        self.table.setItem(0, 5, SpreadSheetItem("NOK"))
        self.table.item(0, 5).setBackground(titleBackground)
        self.table.item(0, 5).setToolTip("This column shows the expenses in NOK")
        self.table.item(0, 5).setFont(titleFont)
        self.table.setItem(1, 5, SpreadSheetItem("* C2 E2"))
        self.table.setItem(2, 5, SpreadSheetItem("* C3 E3"))
        self.table.setItem(3, 5, SpreadSheetItem("* C4 E4"))
        self.table.setItem(4, 5, SpreadSheetItem("* C5 E5"))
        self.table.setItem(5, 5, SpreadSheetItem("* C6 E6"))
        self.table.setItem(6, 5, SpreadSheetItem("* C7 E7"))
        self.table.setItem(7, 5, SpreadSheetItem("* C8 E8"))
        self.table.setItem(8, 5, SpreadSheetItem("* C9 E9"))
        self.table.setItem(9, 5, SpreadSheetItem("sum F2 F9"))
        self.table.item(9,5).setBackground(Qt.lightGray)

    def showAbout(self):
        QMessageBox.about(self, "About Spreadsheet", """
            <HTML>
            <p><b>This demo shows use of <c>QTableWidget</c> with custom handling for
             individual cells.</b></p>
            <p>Using a customized table item we make it possible to have dynamic
             output in different cells. The content that is implemented for this
             particular demo is:
            <ul>
            <li>Adding two cells.</li>
            <li>Subtracting one cell from another.</li>
            <li>Multiplying two cells.</li>
            <li>Dividing one cell with another.</li>
            <li>Summing the contents of an arbitrary number of cells.</li>
            </HTML>
        """)

    def print_(self):
        printer = QPrinter(QPrinter.ScreenResolution)
        dlg = QPrintPreviewDialog(printer)
        view = PrintView()
        view.setModel(self.table.model())
        dlg.paintRequested.connect(view.print_)
        dlg.exec_()
Esempio n. 44
0
    def device_discovery(self, devs):
        """Called when new devices have been added"""
        for menu in self._all_role_menus:
            role_menu = menu["rolemenu"]
            mux_menu = menu["muxmenu"]
            dev_group = QActionGroup(role_menu, exclusive=True)
            for d in devs:
                dev_node = QAction(d.name, role_menu, checkable=True,
                                   enabled=True)
                role_menu.addAction(dev_node)
                dev_group.addAction(dev_node)
                dev_node.toggled.connect(self._inputdevice_selected)

                map_node = None
                if d.supports_mapping:
                    map_node = QMenu("    Input map", role_menu, enabled=False)
                    map_group = QActionGroup(role_menu, exclusive=True)
                    # Connect device node to map node for easy
                    # enabling/disabling when selection changes and device
                    # to easily enable it
                    dev_node.setData((map_node, d))
                    for c in ConfigManager().get_list_of_configs():
                        node = QAction(c, map_node, checkable=True,
                                       enabled=True)
                        node.toggled.connect(self._inputconfig_selected)
                        map_node.addAction(node)
                        # Connect all the map nodes back to the device
                        # action node where we can access the raw device
                        node.setData(dev_node)
                        map_group.addAction(node)
                        # If this device hasn't been found before, then
                        # select the default mapping for it.
                        if d not in self._available_devices:
                            last_map = Config().get("device_config_mapping")
                            if d.name in last_map and last_map[d.name] == c:
                                node.setChecked(True)
                    role_menu.addMenu(map_node)
                dev_node.setData((map_node, d, mux_menu))

        # Update the list of what devices we found
        # to avoid selecting default mapping for all devices when
        # a new one is inserted
        self._available_devices = ()
        for d in devs:
            self._available_devices += (d,)

        # Only enable MUX nodes if we have enough devies to cover
        # the roles
        for mux_node in self._all_mux_nodes:
            (mux, sub_nodes) = mux_node.data()
            if len(mux.supported_roles()) <= len(self._available_devices):
                mux_node.setEnabled(True)

        # TODO: Currently only supports selecting default mux
        if self._all_mux_nodes[0].isEnabled():
            self._all_mux_nodes[0].setChecked(True)

        # If the previous length of the available devies was 0, then select
        # the default on. If that's not available then select the first
        # on in the list.
        # TODO: This will only work for the "Normal" mux so this will be
        #       selected by default
        if Config().get("input_device") in [d.name for d in devs]:
            for dev_menu in self._all_role_menus[0]["rolemenu"].actions():
                if dev_menu.text() == Config().get("input_device"):
                    dev_menu.setChecked(True)
        else:
            # Select the first device in the first mux (will always be "Normal"
            # mux)
            self._all_role_menus[0]["rolemenu"].actions()[0].setChecked(True)
            logger.info("Select first device")

        self._update_input_device_footer()
Esempio n. 45
0
class ReTextWindow(QMainWindow):
	def __init__(self, parent=None):
		QMainWindow.__init__(self, parent)
		self.resize(950, 700)
		screenRect = QDesktopWidget().screenGeometry()
		if globalSettings.windowGeometry:
			self.restoreGeometry(globalSettings.windowGeometry)
		else:
			self.move((screenRect.width()-self.width())/2, (screenRect.height()-self.height())/2)
		if not screenRect.contains(self.geometry()):
			self.showMaximized()
		if globalSettings.iconTheme:
			QIcon.setThemeName(globalSettings.iconTheme)
		if QIcon.themeName() in ('hicolor', ''):
			if not QFile.exists(icon_path + 'document-new.png'):
				QIcon.setThemeName(get_icon_theme())
		if QFile.exists(icon_path+'retext.png'):
			self.setWindowIcon(QIcon(icon_path+'retext.png'))
		elif QFile.exists('/usr/share/pixmaps/retext.png'):
			self.setWindowIcon(QIcon('/usr/share/pixmaps/retext.png'))
		else:
			self.setWindowIcon(QIcon.fromTheme('retext',
				QIcon.fromTheme('accessories-text-editor')))
		self.tabWidget = QTabWidget(self)
		self.initTabWidget()
		self.setCentralWidget(self.tabWidget)
		self.tabWidget.currentChanged.connect(self.changeIndex)
		self.tabWidget.tabCloseRequested.connect(self.closeTab)
		toolBar = QToolBar(self.tr('File toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, toolBar)
		self.editBar = QToolBar(self.tr('Edit toolbar'), self)
		self.addToolBar(Qt.TopToolBarArea, self.editBar)
		self.searchBar = QToolBar(self.tr('Search toolbar'), self)
		self.addToolBar(Qt.BottomToolBarArea, self.searchBar)
		toolBar.setVisible(not globalSettings.hideToolBar)
		self.editBar.setVisible(not globalSettings.hideToolBar)
		self.actionNew = self.act(self.tr('New'), 'document-new',
			self.createNew, shct=QKeySequence.New)
		self.actionNew.setPriority(QAction.LowPriority)
		self.actionOpen = self.act(self.tr('Open'), 'document-open',
			self.openFile, shct=QKeySequence.Open)
		self.actionOpen.setPriority(QAction.LowPriority)
		self.actionSetEncoding = self.act(self.tr('Set encoding'),
			trig=self.showEncodingDialog)
		self.actionSetEncoding.setEnabled(False)
		self.actionReload = self.act(self.tr('Reload'), 'view-refresh',
			lambda: self.currentTab.readTextFromFile())
		self.actionReload.setEnabled(False)
		self.actionSave = self.act(self.tr('Save'), 'document-save',
			self.saveFile, shct=QKeySequence.Save)
		self.actionSave.setEnabled(False)
		self.actionSave.setPriority(QAction.LowPriority)
		self.actionSaveAs = self.act(self.tr('Save as'), 'document-save-as',
			self.saveFileAs, shct=QKeySequence.SaveAs)
		self.actionNextTab = self.act(self.tr('Next tab'), 'go-next',
			lambda: self.switchTab(1), shct=Qt.CTRL+Qt.Key_PageDown)
		self.actionPrevTab = self.act(self.tr('Previous tab'), 'go-previous',
			lambda: self.switchTab(-1), shct=Qt.CTRL+Qt.Key_PageUp)
		self.actionPrint = self.act(self.tr('Print'), 'document-print',
			self.printFile, shct=QKeySequence.Print)
		self.actionPrint.setPriority(QAction.LowPriority)
		self.actionPrintPreview = self.act(self.tr('Print preview'), 'document-print-preview',
			self.printPreview)
		self.actionViewHtml = self.act(self.tr('View HTML code'), 'text-html', self.viewHtml)
		self.actionChangeEditorFont = self.act(self.tr('Change editor font'),
			trig=self.changeEditorFont)
		self.actionChangePreviewFont = self.act(self.tr('Change preview font'),
			trig=self.changePreviewFont)
		self.actionSearch = self.act(self.tr('Find text'), 'edit-find', shct=QKeySequence.Find)
		self.actionSearch.setCheckable(True)
		self.actionSearch.triggered[bool].connect(self.searchBar.setVisible)
		self.searchBar.visibilityChanged.connect(self.searchBarVisibilityChanged)
		self.actionPreview = self.act(self.tr('Preview'), shct=Qt.CTRL+Qt.Key_E,
			trigbool=self.preview)
		if QIcon.hasThemeIcon('document-preview'):
			self.actionPreview.setIcon(QIcon.fromTheme('document-preview'))
		elif QIcon.hasThemeIcon('preview-file'):
			self.actionPreview.setIcon(QIcon.fromTheme('preview-file'))
		elif QIcon.hasThemeIcon('x-office-document'):
			self.actionPreview.setIcon(QIcon.fromTheme('x-office-document'))
		else:
			self.actionPreview.setIcon(QIcon(icon_path+'document-preview.png'))
		self.actionLivePreview = self.act(self.tr('Live preview'), shct=Qt.CTRL+Qt.Key_L,
		trigbool=self.enableLivePreview)
		menuPreview = QMenu()
		menuPreview.addAction(self.actionLivePreview)
		self.actionPreview.setMenu(menuPreview)
		self.actionTableMode = self.act(self.tr('Table mode'), shct=Qt.CTRL+Qt.Key_T,
			trigbool=lambda x: self.currentTab.editBox.enableTableMode(x))
		if ReTextFakeVimHandler:
			self.actionFakeVimMode = self.act(self.tr('FakeVim mode'),
				shct=Qt.CTRL+Qt.ALT+Qt.Key_V, trigbool=self.enableFakeVimMode)
			if globalSettings.useFakeVim:
				self.actionFakeVimMode.setChecked(True)
				self.enableFakeVimMode(True)
		self.actionFullScreen = self.act(self.tr('Fullscreen mode'), 'view-fullscreen',
			shct=Qt.Key_F11, trigbool=self.enableFullScreen)
		self.actionFullScreen.setPriority(QAction.LowPriority)
		self.actionConfig = self.act(self.tr('Preferences'), icon='preferences-system',
			trig=self.openConfigDialog)
		self.actionConfig.setMenuRole(QAction.PreferencesRole)
		self.actionSaveHtml = self.act('HTML', 'text-html', self.saveFileHtml)
		self.actionPdf = self.act('PDF', 'application-pdf', self.savePdf)
		self.actionOdf = self.act('ODT', 'x-office-document', self.saveOdf)
		self.getExportExtensionsList()
		self.actionQuit = self.act(self.tr('Quit'), 'application-exit', shct=QKeySequence.Quit)
		self.actionQuit.setMenuRole(QAction.QuitRole)
		self.actionQuit.triggered.connect(self.close)
		self.actionUndo = self.act(self.tr('Undo'), 'edit-undo',
			lambda: self.currentTab.editBox.undo(), shct=QKeySequence.Undo)
		self.actionRedo = self.act(self.tr('Redo'), 'edit-redo',
			lambda: self.currentTab.editBox.redo(), shct=QKeySequence.Redo)
		self.actionCopy = self.act(self.tr('Copy'), 'edit-copy',
			lambda: self.currentTab.editBox.copy(), shct=QKeySequence.Copy)
		self.actionCut = self.act(self.tr('Cut'), 'edit-cut',
			lambda: self.currentTab.editBox.cut(), shct=QKeySequence.Cut)
		self.actionPaste = self.act(self.tr('Paste'), 'edit-paste',
			lambda: self.currentTab.editBox.paste(), shct=QKeySequence.Paste)
		self.actionUndo.setEnabled(False)
		self.actionRedo.setEnabled(False)
		self.actionCopy.setEnabled(False)
		self.actionCut.setEnabled(False)
		qApp = QApplication.instance()
		qApp.clipboard().dataChanged.connect(self.clipboardDataChanged)
		self.clipboardDataChanged()
		if enchant_available:
			self.actionEnableSC = self.act(self.tr('Enable'), trigbool=self.enableSpellCheck)
			self.actionSetLocale = self.act(self.tr('Set locale'), trig=self.changeLocale)
		self.actionWebKit = self.act(self.tr('Use WebKit renderer'), trigbool=self.enableWebKit)
		self.actionWebKit.setChecked(globalSettings.useWebKit)
		self.actionShow = self.act(self.tr('Show directory'), 'system-file-manager', self.showInDir)
		self.actionFind = self.act(self.tr('Next'), 'go-next', self.find,
			shct=QKeySequence.FindNext)
		self.actionFindPrev = self.act(self.tr('Previous'), 'go-previous',
			lambda: self.find(back=True), shct=QKeySequence.FindPrevious)
		self.actionCloseSearch = self.act(self.tr('Close'), 'window-close',
			lambda: self.searchBar.setVisible(False))
		self.actionCloseSearch.setPriority(QAction.LowPriority)
		self.actionHelp = self.act(self.tr('Get help online'), 'help-contents', self.openHelp)
		self.aboutWindowTitle = self.tr('About ReText')
		self.actionAbout = self.act(self.aboutWindowTitle, 'help-about', self.aboutDialog)
		self.actionAbout.setMenuRole(QAction.AboutRole)
		self.actionAboutQt = self.act(self.tr('About Qt'))
		self.actionAboutQt.setMenuRole(QAction.AboutQtRole)
		self.actionAboutQt.triggered.connect(qApp.aboutQt)
		availableMarkups = markups.get_available_markups()
		if not availableMarkups:
			print('Warning: no markups are available!')
		self.defaultMarkup = availableMarkups[0] if availableMarkups else None
		if globalSettings.defaultMarkup:
			mc = markups.find_markup_class_by_name(globalSettings.defaultMarkup)
			if mc and mc.available():
				self.defaultMarkup = mc
		if len(availableMarkups) > 1:
			self.chooseGroup = QActionGroup(self)
			markupActions = []
			for markup in availableMarkups:
				markupAction = self.act(markup.name, trigbool=self.markupFunction(markup))
				if markup == self.defaultMarkup:
					markupAction.setChecked(True)
				self.chooseGroup.addAction(markupAction)
				markupActions.append(markupAction)
		self.actionBold = self.act(self.tr('Bold'), shct=QKeySequence.Bold,
			trig=lambda: self.insertFormatting('bold'))
		self.actionItalic = self.act(self.tr('Italic'), shct=QKeySequence.Italic,
			trig=lambda: self.insertFormatting('italic'))
		self.actionUnderline = self.act(self.tr('Underline'), shct=QKeySequence.Underline,
			trig=lambda: self.insertFormatting('underline'))
		self.usefulTags = ('header', 'italic', 'bold', 'underline', 'numbering',
			'bullets', 'image', 'link', 'inline code', 'code block', 'blockquote')
		self.usefulChars = ('deg', 'divide', 'dollar', 'hellip', 'laquo', 'larr',
			'lsquo', 'mdash', 'middot', 'minus', 'nbsp', 'ndash', 'raquo',
			'rarr', 'rsquo', 'times')
		self.formattingBox = QComboBox(self.editBar)
		self.formattingBox.addItem(self.tr('Formatting'))
		self.formattingBox.addItems(self.usefulTags)
		self.formattingBox.activated[str].connect(self.insertFormatting)
		self.symbolBox = QComboBox(self.editBar)
		self.symbolBox.addItem(self.tr('Symbols'))
		self.symbolBox.addItems(self.usefulChars)
		self.symbolBox.activated.connect(self.insertSymbol)
		self.updateStyleSheet()
		menubar = self.menuBar()
		menuFile = menubar.addMenu(self.tr('File'))
		menuEdit = menubar.addMenu(self.tr('Edit'))
		menuHelp = menubar.addMenu(self.tr('Help'))
		menuFile.addAction(self.actionNew)
		menuFile.addAction(self.actionOpen)
		self.menuRecentFiles = menuFile.addMenu(self.tr('Open recent'))
		self.menuRecentFiles.aboutToShow.connect(self.updateRecentFiles)
		menuFile.addAction(self.actionShow)
		menuFile.addAction(self.actionSetEncoding)
		menuFile.addAction(self.actionReload)
		menuFile.addSeparator()
		menuFile.addAction(self.actionSave)
		menuFile.addAction(self.actionSaveAs)
		menuFile.addSeparator()
		menuFile.addAction(self.actionNextTab)
		menuFile.addAction(self.actionPrevTab)
		menuFile.addSeparator()
		menuExport = menuFile.addMenu(self.tr('Export'))
		menuExport.addAction(self.actionSaveHtml)
		menuExport.addAction(self.actionOdf)
		menuExport.addAction(self.actionPdf)
		if self.extensionActions:
			menuExport.addSeparator()
			for action, mimetype in self.extensionActions:
				menuExport.addAction(action)
			menuExport.aboutToShow.connect(self.updateExtensionsVisibility)
		menuFile.addAction(self.actionPrint)
		menuFile.addAction(self.actionPrintPreview)
		menuFile.addSeparator()
		menuFile.addAction(self.actionQuit)
		menuEdit.addAction(self.actionUndo)
		menuEdit.addAction(self.actionRedo)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionCut)
		menuEdit.addAction(self.actionCopy)
		menuEdit.addAction(self.actionPaste)
		menuEdit.addSeparator()
		if enchant_available:
			menuSC = menuEdit.addMenu(self.tr('Spell check'))
			menuSC.addAction(self.actionEnableSC)
			menuSC.addAction(self.actionSetLocale)
		menuEdit.addAction(self.actionSearch)
		menuEdit.addAction(self.actionChangeEditorFont)
		menuEdit.addAction(self.actionChangePreviewFont)
		menuEdit.addSeparator()
		if len(availableMarkups) > 1:
			self.menuMode = menuEdit.addMenu(self.tr('Default markup'))
			for markupAction in markupActions:
				self.menuMode.addAction(markupAction)
		menuFormat = menuEdit.addMenu(self.tr('Formatting'))
		menuFormat.addAction(self.actionBold)
		menuFormat.addAction(self.actionItalic)
		menuFormat.addAction(self.actionUnderline)
		menuEdit.addAction(self.actionWebKit)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionViewHtml)
		menuEdit.addAction(self.actionPreview)
		menuEdit.addAction(self.actionTableMode)
		if ReTextFakeVimHandler:
			menuEdit.addAction(self.actionFakeVimMode)
		menuEdit.addSeparator()
		menuEdit.addAction(self.actionFullScreen)
		menuEdit.addAction(self.actionConfig)
		menuHelp.addAction(self.actionHelp)
		menuHelp.addSeparator()
		menuHelp.addAction(self.actionAbout)
		menuHelp.addAction(self.actionAboutQt)
		toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		toolBar.addAction(self.actionNew)
		toolBar.addSeparator()
		toolBar.addAction(self.actionOpen)
		toolBar.addAction(self.actionSave)
		toolBar.addAction(self.actionPrint)
		toolBar.addSeparator()
		toolBar.addAction(self.actionPreview)
		toolBar.addAction(self.actionFullScreen)
		self.editBar.addAction(self.actionUndo)
		self.editBar.addAction(self.actionRedo)
		self.editBar.addSeparator()
		self.editBar.addAction(self.actionCut)
		self.editBar.addAction(self.actionCopy)
		self.editBar.addAction(self.actionPaste)
		self.editBar.addSeparator()
		self.editBar.addWidget(self.formattingBox)
		self.editBar.addWidget(self.symbolBox)
		self.searchEdit = QLineEdit(self.searchBar)
		self.searchEdit.setPlaceholderText(self.tr('Search'))
		self.searchEdit.returnPressed.connect(self.find)
		self.csBox = QCheckBox(self.tr('Case sensitively'), self.searchBar)
		self.searchBar.addWidget(self.searchEdit)
		self.searchBar.addSeparator()
		self.searchBar.addWidget(self.csBox)
		self.searchBar.addAction(self.actionFindPrev)
		self.searchBar.addAction(self.actionFind)
		self.searchBar.addAction(self.actionCloseSearch)
		self.searchBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
		self.searchBar.setVisible(False)
		self.autoSaveEnabled = globalSettings.autoSave
		if self.autoSaveEnabled:
			timer = QTimer(self)
			timer.start(60000)
			timer.timeout.connect(self.saveAll)
		self.ind = None
		if enchant_available:
			self.sl = globalSettings.spellCheckLocale
			if self.sl:
				try:
					enchant.Dict(self.sl)
				except Exception as e:
					print(e, file=sys.stderr)
					self.sl = None
			if globalSettings.spellCheck:
				self.actionEnableSC.setChecked(True)
		self.fileSystemWatcher = QFileSystemWatcher()
		self.fileSystemWatcher.fileChanged.connect(self.fileChanged)

	def iterateTabs(self):
		for i in range(self.tabWidget.count()):
			yield self.tabWidget.widget(i).tab

	def updateStyleSheet(self):
		if globalSettings.styleSheet:
			sheetfile = QFile(globalSettings.styleSheet)
			sheetfile.open(QIODevice.ReadOnly)
			self.ss = QTextStream(sheetfile).readAll()
			sheetfile.close()
		else:
			palette = QApplication.palette()
			self.ss = 'html { color: %s; }\n' % palette.color(QPalette.WindowText).name()
			self.ss += 'td, th { border: 1px solid #c3c3c3; padding: 0 3px 0 3px; }\n'
			self.ss += 'table { border-collapse: collapse; }\n'

	def initTabWidget(self):
		def dragEnterEvent(e):
			e.acceptProposedAction()
		def dropEvent(e):
			fn = bytes(e.mimeData().data('text/plain')).decode().rstrip()
			if fn.startswith('file:'):
				fn = QUrl(fn).toLocalFile()
			self.openFileWrapper(fn)
		self.tabWidget.setTabsClosable(True)
		self.tabWidget.setAcceptDrops(True)
		self.tabWidget.setMovable(True)
		self.tabWidget.dragEnterEvent = dragEnterEvent
		self.tabWidget.dropEvent = dropEvent

	def act(self, name, icon=None, trig=None, trigbool=None, shct=None):
		if not isinstance(shct, QKeySequence):
			shct = QKeySequence(shct)
		if icon:
			action = QAction(self.actIcon(icon), name, self)
		else:
			action = QAction(name, self)
		if trig:
			action.triggered.connect(trig)
		elif trigbool:
			action.setCheckable(True)
			action.triggered[bool].connect(trigbool)
		if shct:
			action.setShortcut(shct)
		return action

	def actIcon(self, name):
		return QIcon.fromTheme(name, QIcon(icon_path+name+'.png'))

	def printError(self):
		import traceback
		print('Exception occured while parsing document:', file=sys.stderr)
		traceback.print_exc()

	def createTab(self, fileName):
		self.currentTab = ReTextTab(self, fileName,
			previewState=int(globalSettings.livePreviewByDefault))
		self.tabWidget.addTab(self.currentTab.getSplitter(), self.tr("New document"))

	def closeTab(self, ind):
		if self.maybeSave(ind):
			if self.tabWidget.count() == 1:
				self.createTab("")
			currentWidget = self.tabWidget.widget(ind)
			if currentWidget.tab.fileName:
				self.fileSystemWatcher.removePath(currentWidget.tab.fileName)
			del currentWidget.tab
			self.tabWidget.removeTab(ind)

	def docTypeChanged(self):
		markupClass = self.currentTab.getMarkupClass()
		if type(self.currentTab.markup) != markupClass:
			self.currentTab.setMarkupClass(markupClass)
			self.currentTab.updatePreviewBox()
		dtMarkdown = (markupClass == markups.MarkdownMarkup)
		dtMkdOrReST = dtMarkdown or (markupClass == markups.ReStructuredTextMarkup)
		self.formattingBox.setEnabled(dtMarkdown)
		self.symbolBox.setEnabled(dtMarkdown)
		self.actionUnderline.setEnabled(dtMarkdown)
		self.actionBold.setEnabled(dtMkdOrReST)
		self.actionItalic.setEnabled(dtMkdOrReST)
		canReload = bool(self.currentTab.fileName) and not self.autoSaveActive()
		self.actionSetEncoding.setEnabled(canReload)
		self.actionReload.setEnabled(canReload)

	def changeIndex(self, ind):
		self.currentTab = self.tabWidget.currentWidget().tab
		editBox = self.currentTab.editBox
		previewState = self.currentTab.previewState
		self.actionUndo.setEnabled(editBox.document().isUndoAvailable())
		self.actionRedo.setEnabled(editBox.document().isRedoAvailable())
		self.actionCopy.setEnabled(editBox.textCursor().hasSelection())
		self.actionCut.setEnabled(editBox.textCursor().hasSelection())
		self.actionPreview.setChecked(previewState >= PreviewLive)
		self.actionLivePreview.setChecked(previewState == PreviewLive)
		self.actionTableMode.setChecked(editBox.tableModeEnabled)
		self.editBar.setEnabled(previewState < PreviewNormal)
		self.ind = ind
		if self.currentTab.fileName:
			self.setCurrentFile()
		else:
			self.setWindowTitle(self.tr('New document') + '[*]')
			self.docTypeChanged()
		self.modificationChanged(editBox.document().isModified())
		editBox.setFocus(Qt.OtherFocusReason)

	def changeEditorFont(self):
		font, ok = QFontDialog.getFont(globalSettings.editorFont, self)
		if ok:
			globalSettings.editorFont = font
			for tab in self.iterateTabs():
				tab.editBox.updateFont()

	def changePreviewFont(self):
		font, ok = QFontDialog.getFont(globalSettings.font, self)
		if ok:
			globalSettings.font = font
			for tab in self.iterateTabs():
				tab.updatePreviewBox()

	def preview(self, viewmode):
		self.currentTab.previewState = viewmode * 2
		self.actionLivePreview.setChecked(False)
		self.editBar.setDisabled(viewmode)
		self.currentTab.updateBoxesVisibility()
		if viewmode:
			self.currentTab.updatePreviewBox()

	def enableLivePreview(self, livemode):
		self.currentTab.previewState = int(livemode)
		self.actionPreview.setChecked(livemode)
		self.editBar.setEnabled(True)
		self.currentTab.updateBoxesVisibility()
		if livemode:
			self.currentTab.updatePreviewBox()

	def enableWebKit(self, enable):
		globalSettings.useWebKit = enable
		for i in range(self.tabWidget.count()):
			splitter = self.tabWidget.widget(i)
			tab = splitter.tab
			tab.previewBox.disconnectExternalSignals()
			tab.previewBox.setParent(None)
			tab.previewBox.deleteLater()
			tab.previewBox = tab.createPreviewBox(tab.editBox)
			tab.previewBox.setMinimumWidth(125)
			splitter.addWidget(tab.previewBox)
			splitter.setSizes((50, 50))
			tab.updatePreviewBox()
			tab.updateBoxesVisibility()

	def enableCopy(self, copymode):
		self.actionCopy.setEnabled(copymode)
		self.actionCut.setEnabled(copymode)

	def enableFullScreen(self, yes):
		if yes:
			self.showFullScreen()
		else:
			self.showNormal()

	def openConfigDialog(self):
		dlg = ConfigDialog(self)
		dlg.setWindowTitle(self.tr('Preferences'))
		dlg.show()

	def enableFakeVimMode(self, yes):
		globalSettings.useFakeVim = yes
		if yes:
			FakeVimMode.init(self)
			for tab in self.iterateTabs():
				tab.installFakeVimHandler()
		else:
			FakeVimMode.exit(self)

	def enableSpellCheck(self, yes):
		if yes:
			self.setAllDictionaries(enchant.Dict(self.sl or None))
		else:
			self.setAllDictionaries(None)
		globalSettings.spellCheck = yes

	def setAllDictionaries(self, dictionary):
		for tab in self.iterateTabs():
			hl = tab.highlighter
			hl.dictionary = dictionary
			hl.rehighlight()

	def changeLocale(self):
		if self.sl:
			localedlg = LocaleDialog(self, defaultText=self.sl)
		else:
			localedlg = LocaleDialog(self)
		if localedlg.exec() != QDialog.Accepted:
			return
		sl = localedlg.localeEdit.text()
		setdefault = localedlg.checkBox.isChecked()
		if sl:
			try:
				sl = str(sl)
				enchant.Dict(sl)
			except Exception as e:
				QMessageBox.warning(self, '', str(e))
			else:
				self.sl = sl
				self.enableSpellCheck(self.actionEnableSC.isChecked())
		else:
			self.sl = None
			self.enableSpellCheck(self.actionEnableSC.isChecked())
		if setdefault:
			globalSettings.spellCheckLocale = sl

	def searchBarVisibilityChanged(self, visible):
		self.actionSearch.setChecked(visible)
		if visible:
			self.searchEdit.setFocus(Qt.ShortcutFocusReason)

	def find(self, back=False):
		flags = QTextDocument.FindFlags()
		if back:
			flags |= QTextDocument.FindBackward
		if self.csBox.isChecked():
			flags |= QTextDocument.FindCaseSensitively
		text = self.searchEdit.text()
		editBox = self.currentTab.editBox
		cursor = editBox.textCursor()
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		cursor.movePosition(QTextCursor.End if back else QTextCursor.Start)
		newCursor = editBox.document().find(text, cursor, flags)
		if not newCursor.isNull():
			editBox.setTextCursor(newCursor)
			return self.setSearchEditColor(True)
		self.setSearchEditColor(False)

	def setSearchEditColor(self, found):
		palette = self.searchEdit.palette()
		palette.setColor(QPalette.Active, QPalette.Base,
		                 Qt.white if found else QColor(255, 102, 102))
		self.searchEdit.setPalette(palette)

	def showInDir(self):
		if self.currentTab.fileName:
			path = QFileInfo(self.currentTab.fileName).path()
			QDesktopServices.openUrl(QUrl.fromLocalFile(path))
		else:
			QMessageBox.warning(self, '', self.tr("Please, save the file somewhere."))

	def setCurrentFile(self):
		self.setWindowTitle("")
		self.tabWidget.setTabText(self.ind, self.currentTab.getDocumentTitle(baseName=True))
		self.tabWidget.setTabToolTip(self.ind, self.currentTab.fileName or '')
		self.setWindowFilePath(self.currentTab.fileName)
		files = readListFromSettings("recentFileList")
		while self.currentTab.fileName in files:
			files.remove(self.currentTab.fileName)
		files.insert(0, self.currentTab.fileName)
		if len(files) > 10:
			del files[10:]
		writeListToSettings("recentFileList", files)
		QDir.setCurrent(QFileInfo(self.currentTab.fileName).dir().path())
		self.docTypeChanged()

	def createNew(self, text=None):
		self.createTab("")
		self.ind = self.tabWidget.count()-1
		self.tabWidget.setCurrentIndex(self.ind)
		if text:
			self.currentTab.editBox.textCursor().insertText(text)

	def switchTab(self, shift=1):
		self.tabWidget.setCurrentIndex((self.ind + shift) % self.tabWidget.count())

	def updateRecentFiles(self):
		self.menuRecentFiles.clear()
		self.recentFilesActions = []
		filesOld = readListFromSettings("recentFileList")
		files = []
		for f in filesOld:
			if QFile.exists(f):
				files.append(f)
				self.recentFilesActions.append(self.act(f, trig=self.openFunction(f)))
		writeListToSettings("recentFileList", files)
		for action in self.recentFilesActions:
			self.menuRecentFiles.addAction(action)

	def markupFunction(self, markup):
		return lambda: self.setDefaultMarkup(markup)

	def openFunction(self, fileName):
		return lambda: self.openFileWrapper(fileName)

	def extensionFunction(self, data):
		return lambda: \
		self.runExtensionCommand(data['Exec'], data['FileFilter'], data['DefaultExtension'])

	def getExportExtensionsList(self):
		extensions = []
		for extsprefix in datadirs:
			extsdir = QDir(extsprefix+'/export-extensions/')
			if extsdir.exists():
				for fileInfo in extsdir.entryInfoList(['*.desktop', '*.ini'],
				QDir.Files | QDir.Readable):
					extensions.append(self.readExtension(fileInfo.filePath()))
		locale = QLocale.system().name()
		self.extensionActions = []
		for extension in extensions:
			try:
				if ('Name[%s]' % locale) in extension:
					name = extension['Name[%s]' % locale]
				elif ('Name[%s]' % locale.split('_')[0]) in extension:
					name = extension['Name[%s]' % locale.split('_')[0]]
				else:
					name = extension['Name']
				data = {}
				for prop in ('FileFilter', 'DefaultExtension', 'Exec'):
					if 'X-ReText-'+prop in extension:
						data[prop] = extension['X-ReText-'+prop]
					elif prop in extension:
						data[prop] = extension[prop]
					else:
						data[prop] = ''
				action = self.act(name, trig=self.extensionFunction(data))
				if 'Icon' in extension:
					action.setIcon(self.actIcon(extension['Icon']))
				mimetype = extension['MimeType'] if 'MimeType' in extension else None
			except KeyError:
				print('Failed to parse extension: Name is required', file=sys.stderr)
			else:
				self.extensionActions.append((action, mimetype))

	def updateExtensionsVisibility(self):
		markupClass = self.currentTab.getMarkupClass()
		for action in self.extensionActions:
			if markupClass is None:
				action[0].setEnabled(False)
				continue
			mimetype = action[1]
			if mimetype == None:
				enabled = True
			elif markupClass == markups.MarkdownMarkup:
				enabled = (mimetype in ("text/x-retext-markdown", "text/x-markdown"))
			elif markupClass == markups.ReStructuredTextMarkup:
				enabled = (mimetype in ("text/x-retext-rst", "text/x-rst"))
			else:
				enabled = False
			action[0].setEnabled(enabled)

	def readExtension(self, fileName):
		extFile = QFile(fileName)
		extFile.open(QIODevice.ReadOnly)
		extension = {}
		stream = QTextStream(extFile)
		while not stream.atEnd():
			line = stream.readLine()
			if '=' in line:
				index = line.index('=')
				extension[line[:index].rstrip()] = line[index+1:].lstrip()
		extFile.close()
		return extension

	def openFile(self):
		supportedExtensions = ['.txt']
		for markup in markups.get_all_markups():
			supportedExtensions += markup.file_extensions
		fileFilter = ' (' + str.join(' ', ['*'+ext for ext in supportedExtensions]) + ');;'
		fileNames = QFileDialog.getOpenFileNames(self,
			self.tr("Select one or several files to open"), "",
			self.tr("Supported files") + fileFilter + self.tr("All files (*)"))
		for fileName in fileNames[0]:
			self.openFileWrapper(fileName)

	def openFileWrapper(self, fileName):
		if not fileName:
			return
		fileName = QFileInfo(fileName).canonicalFilePath()
		exists = False
		for i, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				exists = True
				ex = i
		if exists:
			self.tabWidget.setCurrentIndex(ex)
		elif QFile.exists(fileName):
			noEmptyTab = (
				(self.ind is None) or
				self.currentTab.fileName or
				self.currentTab.editBox.toPlainText() or
				self.currentTab.editBox.document().isModified()
			)
			if noEmptyTab:
				self.createTab(fileName)
				self.ind = self.tabWidget.count()-1
				self.tabWidget.setCurrentIndex(self.ind)
			if fileName:
				self.fileSystemWatcher.addPath(fileName)
			self.currentTab.fileName = fileName
			self.currentTab.readTextFromFile()
			editBox = self.currentTab.editBox
			self.setCurrentFile()
			self.setWindowModified(editBox.document().isModified())

	def showEncodingDialog(self):
		if not self.maybeSave(self.ind):
			return
		encoding, ok = QInputDialog.getItem(self, '',
			self.tr('Select file encoding from the list:'),
			[bytes(b).decode() for b in QTextCodec.availableCodecs()],
			0, False)
		if ok:
			self.currentTab.readTextFromFile(encoding)

	def saveFile(self):
		self.saveFileMain(dlg=False)

	def saveFileAs(self):
		self.saveFileMain(dlg=True)

	def saveAll(self):
		for tab in self.iterateTabs():
			if tab.fileName and QFileInfo(tab.fileName).isWritable():
				tab.saveTextToFile()
				tab.editBox.document().setModified(False)

	def saveFileMain(self, dlg):
		if (not self.currentTab.fileName) or dlg:
			markupClass = self.currentTab.getMarkupClass()
			if (markupClass is None) or not hasattr(markupClass, 'default_extension'):
				defaultExt = self.tr("Plain text (*.txt)")
				ext = ".txt"
			else:
				defaultExt = self.tr('%s files',
					'Example of final string: Markdown files') \
					% markupClass.name + ' (' + str.join(' ',
					('*'+extension for extension in markupClass.file_extensions)) + ')'
				if markupClass == markups.MarkdownMarkup:
					ext = globalSettings.markdownDefaultFileExtension
				elif markupClass == markups.ReStructuredTextMarkup:
					ext = globalSettings.restDefaultFileExtension
				else:
					ext = markupClass.default_extension
			newFileName = QFileDialog.getSaveFileName(self,
				self.tr("Save file"), "", defaultExt)[0]
			if newFileName:
				if not QFileInfo(newFileName).suffix():
					newFileName += ext
				if self.currentTab.fileName:
					self.fileSystemWatcher.removePath(self.currentTab.fileName)
				self.currentTab.fileName = newFileName
				self.actionSetEncoding.setDisabled(self.autoSaveActive())
		if self.currentTab.fileName:
			if self.currentTab.saveTextToFile():
				self.setCurrentFile()
				self.currentTab.editBox.document().setModified(False)
				self.setWindowModified(False)
				return True
			else:
				QMessageBox.warning(self, '',
				self.tr("Cannot save to file because it is read-only!"))
		return False

	def saveHtml(self, fileName):
		if not QFileInfo(fileName).suffix():
			fileName += ".html"
		try:
			htmltext = self.currentTab.getHtml(includeStyleSheet=False,
				webenv=True)
		except Exception:
			return self.printError()
		htmlFile = QFile(fileName)
		htmlFile.open(QIODevice.WriteOnly)
		html = QTextStream(htmlFile)
		if globalSettings.defaultCodec:
			html.setCodec(globalSettings.defaultCodec)
		html << htmltext
		htmlFile.close()

	def textDocument(self):
		td = QTextDocument()
		td.setMetaInformation(QTextDocument.DocumentTitle,
		                      self.currentTab.getDocumentTitle())
		if self.ss:
			td.setDefaultStyleSheet(self.ss)
		td.setHtml(self.currentTab.getHtml())
		td.setDefaultFont(globalSettings.font)
		return td

	def saveOdf(self):
		try:
			document = self.textDocument()
		except Exception:
			return self.printError()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to ODT"), "",
			self.tr("OpenDocument text files (*.odt)"))[0]
		if not QFileInfo(fileName).suffix():
			fileName += ".odt"
		writer = QTextDocumentWriter(fileName)
		writer.setFormat(b"odf")
		writer.write(document)

	def saveFileHtml(self):
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Save file"), "",
			self.tr("HTML files (*.html *.htm)"))[0]
		if fileName:
			self.saveHtml(fileName)

	def getDocumentForPrint(self):
		if globalSettings.useWebKit:
			return self.currentTab.previewBox
		try:
			return self.textDocument()
		except Exception:
			self.printError()

	def standardPrinter(self):
		printer = QPrinter(QPrinter.HighResolution)
		printer.setDocName(self.currentTab.getDocumentTitle())
		printer.setCreator('ReText %s' % app_version)
		return printer

	def savePdf(self):
		self.currentTab.updatePreviewBox()
		fileName = QFileDialog.getSaveFileName(self,
			self.tr("Export document to PDF"),
			"", self.tr("PDF files (*.pdf)"))[0]
		if fileName:
			if not QFileInfo(fileName).suffix():
				fileName += ".pdf"
			printer = self.standardPrinter()
			printer.setOutputFormat(QPrinter.PdfFormat)
			printer.setOutputFileName(fileName)
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printFile(self):
		self.currentTab.updatePreviewBox()
		printer = self.standardPrinter()
		dlg = QPrintDialog(printer, self)
		dlg.setWindowTitle(self.tr("Print document"))
		if (dlg.exec() == QDialog.Accepted):
			document = self.getDocumentForPrint()
			if document != None:
				document.print(printer)

	def printPreview(self):
		document = self.getDocumentForPrint()
		if document == None:
			return
		printer = self.standardPrinter()
		preview = QPrintPreviewDialog(printer, self)
		preview.paintRequested.connect(document.print)
		preview.exec()

	def runExtensionCommand(self, command, filefilter, defaultext):
		of = ('%of' in command)
		html = ('%html' in command)
		if of:
			if defaultext and not filefilter:
				filefilter = '*'+defaultext
			fileName = QFileDialog.getSaveFileName(self,
				self.tr('Export document'), '', filefilter)[0]
			if not fileName:
				return
			if defaultext and not QFileInfo(fileName).suffix():
				fileName += defaultext
		basename = '.%s.retext-temp' % self.currentTab.getDocumentTitle(baseName=True)
		if html:
			tmpname = basename+'.html'
			self.saveHtml(tmpname)
		else:
			tmpname = basename + self.currentTab.getMarkupClass().default_extension
			self.currentTab.saveTextToFile(fileName=tmpname, addToWatcher=False)
		command = command.replace('%of', '"out'+defaultext+'"')
		command = command.replace('%html' if html else '%if', '"'+tmpname+'"')
		try:
			Popen(str(command), shell=True).wait()
		except Exception as error:
			errorstr = str(error)
			QMessageBox.warning(self, '', self.tr('Failed to execute the command:')
			+ '\n' + errorstr)
		QFile(tmpname).remove()
		if of:
			QFile('out'+defaultext).rename(fileName)

	def autoSaveActive(self, ind=None):
		tab = self.currentTab if ind is None else self.tabWidget.widget(ind).tab
		return (self.autoSaveEnabled and tab.fileName and
			QFileInfo(tab.fileName).isWritable())

	def modificationChanged(self, changed):
		if self.autoSaveActive():
			changed = False
		self.actionSave.setEnabled(changed)
		self.setWindowModified(changed)

	def clipboardDataChanged(self):
		mimeData = QApplication.instance().clipboard().mimeData()
		if mimeData is not None:
			self.actionPaste.setEnabled(mimeData.hasText())

	def insertFormatting(self, formatting):
		cursor = self.currentTab.editBox.textCursor()
		text = cursor.selectedText()
		moveCursorTo = None

		def c(cursor):
			nonlocal moveCursorTo
			moveCursorTo = cursor.position()

		def ensurenl(cursor):
			if not cursor.atBlockStart():
				cursor.insertText('\n\n')

		toinsert = {
			'header': (ensurenl, '# ', text),
			'italic': ('*', text, c, '*'),
			'bold': ('**', text, c, '**'),
			'underline': ('<u>', text, c, '</u>'),
			'numbering': (ensurenl, ' 1. ', text),
			'bullets': (ensurenl, '  * ', text),
			'image': ('![', text or self.tr('Alt text'), c, '](', self.tr('URL'), ')'),
			'link': ('[', text or self.tr('Link text'), c, '](', self.tr('URL'), ')'),
			'inline code': ('`', text, c, '`'),
			'code block': (ensurenl, '    ', text),
			'blockquote': (ensurenl, '> ', text),
		}

		if formatting not in toinsert:
			return

		cursor.beginEditBlock()
		for token in toinsert[formatting]:
			if callable(token):
				token(cursor)
			else:
				cursor.insertText(token)
		cursor.endEditBlock()

		self.formattingBox.setCurrentIndex(0)
		# Bring back the focus on the editor
		self.currentTab.editBox.setFocus(Qt.OtherFocusReason)

		if moveCursorTo:
			cursor.setPosition(moveCursorTo)
			self.currentTab.editBox.setTextCursor(cursor)

	def insertSymbol(self, num):
		if num:
			self.currentTab.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
		self.symbolBox.setCurrentIndex(0)

	def fileChanged(self, fileName):
		ind = None
		for testind, tab in enumerate(self.iterateTabs()):
			if tab.fileName == fileName:
				ind = testind
		if ind is None:
			self.fileSystemWatcher.removePath(fileName)
		self.tabWidget.setCurrentIndex(ind)
		if not QFile.exists(fileName):
			self.currentTab.editBox.document().setModified(True)
			QMessageBox.warning(self, '', self.tr(
				'This file has been deleted by other application.\n'
				'Please make sure you save the file before exit.'))
		elif not self.currentTab.editBox.document().isModified():
			# File was not modified in ReText, reload silently
			self.currentTab.readTextFromFile()
			self.currentTab.updatePreviewBox()
		else:
			text = self.tr(
				'This document has been modified by other application.\n'
				'Do you want to reload the file (this will discard all '
				'your changes)?\n')
			if self.autoSaveEnabled:
				text += self.tr(
					'If you choose to not reload the file, auto save mode will '
					'be disabled for this session to prevent data loss.')
			messageBox = QMessageBox(QMessageBox.Warning, '', text)
			reloadButton = messageBox.addButton(self.tr('Reload'), QMessageBox.YesRole)
			messageBox.addButton(QMessageBox.Cancel)
			messageBox.exec()
			if messageBox.clickedButton() is reloadButton:
				self.currentTab.readTextFromFile()
				self.currentTab.updatePreviewBox()
			else:
				self.autoSaveEnabled = False
				self.currentTab.editBox.document().setModified(True)
		if fileName not in self.fileSystemWatcher.files():
			# https://github.com/retext-project/retext/issues/137
			self.fileSystemWatcher.addPath(fileName)

	def maybeSave(self, ind):
		tab = self.tabWidget.widget(ind).tab
		if self.autoSaveActive(ind):
			tab.saveTextToFile()
			return True
		if not tab.editBox.document().isModified():
			return True
		self.tabWidget.setCurrentIndex(ind)
		ret = QMessageBox.warning(self, '',
			self.tr("The document has been modified.\nDo you want to save your changes?"),
			QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
		if ret == QMessageBox.Save:
			return self.saveFileMain(False)
		elif ret == QMessageBox.Cancel:
			return False
		return True

	def closeEvent(self, closeevent):
		for ind in range(self.tabWidget.count()):
			if not self.maybeSave(ind):
				return closeevent.ignore()
		if globalSettings.saveWindowGeometry and not self.isMaximized():
			globalSettings.windowGeometry = self.saveGeometry()
		closeevent.accept()

	def viewHtml(self):
		htmlDlg = HtmlDialog(self)
		try:
			htmltext = self.currentTab.getHtml(includeStyleSheet=False)
		except Exception:
			return self.printError()
		winTitle = self.currentTab.getDocumentTitle(baseName=True)
		htmlDlg.setWindowTitle(winTitle+" ("+self.tr("HTML code")+")")
		htmlDlg.textEdit.setPlainText(htmltext.rstrip())
		htmlDlg.hl.rehighlight()
		htmlDlg.show()
		htmlDlg.raise_()
		htmlDlg.activateWindow()

	def openHelp(self):
		QDesktopServices.openUrl(QUrl('https://github.com/retext-project/retext/wiki'))

	def aboutDialog(self):
		QMessageBox.about(self, self.aboutWindowTitle,
		'<p><b>' + (self.tr('ReText %s (using PyMarkups %s)') % (app_version, markups.__version__))
		+'</b></p>' + self.tr('Simple but powerful editor'
		' for Markdown and reStructuredText')
		+'</p><p>'+self.tr('Author: Dmitry Shachnev, 2011').replace('2011', '2011–2016')
		+'<br><a href="https://github.com/retext-project/retext">'+self.tr('Website')
		+'</a> | <a href="http://daringfireball.net/projects/markdown/syntax">'
		+self.tr('Markdown syntax')
		+'</a> | <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">'
		+self.tr('reStructuredText syntax')+'</a></p>')

	def setDefaultMarkup(self, markupClass):
		self.defaultMarkup = markupClass
		defaultName = markups.get_available_markups()[0].name
		writeToSettings('defaultMarkup', markupClass.name, defaultName)
		for tab in self.iterateTabs():
			if not tab.fileName:
				tab.setMarkupClass(markupClass)
				tab.updatePreviewBox()
		self.docTypeChanged()
Esempio n. 46
0
    def create_format_actions(self):
        """actions for Format menu"""

        self.copy_format = Action(self.parent,
                                  "&Copy format",
                                  self.parent.workflows.format_copy_format,
                                  icon=Icon.copy_format,
                                  statustip='Copy format of selection to '
                                  'the clipboard')

        self.paste_format = \
            Action(self.parent, "&Paste format",
                   self.parent.workflows.format_paste_format,
                   icon=Icon.paste_format,
                   statustip='Apply format from the clipboard to the selected '
                             'cells')

        self.font = Action(self.parent,
                           "&Font...",
                           self.parent.grid.on_font_dialog,
                           icon=Icon.font_dialog,
                           shortcut='Ctrl+n',
                           statustip='Lauch font dialog')

        self.bold = Action(self.parent,
                           "&Bold",
                           self.parent.grid.on_bold_pressed,
                           icon=Icon.bold,
                           shortcut='Ctrl+b',
                           checkable=True,
                           statustip='Toggle bold font weight for the '
                           'selected cells')

        self.italics = Action(self.parent,
                              "&Italics",
                              self.parent.grid.on_italics_pressed,
                              icon=Icon.italics,
                              shortcut='Ctrl+i',
                              checkable=True,
                              statustip='Toggle italics font style for the '
                              'selected cells')

        self.underline = Action(self.parent,
                                "&Underline",
                                self.parent.grid.on_underline_pressed,
                                icon=Icon.underline,
                                shortcut='Ctrl+u',
                                checkable=True,
                                statustip='Toggle underline for the '
                                'selected cells')

        self.strikethrough = Action(self.parent,
                                    "&Strikethrough",
                                    self.parent.grid.on_strikethrough_pressed,
                                    icon=Icon.strikethrough,
                                    checkable=True,
                                    statustip='Toggle strikethrough for the '
                                    'selected cells')

        self.text = Action(self.parent,
                           "Text renderer",
                           self.parent.grid.on_text_renderer_pressed,
                           icon=Icon.text,
                           checkable=True,
                           statustip='Show cell results as text (default). '
                           'Formats affect the whole cell')

        self.markup = Action(self.parent,
                             "Markup renderer",
                             self.parent.grid.on_markup_renderer_pressed,
                             icon=Icon.markup,
                             checkable=True,
                             statustip='Show cell results as markup, which '
                             'allows partly formatted output')

        self.image = Action(self.parent,
                            "Image renderer",
                            self.parent.grid.on_image_renderer_pressed,
                            icon=Icon.image,
                            checkable=True,
                            statustip='Show cell results as image. A numpy '
                            'array of shape (x, y, 3) '
                            'is expected')
        if matplotlib_figure is not None:
            self.matplotlib = \
                Action(self.parent, "Matplotlib chart renderer",
                       self.parent.grid.on_matplotlib_renderer_pressed,
                       icon=Icon.matplotlib,
                       checkable=True,
                       statustip='Show cell results as matplotlib chart. A '
                                 'numpy array of shape (x, y, 3) is expected')

        renderer_group = QActionGroup(self.parent)
        renderer_group.addAction(self.text)
        renderer_group.addAction(self.markup)
        renderer_group.addAction(self.image)
        if matplotlib_figure is not None:
            renderer_group.addAction(self.matplotlib)

        self.text_color = Action(
            self.parent,
            "Text color...",
            self.parent.widgets.text_color_button.on_pressed,
            icon=Icon.text_color,
            statustip='Lauch text color dialog')

        self.line_color = Action(
            self.parent,
            "Line color...",
            self.parent.widgets.line_color_button.on_pressed,
            icon=Icon.line_color,
            statustip='Lauch line color dialog')

        self.background_color = Action(
            self.parent,
            "Background color...",
            self.parent.widgets.background_color_button.on_pressed,
            icon=Icon.background_color,
            statustip='Lauch background color dialog')

        self.freeze_cell = Action(self.parent,
                                  "Freeze cell",
                                  self.parent.grid.on_freeze_pressed,
                                  icon=Icon.freeze,
                                  checkable=True,
                                  statustip='Freeze the selected cell so that '
                                  'is is only updated when <F5> is '
                                  'pressed')

        self.lock_cell = Action(self.parent,
                                "Lock cell",
                                self.parent.grid.on_lock_pressed,
                                icon=Icon.lock,
                                checkable=True,
                                statustip='Lock cell so that its code '
                                'cannot be changed')

        self.button_cell = Action(self.parent,
                                  "Button cell",
                                  self.parent.grid.on_button_cell_pressed,
                                  icon=Icon.button,
                                  checkable=True,
                                  statustip='Make cell a button cell that is '
                                  'executed only when pressed')

        self.merge_cells = Action(self.parent,
                                  "Merge cells",
                                  self.parent.grid.on_merge_pressed,
                                  icon=Icon.merge_cells,
                                  checkable=True,
                                  statustip='Merge/unmerge selected cells')

        self.rotate_0 = Action(self.parent,
                               "0°",
                               self.parent.grid.on_rotate_0,
                               icon=Icon.rotate_0,
                               checkable=True,
                               statustip='Set text rotation to 0°')

        self.rotate_90 = Action(self.parent,
                                "90°",
                                self.parent.grid.on_rotate_90,
                                icon=Icon.rotate_90,
                                checkable=True,
                                statustip='Set text rotation to 90°')

        self.rotate_180 = Action(self.parent,
                                 "180°",
                                 self.parent.grid.on_rotate_180,
                                 icon=Icon.rotate_180,
                                 checkable=True,
                                 statustip='Set text rotation to 180°')

        self.rotate_270 = Action(self.parent,
                                 "270°",
                                 self.parent.grid.on_rotate_270,
                                 icon=Icon.rotate_270,
                                 checkable=True,
                                 statustip='Set text rotation to 270°')

        rotate_group = QActionGroup(self.parent)
        rotate_group.addAction(self.rotate_0)
        rotate_group.addAction(self.rotate_90)
        rotate_group.addAction(self.rotate_180)
        rotate_group.addAction(self.rotate_270)

        self.justify_left = Action(self.parent,
                                   "Left",
                                   self.parent.grid.on_justify_left,
                                   icon=Icon.justify_left,
                                   checkable=True,
                                   statustip='Display cell result text '
                                   'left justified')

        self.justify_center = Action(self.parent,
                                     "Center",
                                     self.parent.grid.on_justify_center,
                                     checkable=True,
                                     icon=Icon.justify_center,
                                     statustip='Display cell result text '
                                     'centered')

        self.justify_right = Action(self.parent,
                                    "Right",
                                    self.parent.grid.on_justify_right,
                                    checkable=True,
                                    icon=Icon.justify_right,
                                    statustip='Display cell result text '
                                    'right justified')

        self.justify_fill = Action(self.parent,
                                   "Fill",
                                   self.parent.grid.on_justify_fill,
                                   icon=Icon.justify_fill,
                                   checkable=True,
                                   statustip='Display cell result text '
                                   'filled into the cell')

        justify_group = QActionGroup(self.parent)
        justify_group.addAction(self.justify_left)
        justify_group.addAction(self.justify_center)
        justify_group.addAction(self.justify_right)
        justify_group.addAction(self.justify_fill)

        self.align_top = Action(self.parent,
                                "Top",
                                self.parent.grid.on_align_top,
                                icon=Icon.align_top,
                                checkable=True,
                                statustip='Align cell result at the top of '
                                'the cell')

        self.align_center = Action(self.parent,
                                   "Center",
                                   self.parent.grid.on_align_middle,
                                   icon=Icon.align_center,
                                   checkable=True,
                                   statustip='Center cell result within '
                                   'the cell')

        self.align_bottom = Action(self.parent,
                                   "Bottom",
                                   self.parent.grid.on_align_bottom,
                                   icon=Icon.align_bottom,
                                   checkable=True,
                                   statustip='Align cell result at the '
                                   'bottom of the cell')

        align_group = QActionGroup(self.parent)
        align_group.addAction(self.align_top)
        align_group.addAction(self.align_center)
        align_group.addAction(self.align_bottom)

        self.format_borders_all = \
            Action(self.parent, "All borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_all, checkable=True,
                   statustip='Format all borders of selection')

        self.format_borders_top = \
            Action(self.parent, "Top border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_top, checkable=True,
                   statustip='Format top border of selection')

        self.format_borders_bottom = \
            Action(self.parent, "Bottom border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_bottom, checkable=True,
                   statustip='Format bottom border of selection')

        self.format_borders_left = \
            Action(self.parent, "Left border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_left, checkable=True,
                   statustip='Format left border of selection')

        self.format_borders_right = \
            Action(self.parent, "Right border",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_right, checkable=True,
                   statustip='Format right border of selection')

        self.format_borders_outer = \
            Action(self.parent, "Outer borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_outer, checkable=True,
                   statustip='Format outer borders of selection')

        self.format_borders_inner = \
            Action(self.parent, "Inner borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_inner, checkable=True,
                   statustip='Format inner borders of selection')

        self.format_borders_top_bottom = \
            Action(self.parent, "Top and bottom borders",
                   self.parent.grid.on_border_choice,
                   icon=Icon.format_borders_top_bottom, checkable=True,
                   statustip='Format top and bottom borders of selection')

        self.border_group = QActionGroup(self.parent)
        self.border_group.addAction(self.format_borders_all)
        self.border_group.addAction(self.format_borders_top)
        self.border_group.addAction(self.format_borders_bottom)
        self.border_group.addAction(self.format_borders_left)
        self.border_group.addAction(self.format_borders_right)
        self.border_group.addAction(self.format_borders_outer)
        self.border_group.addAction(self.format_borders_inner)
        self.border_group.addAction(self.format_borders_top_bottom)
        self.format_borders_all.setChecked(True)

        self.format_borders_0 = Action(self.parent,
                                       "Border width 0",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_0,
                                       statustip='Set border width to 0')

        self.format_borders_1 = Action(self.parent,
                                       "Border width 1",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_1,
                                       statustip='Set border width to 1')

        self.format_borders_2 = Action(self.parent,
                                       "Border width 2",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_2,
                                       statustip='Set border width to 2')

        self.format_borders_4 = Action(self.parent,
                                       "Border width 4",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_4,
                                       statustip='Set border width to 4')

        self.format_borders_8 = Action(self.parent,
                                       "Border width 8",
                                       self.parent.grid.on_borderwidth,
                                       icon=Icon.format_borders_8,
                                       statustip='Set border width to 8')

        self.format_borders_16 = Action(self.parent,
                                        "Border width 16",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_16,
                                        statustip='Set border width to 16')

        self.format_borders_32 = Action(self.parent,
                                        "Border width 32",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_32,
                                        statustip='Set border width to 32')

        self.format_borders_64 = Action(self.parent,
                                        "Border width 64",
                                        self.parent.grid.on_borderwidth,
                                        icon=Icon.format_borders_64,
                                        statustip='Set border width to 64')

        self.border_width_group = QActionGroup(self.parent)
        self.border_width_group.addAction(self.format_borders_0)
        self.border_width_group.addAction(self.format_borders_1)
        self.border_width_group.addAction(self.format_borders_2)
        self.border_width_group.addAction(self.format_borders_4)
        self.border_width_group.addAction(self.format_borders_8)
        self.border_width_group.addAction(self.format_borders_16)
        self.border_width_group.addAction(self.format_borders_32)
        self.border_width_group.addAction(self.format_borders_64)
        self.format_borders_1.setChecked(True)
Esempio n. 47
0
    def set_menus(self):
        menubar = self.menuBar()

        file_menu = menubar.addMenu("&File")

        for d in ({
                "name": u"&Open...",
                "shct": "Ctrl+O",
                "func": self.open_file
        }, {
                "name": u"&Save HTML...",
                "shct": "Ctrl+S",
                "func": self.save_html
        }, {
                "name": u"&Find...",
                "shct": "Ctrl+F",
                "func": self.show_search_bar
        }, {
                "name": u"&Print...",
                "shct": "Ctrl+P",
                "func": self.print_doc
        }, {
                "name": u"&Quit",
                "shct": "Ctrl+Q",
                "func": self.quit
        }):
            action = QAction(d["name"], self)
            action.setShortcut(QKeySequence(d["shct"]))
            action.triggered.connect(d["func"])
            file_menu.addAction(action)

        view_menu = menubar.addMenu("&View")

        for d in ({
                "name": u"Zoom &In",
                "shct": "Ctrl++",
                "func": self.zoom_in
        }, {
                "name": u"Zoom &Out",
                "shct": "Ctrl+-",
                "func": self.zoom_out
        }, {
                "name": u"&Actual Size",
                "shct": "Ctrl+0",
                "func": self.zoom_reset
        }):
            action = QAction(d["name"], self)
            action.setShortcut(QKeySequence(d["shct"]))
            action.triggered.connect(d["func"])
            view_menu.addAction(action)

        style_menu = menubar.addMenu("&Style")
        style_menu.setStyleSheet("menu-scrollable: 1")
        style_menu.setDisabled(True)

        if os.path.exists(css_dir):
            files = sorted(os.listdir(css_dir))
            files = [f for f in files if f.endswith(".css")]
            if len(files) > 0:
                style_menu.setDisabled(False)
                group = QActionGroup(self, exclusive=True)
                for i, f in enumerate(files, start=1):
                    name = os.path.splitext(f)[0].replace("&", "&&")
                    action = group.addAction(QtWidgets.QAction(name, self))
                    action.triggered.connect(lambda x, stylesheet=f: self.
                                             set_stylesheet(self, stylesheet))
                    if i < 10: action.setShortcut(QKeySequence("Ctrl+%d" % i))
                    action.setCheckable(True)
                    style_menu.addAction(action)
                    if f == self.stylesheet: action.trigger()

        help_menu = menubar.addMenu("&Help")

        for d in (
            {
                "name": u"&About...",
                "func": self.about
            },
            {
                "name": u"&Report an Issue",
                "func": self.report_issue
            },
        ):
            action = QAction(d["name"], self)
            action.triggered.connect(d["func"])
            help_menu.addAction(action)

        # Redefine reload action
        reload_action = self.web_view.page().action(QWebPage.Reload)
        reload_action.setShortcut(QKeySequence.Refresh)
        reload_action.triggered.connect(self.thread1.run)
        self.web_view.addAction(reload_action)

        # Define additional shortcuts
        QShortcut(QKeySequence("j"), self, activated=self.scroll_down)
        QShortcut(QKeySequence("k"), self, activated=self.scroll_up)
        QShortcut(QKeySequence("t"), self, activated=self.toggle_toc)
Esempio n. 48
0
    def init_menu(self):
        layout_load_act = QAction(tr("MenuFile", "Load saved layout..."), self)
        layout_load_act.setShortcut("Ctrl+O")
        layout_load_act.triggered.connect(self.on_layout_load)

        layout_save_act = QAction(tr("MenuFile", "Save current layout..."),
                                  self)
        layout_save_act.setShortcut("Ctrl+S")
        layout_save_act.triggered.connect(self.on_layout_save)

        sideload_json_act = QAction(tr("MenuFile", "Sideload VIA JSON..."),
                                    self)
        sideload_json_act.triggered.connect(self.on_sideload_json)

        download_via_stack_act = QAction(
            tr("MenuFile", "Download VIA definitions"), self)
        download_via_stack_act.triggered.connect(self.load_via_stack_json)

        load_dummy_act = QAction(tr("MenuFile", "Load dummy JSON..."), self)
        load_dummy_act.triggered.connect(self.on_load_dummy)

        exit_act = QAction(tr("MenuFile", "Exit"), self)
        exit_act.setShortcut("Ctrl+Q")
        exit_act.triggered.connect(qApp.exit)

        file_menu = self.menuBar().addMenu(tr("Menu", "File"))
        file_menu.addAction(layout_load_act)
        file_menu.addAction(layout_save_act)
        file_menu.addSeparator()
        file_menu.addAction(sideload_json_act)
        file_menu.addAction(download_via_stack_act)
        file_menu.addAction(load_dummy_act)
        file_menu.addSeparator()
        file_menu.addAction(exit_act)

        keyboard_unlock_act = QAction(tr("MenuSecurity", "Unlock"), self)
        keyboard_unlock_act.triggered.connect(self.unlock_keyboard)

        keyboard_lock_act = QAction(tr("MenuSecurity", "Lock"), self)
        keyboard_lock_act.triggered.connect(self.lock_keyboard)

        keyboard_reset_act = QAction(
            tr("MenuSecurity", "Reboot to bootloader"), self)
        keyboard_reset_act.triggered.connect(self.reboot_to_bootloader)

        keyboard_layout_menu = self.menuBar().addMenu(
            tr("Menu", "Keyboard layout"))
        keymap_group = QActionGroup(self)
        for idx, keymap in enumerate(KEYMAPS):
            act = QAction(tr("KeyboardLayout", keymap[0]), self)
            act.triggered.connect(
                lambda checked, x=idx: self.change_keyboard_layout(x))
            act.setCheckable(True)
            if idx == 0:
                act.setChecked(True)
            keymap_group.addAction(act)
            keyboard_layout_menu.addAction(act)

        self.security_menu = self.menuBar().addMenu(tr("Menu", "Security"))
        self.security_menu.addAction(keyboard_unlock_act)
        self.security_menu.addAction(keyboard_lock_act)
        self.security_menu.addSeparator()
        self.security_menu.addAction(keyboard_reset_act)

        self.theme_menu = self.menuBar().addMenu(tr("Menu", "Theme"))
        theme_group = QActionGroup(self)
        selected_theme = self.settings.value("theme")
        for name, _ in [("System", None)] + themes.themes:
            act = QAction(tr("MenuTheme", name), self)
            act.triggered.connect(lambda x, name=name: self.set_theme(name))
            act.setCheckable(True)
            act.setChecked(selected_theme == name)
            theme_group.addAction(act)
            self.theme_menu.addAction(act)
        # check "System" if nothing else is selected
        if theme_group.checkedAction() is None:
            theme_group.actions()[0].setChecked(True)
Esempio n. 49
0
 def setupLanguageActions(self):
     languageActionGroup = QActionGroup(self)
     languageActionGroup.addAction(self.actionEnglish)
     languageActionGroup.addAction(self.actionGerman)
     languageActionGroup.setExclusive(True)
Esempio n. 50
0
    def __init__(self, parent=None):
        super().__init__(parent)
        self.pyPath = ''
        self.pyName = ''
        self.icoPath = ''
        self.icoName = ''
        self.specName = ''
        self.MoveTo = ''
        self.PassWord = '******'
        self.SettingName = 'Settings.json'
        self.ColorW = [240, 240, 240, 255]
        self.ColorT = [0, 0, 0, 255]
        self.argv = sys.argv
        self.stayPath = os.path.dirname(os.path.realpath(sys.argv[0]))

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.linePath.setReadOnly(True)
        self.ui.pBtnOpen.setEnabled(False)
        self.ui.linePath.setFocusPolicy(Qt.NoFocus)
        self.ui.pBtnOK.setShortcut(Qt.Key_Return)

        self.BarLable = QLabel('注意:在使用本程序之前,请确认已经配置好Python的环境变量'
        '以及安装pyinstaller。')
        self.ui.statusBar.addWidget(self.BarLable, stretch=0)

        actGroup = QActionGroup(self)
        actGroup.addAction(self.ui.actDall)
        actGroup.addAction(self.ui.actDbootloader)
        actGroup.addAction(self.ui.actDimports)
        actGroup.addAction(self.ui.actDnoarchive)
        actGroup.setExclusive(True)
        try:
            self.LoadSettings()
        except KeyError:
            pass

        if not os.path.exists(self.stayPath+'\\Temporary.json'):
            if self.ui.actRigthMenu.isChecked():
                if len(self.argv) == 2:
                    Path = self.argv[1]
                    if os.path.isfile(Path):
                        if os.path.splitext(Path)[1].lower() == '.py':
                            self.pyName = Path.split('\\')[-1]
                            self.ui.linePath.setText(Path)
                        else:
                            self.pyName = ''
                            self.ui.linePath.clear()
                        self.pyPath = os.path.dirname(Path)
                    else:
                        self.pyPath = Path
                        self.pyName = ''
                        self.ui.linePath.clear()
                else:
                    self.pyPath = os.getcwd()
                    self.pyName = ''
                    self.ui.linePath.clear()
                self.ui.pBtnOpen.setEnabled(True)
        
        if self.pyPath != "":
            if os.path.exists(self.pyPath):
                self.ui.actCleanPyo.setEnabled(True)
                self.ui.actCleanSpec.setEnabled(True)

        self.Thread = QmyTread()
        self.Thread.finish.connect(self.do_Finish)
Esempio n. 51
0
class PyScientificSpinBox(QDoubleSpinBox):
    # This is emited like valueChanged, but not when using setValue_quiet and not within valueChange_gui_time
    valueChanged_gui = pyqtSignal(float)

    # For PyQt < 5.11: only a class with attributes using integer can be made an enum
    #                  (so simple object subclass)
    #        from 5.11: Enum class can also be used. (also Q_ENUMS, Q_FLAGS are depracated for Q_ENUM and Q_FLAG)
    # Version can be obtain as:
    #   import PyQt5.QtCore
    #   PyQt5.QtCore.PYQT_VERSION_STR
    #class Sci_Mode(Enum):
    class Sci_Mode(object):
        ENG_UNIT, ENG, SCI = range(3)

    Q_ENUMS(Sci_Mode)

    class Enabled_Controls(object):
        none = 0x00
        minimum = 0x01
        maximum = 0x02
        min_increment = 0x04
        precision = 0x08
        display_mode = 0x10
        log_increment_mode = 0x20
        fixed_exponent = 0x40
        all = 0x7f
        minmax = 0x03
        no_minmax = all & ~minmax

    Q_FLAGS(Enabled_Controls)

    def __init__(self, *args, **kwargs):
        """ display_mode can be any of the Sci_Mode enum: ENG_UNIT (default), ENG or SCI
            To have a unit, set the suffix.
            keyboardTracking is disabled by default.
            accelerated is enabled by default
            decimals is overriden and redirected to precision.
            A new signal, valueChanged_gui, is emitted when the value is updated but at a restricted rate,
                 valueChange_gui_time, and also when pressing enter. It is not emitted when using
                 setValue_quiet
            option disable_context_menu can be used to disable the context menu. It is False by default.
            enabled_controls property can be used to disable some entries in the context_menu
        """
        self._initializing = True
        self._in_setValue_quiet = False
        key_track = kwargs.pop('keyboardTracking', False)
        kwargs['keyboardTracking'] = key_track
        # need to handle min/max before decimal
        self._disable_context_menu = kwargs.pop('disable_context_menu', False)
        maximum = kwargs.pop('maximum', np.inf)
        minimum = kwargs.pop('minimum', -np.inf)
        accelerated = kwargs.pop('accelerated', True)
        decimals = kwargs.pop('decimals', 5)
        precision = kwargs.pop('precision', decimals)
        self.text_formatter = Format_Float()
        self.text_formatter.precision = precision
        self.validator = FloatValidator()
        self._min_increment = 1e-15
        self._log_increment_mode = False
        self._log_increment = 0.01
        self._display_mode = self.Sci_Mode.ENG_UNIT
        self._enabled_controls = self.Enabled_Controls.all
        self._last_enabled_controls = None
        self._valueChange_timer = QTimer()
        self._valueChange_timer.setInterval(100)  # ms
        # We override decimals. This should set the parent value.
        kwargs[
            'decimals'] = 1000  # this get adjusted to max double  precision (323)
        kwargs['accelerated'] = accelerated
        kwargs['minimum'] = minimum
        kwargs['maximum'] = maximum
        # Note that this will call all properties that are left in kwargs
        super(PyScientificSpinBox, self).__init__(*args, **kwargs)
        # decimals is used when changing min/max, on setValue and on converting value to Text (internally, it is overriden here)
        #super(PyScientificSpinBox, self).setDecimals(1000) # this get adjusted to max double  precision (323)
        # make sure parent class decimals is set properly
        if super(PyScientificSpinBox, self).decimals() < 323:
            raise RuntimeError('Unexpected parent decimals value.')
        # The suffix/prefix change in the above QDoubleSpinBox.__init__ did not call our override
        # lets update them now
        self.setSuffix(self.suffix())
        self.setPrefix(self.prefix())
        # pyqt5 5.6 on windows (at least) QAction requires the parent value.
        self.copyAction = QAction('&Copy',
                                  None,
                                  shortcut=QKeySequence(QKeySequence.Copy),
                                  triggered=self.handle_copy)
        self.pasteAction = QAction('&Paste',
                                   None,
                                   shortcut=QKeySequence(QKeySequence.Paste),
                                   triggered=self.handle_paste)
        self.addActions((self.copyAction, self.pasteAction))
        self._create_menu_init()
        self.clipboard = QApplication.instance().clipboard()
        self._last_valueChanged_gui = self.value()
        self._current_valueChanged_gui = self._last_valueChanged_gui
        self.valueChanged.connect(self._valueChanged_helper)
        self._valueChange_timer.timeout.connect(self._do_valueChanged_gui_emit)
        self._last_focus_reason_mouse = False
        self.lineEdit().installEventFilter(self)
        self.text_is_being_changed_state = False
        self.lineEdit().textEdited.connect(self.text_is_being_changed)
        self._last_key_pressed_is_enter = False
        self.editingFinished.connect(self.editFinished_slot)
        self._in_config_menu = False
        self._initializing = False

    def _create_menu_init(self):
        if self._disable_context_menu:
            return
        # pyqt5 5.6 on windows (at least) QAction requires the parent value.
        self.accel_action = QAction('&Acceleration',
                                    None,
                                    checkable=True,
                                    checked=self.isAccelerated(),
                                    triggered=self.setAccelerated)
        self.log_increment_mode_action = QAction(
            '&Log increment',
            None,
            checkable=True,
            checked=self.log_increment_mode,
            triggered=self.log_increment_mode_change)
        self.help_action = QAction('&Help', None, triggered=self.help_dialog)
        self.stepUpAction = QAction('Step &up', None)
        self.stepUpAction.triggered.connect(self.stepUp)
        self.stepDownAction = QAction('Step &down', None)
        self.stepDownAction.triggered.connect(self.stepDown)
        self.display_mode_grp = QActionGroup(None)
        self.display_mode_eng_unit = QAction('Engineering with &units',
                                             None,
                                             checkable=True)
        self.display_mode_eng = QAction('&Engineering', None, checkable=True)
        self.display_mode_sci = QAction('&Scientific', None, checkable=True)
        self.display_mode_option = {
            self.Sci_Mode.ENG_UNIT: self.display_mode_eng_unit,
            self.Sci_Mode.ENG: self.display_mode_eng,
            self.Sci_Mode.SCI: self.display_mode_sci
        }
        self.display_mode_grp.addAction(self.display_mode_eng_unit)
        self.display_mode_grp.addAction(self.display_mode_eng)
        self.display_mode_grp.addAction(self.display_mode_sci)
        self.display_mode_grp.triggered.connect(self.handle_display_mode)
        # fixed exponent
        self.fixed_exponent_menu = submenu = QMenu('&Fixed exponent')
        self.fixed_exponent_group = subgroup = QActionGroup(None)
        self.fixed_exponent_actions = actions = {}
        for i in range(-15, 9 + 1, 3)[::-1]:
            if i == 0:
                act = QAction('%i: %s' % (i, ''), None, checkable=True)
            else:
                act = QAction('%i: %s' % (i, units_inverse[i]),
                              None,
                              checkable=True)
            submenu.addAction(act)
            subgroup.addAction(act)
            actions[i] = act
        act = QAction('&Custom', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['custom'] = act
        self.fixed_exponent_custom_action = QWidgetAction(None)
        self.fixed_exponent_custom_widget = QSpinBox(value=0,
                                                     minimum=-MAX_FIXED_EXP,
                                                     maximum=MAX_FIXED_EXP)
        self.fixed_exponent_custom_action.setDefaultWidget(
            self.fixed_exponent_custom_widget)
        submenu.addAction(self.fixed_exponent_custom_action)
        submenu.addSeparator()
        act = QAction('&Disabled', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['disabled'] = act
        subgroup.triggered.connect(self.handle_fixed_exponent)
        self.fixed_exponent_custom_widget.valueChanged.connect(
            self.handle_fixed_exponent)
        # precision
        self.precision_menu = submenu = QMenu('&Precision')
        self.precision_group = subgroup = QActionGroup(None)
        self.precision_actions = actions = {}
        for i in range(0, 10):
            act = QAction('&%i' % i, None, checkable=True)
            submenu.addAction(act)
            subgroup.addAction(act)
            actions[i] = act
        act = QAction('&Custom', None, checkable=True)
        submenu.addAction(act)
        subgroup.addAction(act)
        actions['custom'] = act
        self.precision_custom_action = QWidgetAction(None)
        self.precision_custom_widget = QSpinBox(value=0,
                                                minimum=0,
                                                maximum=MAX_PRECISION)
        self.precision_custom_action.setDefaultWidget(
            self.precision_custom_widget)
        submenu.addAction(self.precision_custom_action)
        subgroup.triggered.connect(self.handle_precision)
        self.precision_custom_widget.valueChanged.connect(
            self.handle_precision)
        # min, max, min_increment
        self.min_custom_action = QWidgetAction(None)
        self.min_custom_widget = PyScientificSpinBox(prefix='min: ',
                                                     disable_context_menu=True)
        self.min_custom_action.setDefaultWidget(self.min_custom_widget)
        self.min_custom_widget.valueChanged.connect(
            self.handle_min_custom_widget)
        self.max_custom_action = QWidgetAction(None)
        self.max_custom_widget = PyScientificSpinBox(prefix='max: ',
                                                     disable_context_menu=True)
        self.max_custom_action.setDefaultWidget(self.max_custom_widget)
        self.max_custom_widget.valueChanged.connect(
            self.handle_max_custom_widget)
        self.min_incr_custom_action = QWidgetAction(None)
        self.min_incr_custom_widget = PyScientificSpinBox(
            prefix='min incr: ', disable_context_menu=True)
        self.min_incr_custom_action.setDefaultWidget(
            self.min_incr_custom_widget)
        self.min_incr_custom_widget.valueChanged.connect(
            self.handle_min_incr_custom_widget)
        self.display_mode_sep = QAction('Display mode', None)
        self.display_mode_sep.setSeparator(True)

    def text_is_being_changed(self):
        self.text_is_being_changed_state = True

    def _create_menu(self):
        ec = self.enabled_controls
        EC = self.Enabled_Controls
        if ec == self._last_enabled_controls:
            return self.modified_context_menu
        self._last_enabled_controls = ec
        #self.modified_context_menu = menu = self.lineEdit().createStandardContextMenu()
        self.modified_context_menu = menu = QMenu('Main Menu')
        menu.addActions((self.copyAction, self.pasteAction))
        menu.addSeparator()
        menu.addActions((self.stepUpAction, self.stepDownAction))
        menu.addSeparator()
        if ec & EC.display_mode:
            menu.addActions((self.display_mode_sep, self.display_mode_eng_unit,
                             self.display_mode_eng, self.display_mode_sci))
            menu.addSeparator()
        menu.addAction(self.accel_action)
        if ec & EC.log_increment_mode:
            menu.addAction(self.log_increment_mode_action)
        if ec & EC.fixed_exponent:
            menu.addMenu(self.fixed_exponent_menu)
        if ec & EC.precision:
            menu.addMenu(self.precision_menu)
        menu.addActions((self.min_custom_action, self.max_custom_action,
                         self.min_incr_custom_action))
        self.min_custom_action.setEnabled(ec & EC.minimum)
        self.max_custom_action.setEnabled(ec & EC.maximum)
        self.min_incr_custom_action.setEnabled(ec & EC.min_increment)
        menu.addSeparator()
        menu.addAction(self.help_action)
        return menu

    #def __del__(self):
    #    print('Deleting up PyScientificSpinBox')

    def pyqtConfigure(self, **kwargs):
        # this is necessary to override pyqtConfigure decimals
        # otherwise it calls the parent one directly.
        decimals = kwargs.pop('decimals', None)
        if decimals is not None:
            self.setDecimals(decimals)
        # same thing for suffix/prefix
        prefix = kwargs.pop('prefix', None)
        if prefix is not None:
            self.setPrefix(prefix)
        suffix = kwargs.pop('suffix', None)
        if suffix is not None:
            self.setSuffix(suffix)
        if len(kwargs):
            super(PyScientificSpinBox, self).pyqtConfigure(**kwargs)

    def get_config(self):
        """ The return value can by used in pyqtConfigure.
        """
        return dict(display_mode=self.display_mode,
                    precision=self.precision,
                    min_increment=self.min_increment,
                    minimum=self.minimum(),
                    maximum=self.maximum(),
                    log_increment_mode=self.log_increment_mode,
                    log_increment=self.log_increment,
                    fixed_exponent=self.fixed_exponent,
                    suffix=self.suffix(),
                    prefix=self.prefix(),
                    singleStep=self.singleStep(),
                    accelerated=self.isAccelerated(),
                    wrapping=self.wrapping(),
                    keyboardTracking=self.keyboardTracking())

    def force_update(self):
        val = self.value()
        self.setValue(val)
        self.selectAll()

    def decimals(self):
        return self.precision

    def setDecimals(self, val):
        self.precision = val

    @pyqtProperty(Enabled_Controls)
    def enabled_controls(self):
        return self._enabled_controls

    @enabled_controls.setter
    def enabled_controls(self, val):
        self._enabled_controls = val

    @pyqtProperty(Sci_Mode)
    #@pyqtProperty(int)
    def display_mode(self):
        return self._display_mode

    @display_mode.setter
    def display_mode(self, val):
        self._display_mode = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(int)
    def min_increment(self):
        return self._min_increment

    @min_increment.setter
    def min_increment(self, val):
        self._min_increment = val
        if self.singleStep() < val:
            self.setSingleStep(val)

    @pyqtProperty(int)
    def precision(self):
        return self.text_formatter.precision

    @precision.setter
    def precision(self, val):
        val = min(max(val, 0), MAX_PRECISION)
        self.text_formatter.precision = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(bool)
    def log_increment_mode(self):
        return self._log_increment_mode

    @log_increment_mode.setter
    def log_increment_mode(self, val):
        self._log_increment_mode = val

    @pyqtProperty(float)
    def log_increment(self):
        return self._log_increment

    @log_increment.setter
    def log_increment(self, val):
        self._log_increment = val

    @pyqtProperty(int)
    def fixed_exponent(self):
        """ 9999 disables fixed exponent mode """
        val = self.text_formatter.fixed_exponent
        if val is None:
            return 9999
        return val

    @fixed_exponent.setter
    def fixed_exponent(self, val):
        if val == 9999:
            val = None
        else:
            # keep within -308 to 308: MAX_FIXED_EXP = 308
            val = max(val, -MAX_FIXED_EXP)
            val = min(val, MAX_FIXED_EXP)
        self.text_formatter.fixed_exponent = val
        self.force_update()
        self.updateGeometry()

    @pyqtProperty(float)
    def valueChange_gui_time(self):
        return self._valueChange_timer.interval() / 1e3

    @valueChange_gui_time.setter
    def valueChange_gui_time(self, val):
        val = int(val * 1e3)
        if val <= 0:
            self._valueChange_timer.stop()
        self._valueChange_timer.setInterval(val)

    def keyPressEvent(self, event):
        key = event.key()
        self._last_key_pressed_is_enter = False
        if event.matches(QKeySequence.Copy):
            #print('doing copy')
            self.copyAction.trigger()
        elif event.matches(QKeySequence.Paste):
            #print('doing paste')
            self.pasteAction.trigger()
        elif key == QtCore.Qt.Key_Escape:
            if self.text_is_being_changed_state:
                #print('doing escape')
                self.setValue(self.value())
            else:
                #print('letting escape propagate')
                event.ignore()
        elif key in [QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return]:
            self._last_key_pressed_is_enter = True
            text_was_being_changed_state = self.text_is_being_changed_state
            super(PyScientificSpinBox, self).keyPressEvent(event)
            # default handling does event.ignore() so event, after doing something here
            # propagate. only do that if test is not being changed.
            if text_was_being_changed_state:
                event.accept()
        else:
            super(PyScientificSpinBox, self).keyPressEvent(event)

    # Can't seem to do the same thing (stop short timer) for wheelEvent
    def keyReleaseEvent(self, event):
        if not event.isAutoRepeat() and event.key() in [
                QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp,
                QtCore.Qt.Key_PageDown
        ]:
            self._valueChange_timer.stop()
            self._do_valueChanged_gui_emit()
        super(PyScientificSpinBox, self).keyReleaseEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self._valueChange_timer.stop()
            self._do_valueChanged_gui_emit()
        super(PyScientificSpinBox, self).mouseReleaseEvent(event)

    def mousePressEvent(self, event):
        self._last_focus_reason_mouse = False
        super(PyScientificSpinBox, self).mousePressEvent(event)

    def focusInEvent(self, event):
        if event.reason() == QtCore.Qt.MouseFocusReason:
            self._last_focus_reason_mouse = True
        l = self.lineEdit()
        super(PyScientificSpinBox, self).focusInEvent(event)
        # Using tab to activate widget already does this.
        # It is necessary so that pressing the up/down buttons directly from another widget
        # does not increment before the unit (because position is 0, selection is False)
        self.selectAll()

    def editFinished_slot(self):
        self.text_is_being_changed_state = False
        if self._last_key_pressed_is_enter:
            self._last_key_pressed_is_enter = False
            self._do_valueChanged_gui_emit(force=True)

    def eventFilter(self, watched_obj, event):
        if event.type(
        ) == QEvent.MouseButtonPress and self._last_focus_reason_mouse:
            self._last_focus_reason_mouse = False
            return True
        return False

    def handle_copy(self):
        #print('handling copy')
        self.clipboard.setText('%r' % self.value())

    def handle_paste(self):
        #print('handling paste')
        text = self.clipboard.text()
        # let user decide if it is ok by pressing ok (not esc)
        self.lineEdit().setText(text)
        self.text_is_being_changed_state = True
        # update value immediately
        #try:
        #    self.setValue(float(text))
        #except ValueError: # maybe text is not pure number (contains units)
        #    self.lineEdit().setText(text)
        #    self.interpretText()

    def handle_display_mode(self, action):
        if self._in_config_menu:
            return
        mode = [k for k, v in self.display_mode_option.items()
                if v == action][0]
        self.display_mode = mode

    def handle_fixed_exponent(self, action_or_val):
        if self._in_config_menu:
            return
        if isinstance(action_or_val, QAction):
            enable = False
            action = [
                k for k, v in self.fixed_exponent_actions.items()
                if v == action_or_val
            ][0]
            if action == 'disabled':
                self.fixed_exponent = 9999
            elif action == 'custom':
                enable = True
                self.fixed_exponent = self.fixed_exponent_custom_widget.value()
            else:
                self.fixed_exponent = action
            self.fixed_exponent_custom_action.setEnabled(enable)
        else:
            self.fixed_exponent = action_or_val

    def handle_precision(self, action_or_val):
        if self._in_config_menu:
            return
        if isinstance(action_or_val, QAction):
            enable = False
            action = [
                k for k, v in self.precision_actions.items()
                if v == action_or_val
            ][0]
            if action == 'custom':
                enable = True
                self.precision = self.precision_custom_widget.value()
            else:
                self.precision = action
            self.precision_custom_action.setEnabled(enable)
        else:
            self.precision = action_or_val

    def handle_min_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.setMinimum(val)

    def handle_max_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.setMaximum(val)

    def handle_min_incr_custom_widget(self, val):
        if self._in_config_menu:
            return
        self.min_increment = val

    def setSuffix(self, string):
        self.validator.suffix = string
        super(PyScientificSpinBox, self).setSuffix(string)
        self.updateGeometry()

    def setPrefix(self, string):
        self.validator.prefix = string
        super(PyScientificSpinBox, self).setPrefix(string)
        self.updateGeometry()

    def contextMenuEvent(self, event):
        if self._disable_context_menu:
            return
        self._in_config_menu = True
        menu = self._create_menu()
        se = self.stepEnabled()
        self.stepUpAction.setEnabled(se & QAbstractSpinBox.StepUpEnabled)
        self.stepDownAction.setEnabled(se & QAbstractSpinBox.StepDownEnabled)
        mode = self.display_mode
        self.display_mode_option[mode].setChecked(True)
        self.log_increment_mode_action.setChecked(self.log_increment_mode)
        self.accel_action.setChecked(self.isAccelerated())
        # fixed_exponent
        fe = self.fixed_exponent
        if fe == 9999:
            fe = 'disabled'
        action = self.fixed_exponent_actions.get(fe, None)
        fe_action_custom = self.fixed_exponent_actions['custom']
        if fe_action_custom.isChecked() or action is None:
            action = fe_action_custom
            self.fixed_exponent_custom_action.setEnabled(True)
            self.fixed_exponent_custom_widget.setValue(fe)
        else:
            self.fixed_exponent_custom_action.setEnabled(False)
        action.setChecked(True)
        # precision
        prec = self.precision
        action = self.precision_actions.get(prec, None)
        prec_action_custom = self.precision_actions['custom']
        if prec_action_custom.isChecked() or action is None:
            action = prec_action_custom
            self.precision_custom_action.setEnabled(True)
            self.precision_custom_widget.setValue(prec)
        else:
            self.precision_custom_action.setEnabled(False)
        action.setChecked(True)
        #min
        conf = self.get_config()
        del conf['prefix'], conf['maximum'], conf['minimum'], conf[
            'min_increment']
        conf['keyboardTracking'] = False
        self.min_custom_widget.pyqtConfigure(value=self.minimum(), **conf)
        self.max_custom_widget.pyqtConfigure(value=self.maximum(), **conf)
        self.min_incr_custom_widget.pyqtConfigure(value=self.min_increment,
                                                  minimum=0.,
                                                  **conf)
        self._in_config_menu = False
        menu.exec_(event.globalPos())

    def validate(self, text, position):
        if self._initializing:
            return QValidator.Invalid, text, position
        return self.validator.validate(text, position)

    def fixup(self, text):
        return self.validator.fixup(text)

    def valueFromText(self, text):
        val, unit, scale = self.validator.valueFromText(text)
        if unit:
            if val == 0:
                self.text_formatter.decode_eng(scale)  # update last_exp
        return val

    def textFromValue(self, value, tmp=False):
        #mode = self.display_mode_grp.checkedAction()
        mode = self.display_mode
        fmt = self.text_formatter
        fmt_func_d = {
            self.Sci_Mode.ENG_UNIT: fmt.to_eng_unit,
            self.Sci_Mode.ENG: fmt.to_eng,
            self.Sci_Mode.SCI: fmt.to_float
        }
        suffix = self.suffix()
        ret = fmt_func_d[mode](value, suffix, tmp)
        return ret

    # this is needed because setAccelerated is not a slot, which makes
    # using it connect block deletes. wrapping it in python makes it work properly.
    def setAccelerated(self, val):
        if self._in_config_menu:
            return
        super(PyScientificSpinBox, self).setAccelerated(val)

    def log_increment_mode_change(self, val):
        if self._in_config_menu:
            return
        self.log_increment_mode = val

    def setSingleStep_bounded(self, incr):
        incr = max(incr, self.min_increment)
        self.setSingleStep(incr)

    @pyqtSlot(int)
    def stepBy(self, steps):
        pos = self.lineEdit().cursorPosition()
        if not self.lineEdit().hasSelectedText():
            text = self.lineEdit().text()
            special = self.specialValueText()
            if not (special and special == text):
                incr, log_incr = self.validator.find_increment(text, pos)
                self.setSingleStep_bounded(incr)
                if log_incr is not None:
                    self.log_increment = log_incr
        if self.log_increment_mode:
            #frac = self._adapative_step_frac
            #frac = .01
            frac = self.log_increment
            frac_exp = np.floor(np.log10(frac))
            cval = self.value()
            step_dir = np.sign(steps)
            val_dir = np.sign(cval)
            min_increment = self.min_increment
            for i in range(np.abs(steps)):
                cval = self.value()
                val_dir = np.sign(cval)
                abs_cval = np.abs(cval)
                abs_increase = val_dir == step_dir
                if cval == 0:
                    incr = min_increment
                elif abs_cval < min_increment:
                    if abs_increase:
                        incr = min_increment - cval
                    else:
                        #incr = abs_cval
                        self.setValue(0)
                        continue
                else:
                    incr = abs_cval * frac
                    cval_exp = np.log10(abs_cval)
                    incr_exp = np.floor(cval_exp + frac_exp)
                    incr = 10.**incr_exp
                    if not abs_increase:
                        incr_1 = 10.**(incr_exp - 1)
                        if np.log10(np.abs(cval) -
                                    incr_1) < np.floor(cval_exp):
                            incr = incr_1
                self.setSingleStep_bounded(incr)
                super(PyScientificSpinBox, self).stepBy(step_dir)
        else:
            super(PyScientificSpinBox, self).stepBy(steps)

    def _do_valueChanged_gui_emit(self, val=None, force=False):
        if val is None:
            val = self._current_valueChanged_gui
        if val != self._last_valueChanged_gui or force:
            self._last_valueChanged_gui = val
            self.valueChanged_gui.emit(val)
        else:
            self._valueChange_timer.stop()

    @pyqtSlot(float)
    def _valueChanged_helper(self, val):
        if not self._in_setValue_quiet:
            self._current_valueChanged_gui = val
            if not self._valueChange_timer.isActive():
                self._do_valueChanged_gui_emit(val)
                if self.valueChange_gui_time > 0:
                    self._valueChange_timer.start()

    def setValue_quiet(self, val):
        self._in_setValue_quiet = True
        self.setValue(val)
        self._in_setValue_quiet = False

    def _sizeHint_helper(self, minimum=False):
        self.ensurePolished()
        # code from widgets/qabstractspinbox.cpp
        fm = self.fontMetrics()  # migth need a copy
        try:
            fmw = fm.horizontalAdvance
        except AttributeError:
            fmw = fm.width
        h = self.lineEdit().sizeHint().height()
        w = 0
        if minimum:
            fixedContent = self.prefix() + " "
        else:
            fixedContent = self.prefix() + self.suffix() + " "
        val = -988.888e-99
        if self.fixed_exponent != 9999:
            if self.maximum() != np.inf:
                val = self.maximum()
            else:
                val = -999888. * 10.**self.fixed_exponent
        s = self.textFromValue(val, tmp=True)
        s += fixedContent
        w = fmw(s)
        special = self.specialValueText()
        if special:
            w = max(w, fmw(special))
        w += 2  # cursor blinking space
        hint = QSize(w, h)
        opt = QStyleOptionSpinBox()
        self.initStyleOption(opt)
        style = self.style()
        hint = style.sizeFromContents(style.CT_SpinBox, opt, QSize(w, h), self)
        qapp = QApplication.instance()
        hint = hint.expandedTo(qapp.globalStrut())
        return hint

    def minimumSizeHint(self):
        return self._sizeHint_helper(minimum=True)

    def sizeHint(self):
        return self._sizeHint_helper()

# Observations from QDoubleSpinBox code:
#  minimumSizeHint is the same as SizeHint less the space for suffix
#  sizeHint
#     # internal commands is space of prefix+suffix+max(min, max, special)+graphical elements
#     # note that text is limited to 18 char.
# sizeHint is recalculated and advertised when calling setSuffix (doubleSpinBox.setPrefix seems to only update text)
#  aslo setMinimum, setMaximum, setRange all update the geometry
# setSpecialValueText, setValue, setSingleStep and setSuffix all update the text

    help_text = u"""
For entry you can use scientific entry (like 1e-3) or units. You can add spaces.
The available units are (they are case sensitive):
     Y(1e24), Z, E, P, T, G, M, k(1e3), h(100), da(10), d(0.1), c, m, µ, n, p, f, a, z, y(1e-24)
you can use u instead of µ, and b for no unit (1)

The arrows, or the mouse wheel steps the value.
Page up/down steps the value 10 times faster. Using the CTRL key also goes 10 times faster but only for wheel events.
If you change the cursor position (left/right keys or mouse click) before stepping, the number
to the left of the cursor is incremented by 1.
Adapatative option will increase in a logarithmic way.
Acceleration is a speed up of the steps when pressing the GUI button.
"""

    def help_dialog(self):
        dialog = QMessageBox.information(self, 'Entry Help', self.help_text)
Esempio n. 52
0
    def __init__(self, pointSize, parent=None):
        super(FontToolBar, self).__init__(parent)
        auxiliaryWidth = self.fontMetrics().width('0') * 8
        self.leftTextField = QLineEdit(self)
        self.leftTextField.setMaximumWidth(auxiliaryWidth)
        self.textField = QComboBox(self)
        self.textField.setEditable(True)
        completer = self.textField.completer()
        completer.setCaseSensitivity(Qt.CaseSensitive)
        self.textField.setCompleter(completer)
        # XXX: had to use Maximum because Preferred did entend the widget(?)
        self.textField.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Maximum)
        items = QSettings().value("metricsWindow/comboBoxItems", comboBoxItems,
                                  str)
        self.textField.addItems(items)
        self.rightTextField = QLineEdit(self)
        self.rightTextField.setMaximumWidth(auxiliaryWidth)
        self.leftTextField.textEdited.connect(self.textField.editTextChanged)
        self.rightTextField.textEdited.connect(self.textField.editTextChanged)
        self.comboBox = QComboBox(self)
        self.comboBox.setEditable(True)
        self.comboBox.setValidator(QIntValidator(self))
        for p in pointSizes:
            self.comboBox.addItem(str(p))
        self.comboBox.setEditText(str(pointSize))

        self.configBar = QPushButton(self)
        self.configBar.setFlat(True)
        self.configBar.setIcon(QIcon(":/resources/settings.svg"))
        self.configBar.setStyleSheet("padding: 2px 0px; padding-right: 10px")
        self.toolsMenu = QMenu(self)
        showKerning = self.toolsMenu.addAction(
            "Show Kerning", self.showKerning)
        showKerning.setCheckable(True)
        showMetrics = self.toolsMenu.addAction(
            "Show Metrics", self.showMetrics)
        showMetrics.setCheckable(True)
        self.toolsMenu.addSeparator()
        wrapLines = self.toolsMenu.addAction("Wrap lines", self.wrapLines)
        wrapLines.setCheckable(True)
        noWrapLines = self.toolsMenu.addAction("No wrap", self.noWrapLines)
        noWrapLines.setCheckable(True)
        self.toolsMenu.addSeparator()
        verticalFlip = self.toolsMenu.addAction(
            "Vertical flip", self.verticalFlip)
        verticalFlip.setCheckable(True)
        """
        lineHeight = QWidgetAction(self.toolsMenu)
        lineHeight.setText("Line height:")
        lineHeightSlider = QSlider(Qt.Horizontal, self)
        # QSlider works with integers so we'll just divide by 100 what comes
        # out of it
        lineHeightSlider.setMinimum(80)
        lineHeightSlider.setMaximum(160)
        lineHeightSlider.setValue(100)
        #lineHeightSlider.setContentsMargins(30, 0, 30, 0)
        lineHeightSlider.valueChanged.connect(self.lineHeight)
        lineHeight.setDefaultWidget(lineHeightSlider)
        self.toolsMenu.addAction(lineHeight)
        """

        wrapLinesGroup = QActionGroup(self)
        wrapLinesGroup.addAction(wrapLines)
        wrapLinesGroup.addAction(noWrapLines)
        wrapLines.setChecked(True)
        # self.toolsMenu.setActiveAction(wrapLines)
        self.configBar.setMenu(self.toolsMenu)

        self.addWidget(self.leftTextField)
        self.addWidget(self.textField)
        self.addWidget(self.rightTextField)
        self.addWidget(self.comboBox)
        self.addWidget(self.configBar)
Esempio n. 53
0
class MainGlyphWindow(QMainWindow):

    def __init__(self, glyph, parent=None):
        super().__init__(parent)

        menuBar = self.menuBar()
        fileMenu = QMenu("&File", self)
        fileMenu.addAction("E&xit", self.close, QKeySequence.Quit)
        menuBar.addMenu(fileMenu)
        editMenu = QMenu("&Edit", self)
        self._undoAction = editMenu.addAction(
            "&Undo", self.undo, QKeySequence.Undo)
        self._redoAction = editMenu.addAction(
            "&Redo", self.redo, QKeySequence.Redo)
        editMenu.addSeparator()
        # XXX
        action = editMenu.addAction("C&ut", self.cutOutlines, QKeySequence.Cut)
        action.setEnabled(False)
        self._copyAction = editMenu.addAction(
            "&Copy", self.copyOutlines, QKeySequence.Copy)
        editMenu.addAction("&Paste", self.pasteOutlines, QKeySequence.Paste)
        editMenu.addAction(
            "Select &All", self.selectAll, QKeySequence.SelectAll)
        editMenu.addAction("&Deselect", self.deselect, "Ctrl+D")
        menuBar.addMenu(editMenu)
        glyphMenu = QMenu("&Glyph", self)
        glyphMenu.addAction("&Next Glyph", lambda: self.glyphOffset(1), "End")
        glyphMenu.addAction(
            "&Previous Glyph", lambda: self.glyphOffset(-1), "Home")
        glyphMenu.addAction("&Go To…", self.changeGlyph, "G")
        glyphMenu.addSeparator()
        self._layerAction = glyphMenu.addAction(
            "&Layer Actions…", self.layerActions, "L")
        menuBar.addMenu(glyphMenu)

        # create tools and buttons toolBars
        self._tools = []
        self._toolsActionGroup = QActionGroup(self)
        self._toolsToolBar = QToolBar("Tools", self)
        self._toolsToolBar.setMovable(False)
        self._buttons = []
        self._buttonsToolBar = QToolBar("Buttons", self)
        self._buttonsToolBar.setMovable(False)
        self.addToolBar(self._toolsToolBar)
        self.addToolBar(self._buttonsToolBar)

        # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/
        self._layersToolBar = QToolBar("Layers", self)
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self._currentLayerBox = QComboBox(self)
        self._currentLayerBox.currentIndexChanged.connect(
            self._layerChanged)
        self._layersToolBar.addWidget(spacer)
        self._layersToolBar.addWidget(self._currentLayerBox)
        self._layersToolBar.setContentsMargins(0, 0, 2, 0)
        self._layersToolBar.setMovable(False)
        self.addToolBar(self._layersToolBar)

        viewMenu = self.createPopupMenu()
        viewMenu.setTitle("View")
        viewMenu.addSeparator()
        action = viewMenu.addAction("Lock Toolbars", self.lockToolBars)
        action.setCheckable(True)
        action.setChecked(True)
        menuBar.addMenu(viewMenu)

        self.view = GlyphView(self)
        self.setGlyph(glyph)
        selectionTool = self.installTool(SelectionTool)
        selectionTool.trigger()
        self.installTool(PenTool)
        self.installTool(RulerTool)
        self.installTool(KnifeTool)
        self.installButton(RemoveOverlapButton)

        self.setCentralWidget(self.view.scrollArea())
        self.resize(900, 700)
        self.view.setFocus(True)

    # ----------
    # Menu items
    # ----------

    def glyphOffset(self, offset):
        currentGlyph = self.view.glyph()
        font = currentGlyph.font
        glyphOrder = font.glyphOrder
        # should be enforced in fontView already
        if not (glyphOrder and len(glyphOrder)):
            return
        index = glyphOrder.index(currentGlyph.name)
        newIndex = (index + offset) % len(glyphOrder)
        glyph = font[glyphOrder[newIndex]]
        self.setGlyph(glyph)

    def changeGlyph(self):
        glyph = self.view.glyph()
        newGlyph, ok = GotoDialog.getNewGlyph(self, glyph)
        if ok and newGlyph is not None:
            self.setGlyph(newGlyph)

    def layerActions(self):
        glyph = self.view.glyph()
        newLayer, action, ok = LayerActionsDialog.getLayerAndAction(
            self, glyph)
        if ok and newLayer is not None:
            # TODO: whole glyph for now, but consider selection too
            if not glyph.name in newLayer:
                newLayer.newGlyph(glyph.name)
            otherGlyph = newLayer[glyph.name]
            otherGlyph.disableNotifications()
            if action == "Swap":
                tempGlyph = TGlyph()
                otherGlyph.drawPoints(tempGlyph.getPointPen())
                tempGlyph.width = otherGlyph.width
                otherGlyph.clearContours()
            glyph.drawPoints(otherGlyph.getPointPen())
            otherGlyph.width = glyph.width
            if action != "Copy":
                glyph.disableNotifications()
                glyph.clearContours()
                if action == "Swap":
                    tempGlyph.drawPoints(glyph.getPointPen())
                    glyph.width = tempGlyph.width
                glyph.enableNotifications()
            otherGlyph.enableNotifications()

    def undo(self):
        glyph = self.view.glyph()
        glyph.undo()

    def redo(self):
        glyph = self.view.glyph()
        glyph.redo()

    def cutOutlines(self):
        pass

    def copyOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = QMimeData()
        copyGlyph = glyph.getRepresentation("defconQt.FilterSelection")
        mimeData.setData("application/x-defconQt-glyph-data",
                         pickle.dumps([copyGlyph.serialize(
                             blacklist=("name", "unicode")
                         )]))
        clipboard.setMimeData(mimeData)

    def pasteOutlines(self):
        glyph = self.view.glyph()
        clipboard = QApplication.clipboard()
        mimeData = clipboard.mimeData()
        if mimeData.hasFormat("application/x-defconQt-glyph-data"):
            data = pickle.loads(mimeData.data(
                "application/x-defconQt-glyph-data"))
            if len(data) == 1:
                pen = glyph.getPointPen()
                pasteGlyph = TGlyph()
                pasteGlyph.deserialize(data[0])
                # TODO: if we serialize selected state, we don't need to do
                # this
                pasteGlyph.selected = True
                if len(pasteGlyph) or len(pasteGlyph.components) or \
                        len(pasteGlyph.anchors):
                    glyph.prepareUndo()
                    pasteGlyph.drawPoints(pen)

    def selectAll(self):
        glyph = self.view.glyph()
        glyph.selected = True
        if not len(glyph):
            for component in glyph.components:
                component.selected = True

    def deselect(self):
        glyph = self.view.glyph()
        for anchor in glyph.anchors:
            anchor.selected = False
        for component in glyph.components:
            component.selected = False
        glyph.selected = False

    def lockToolBars(self):
        action = self.sender()
        movable = not action.isChecked()
        for toolBar in (
                self._toolsToolBar, self._buttonsToolBar, self._layersToolBar):
            toolBar.setMovable(movable)

    # --------------------------
    # Tools & buttons management
    # --------------------------

    def installTool(self, tool):
        # TODO: add shortcut with number
        action = self._toolsToolBar.addAction(
            QIcon(tool.iconPath), tool.name, self._setViewTool)
        action.setCheckable(True)
        action.setData(len(self._tools))
        self._toolsActionGroup.addAction(action)
        self._tools.append(tool(parent=self.view))
        return action

    def uninstallTool(self, tool):
        pass  # XXX

    def _setViewTool(self):
        action = self.sender()
        action.setChecked(True)
        index = action.data()
        self.view.currentTool = self._tools[index]

    def installButton(self, button):
        action = self._buttonsToolBar.addAction(
            QIcon(button.iconPath), button.name, self._buttonAction)
        action.setData(len(self._buttons))
        self._buttons.append(button(parent=self.view))
        return action

    def uninstallButton(self, button):
        pass  # XXX

    def _buttonAction(self):
        action = self.sender()
        index = action.data()
        button = self._buttons[index]
        button.clicked()

    # --------------------
    # Notification support
    # --------------------

    # glyph

    def _subscribeToGlyph(self, glyph):
        if glyph is not None:
            glyph.addObserver(self, "_glyphChanged", "Glyph.Changed")
            glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged")
            glyph.addObserver(
                self, "_glyphSelectionChanged", "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.connect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.connect(self._redoAction.setEnabled)
            self._subscribeToFontAndLayerSet(glyph.font)

    def _unsubscribeFromGlyph(self, glyph):
        if glyph is not None:
            glyph.removeObserver(self, "Glyph.Changed")
            glyph.removeObserver(self, "Glyph.NameChanged")
            glyph.removeObserver(self, "Glyph.SelectionChanged")
            undoManager = glyph.undoManager
            undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled)
            undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled)
            self._unsubscribeFromFontAndLayerSet(glyph.font)

    def _glyphChanged(self, notification):
        self.view.glyphChanged()

    def _glyphNameChanged(self, notification):
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    def _glyphSelectionChanged(self, notification):
        self._updateSelection()
        self.view.glyphChanged()

    def _fontInfoChanged(self, notification):
        self.view.fontInfoChanged()
        glyph = self.view.glyph()
        self.setWindowTitle(glyph.name, glyph.font)

    # layers & font

    def _subscribeToFontAndLayerSet(self, font):
        """Note: called by _subscribeToGlyph."""
        if font is None:
            return
        font.info.addObserver(self, "_fontInfoChanged", "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        layerSet.addObserver(self, '_layerSetLayerDeleted',
                             'LayerSet.LayerDeleted')
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged'):
            layerSet.addObserver(self, '_layerSetEvents', event)

    def _unsubscribeFromFontAndLayerSet(self, font):
        """Note: called by _unsubscribeFromGlyph."""
        if font is None:
            return
        font.info.removeObserver(self, "Info.Changed")
        layerSet = font.layers
        if layerSet is None:
            return
        for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged',
                      'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'):
            layerSet.removeObserver(self, event)

    def _layerSetEvents(self, notification):
        self._updateLayerControls()

    def _layerSetLayerDeleted(self, notification):
        self._layerSetEvents(notification)
        self._currentLayerBox.setCurrentIndex(0)

    # other updaters

    def _updateUndoRedo(self):
        glyph = self.view.glyph()
        self._undoAction.setEnabled(glyph.canUndo())
        self._redoAction.setEnabled(glyph.canRedo())

    def _updateSelection(self):
        def hasSelection():
            glyph = self.view.glyph()
            for contour in glyph:
                if len(contour.selection):
                    return True
            for anchor in glyph.anchors:
                if anchor.selected:
                    return True
            for component in glyph.components:
                if component.selected:
                    return True
            return False
        self._copyAction.setEnabled(hasSelection())

    # --------------
    # Public Methods
    # --------------

    def setGlyph(self, glyph):
        currentGlyph = self.view.glyph()
        self._unsubscribeFromGlyph(currentGlyph)
        self._subscribeToGlyph(glyph)
        self.view.setGlyph(glyph)
        self._updateLayerControls()
        self._updateUndoRedo()
        self._updateSelection()
        self.setWindowTitle(glyph.name, glyph.font)

    def setDrawingAttribute(self, attr, value, layerName=None):
        self.view.setDrawingAttribute(attr, value, layerName)

    def drawingAttribute(self, attr, layerName=None):
        return self.view.drawingAttribute(attr, layerName)

    # -----------------
    # Layers management
    # -----------------

    def _layerChanged(self, newLayerIndex):
        glyph = self.view.glyph()
        layer = self._currentLayerBox.itemData(newLayerIndex)
        if layer is None:
            layer = self._makeLayer()
            if layer is None:
                # restore comboBox to active index
                layerSet = glyph.layerSet
                index = layerSet.layerOrder.index(glyph.layer.name)
                self._setLayerBoxIndex(index)
                return

        if glyph.name in layer:
            newGlyph = layer[glyph.name]
        else:
            # TODO: make sure we mimic defcon ufo3 APIs for that
            newGlyph = self._makeLayerGlyph(layer, glyph)
        self.setGlyph(newGlyph)

        # setting the layer-glyph here
        app = QApplication.instance()
        app.setCurrentGlyph(newGlyph)

    def _makeLayer(self):
        # TODO: what with duplicate names?
        glyph = self.view.glyph()
        newLayerName, ok = AddLayerDialog.getNewLayerName(self)
        if ok:
            layerSet = glyph.layerSet
            # TODO: this should return the layer
            layerSet.newLayer(newLayerName)
            return layerSet[newLayerName]
        else:
            return None

    def _makeLayerGlyph(self, layer, currentGlyph):
        glyph = layer.newGlyph(currentGlyph.name)
        glyph.width = currentGlyph.width
        glyph.template = True
        return glyph

    def _updateLayerControls(self):
        comboBox = self._currentLayerBox
        glyph = self.view.glyph()
        comboBox.blockSignals(True)
        comboBox.clear()
        for layer in glyph.layerSet:
            comboBox.addItem(layer.name, layer)
        comboBox.setCurrentText(glyph.layer.name)
        comboBox.addItem("New layer…", None)
        comboBox.blockSignals(False)
        self._layerAction.setEnabled(len(glyph.layerSet) > 1)

    def _setLayerBoxIndex(self, index):
        comboBox = self._currentLayerBox
        comboBox.blockSignals(True)
        comboBox.setCurrentIndex(index)
        comboBox.blockSignals(False)

    # ---------------------
    # QMainWindow functions
    # ---------------------

    def event(self, event):
        if event.type() == QEvent.WindowActivate:
            app = QApplication.instance()
            app.setCurrentGlyph(self.view.glyph())
        return super().event(event)

    def closeEvent(self, event):
        glyph = self.view.glyph()
        self._unsubscribeFromGlyph(glyph)
        event.accept()

    def setWindowTitle(self, title, font=None):
        if font is not None:
            title = "%s – %s %s" % (
                title, font.info.familyName, font.info.styleName)
        super().setWindowTitle(title)
Esempio n. 54
0
    def __init__(self):
        super(MainWindow, self).__init__()

        self.currentPath = ''

        self.view = SvgView()

        fileMenu = QMenu("&File", self)
        openAction = fileMenu.addAction("&Open...")
        openAction.setShortcut("Ctrl+O")
        quitAction = fileMenu.addAction("E&xit")
        quitAction.setShortcut("Ctrl+Q")

        self.menuBar().addMenu(fileMenu)

        viewMenu = QMenu("&View", self)
        self.backgroundAction = viewMenu.addAction("&Background")
        self.backgroundAction.setEnabled(False)
        self.backgroundAction.setCheckable(True)
        self.backgroundAction.setChecked(False)
        self.backgroundAction.toggled.connect(self.view.setViewBackground)

        self.outlineAction = viewMenu.addAction("&Outline")
        self.outlineAction.setEnabled(False)
        self.outlineAction.setCheckable(True)
        self.outlineAction.setChecked(True)
        self.outlineAction.toggled.connect(self.view.setViewOutline)

        self.menuBar().addMenu(viewMenu)

        rendererMenu = QMenu("&Renderer", self)
        self.nativeAction = rendererMenu.addAction("&Native")
        self.nativeAction.setCheckable(True)
        self.nativeAction.setChecked(True)

        if QGLFormat.hasOpenGL():
            self.glAction = rendererMenu.addAction("&OpenGL")
            self.glAction.setCheckable(True)

        self.imageAction = rendererMenu.addAction("&Image")
        self.imageAction.setCheckable(True)

        if QGLFormat.hasOpenGL():
            rendererMenu.addSeparator()
            self.highQualityAntialiasingAction = rendererMenu.addAction("&High Quality Antialiasing")
            self.highQualityAntialiasingAction.setEnabled(False)
            self.highQualityAntialiasingAction.setCheckable(True)
            self.highQualityAntialiasingAction.setChecked(False)
            self.highQualityAntialiasingAction.toggled.connect(self.view.setHighQualityAntialiasing)

        rendererGroup = QActionGroup(self)
        rendererGroup.addAction(self.nativeAction)

        if QGLFormat.hasOpenGL():
            rendererGroup.addAction(self.glAction)

        rendererGroup.addAction(self.imageAction)

        self.menuBar().addMenu(rendererMenu)

        openAction.triggered.connect(self.openFile)
        quitAction.triggered.connect(QApplication.instance().quit)
        rendererGroup.triggered.connect(self.setRenderer)

        self.setCentralWidget(self.view)
        self.setWindowTitle("SVG Viewer")
Esempio n. 55
0
    def initUI(self):
        # TODO: initCanvas()?
        self.canvas = PlotCanvas(self)
        self.canvas.setFocusPolicy(Qt.ClickFocus)
        self.canvas.setFocus()

        # self.canvas.mpl_connect('button_press_event', self.on_press)
        # self.canvas.mpl_connect('button_release_event', self.on_release)
        # self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        # self.canvas.mpl_connect('scroll_event', self.on_scroll)
        # self.canvas.mpl_connect('key_release_event', self.on_key_release)

        menuBar = self.menuBar()
        openMenu = menuBar.addMenu('Open')
        showMenu = menuBar.addMenu('Show')

        allAct = QAction("All", self)
        allAct.setCheckable(True)
        allAct.setChecked(True)
        allAct.triggered.connect(lambda: self.showPts(0))

        missAct = QAction("Miss Clf", self)
        missAct.setCheckable(True)
        missAct.setChecked(False)
        missAct.triggered.connect(lambda: self.showPts(1))

        noneAct = QAction("None", self)
        noneAct.setCheckable(True)
        noneAct.setChecked(False)
        noneAct.triggered.connect(lambda: self.showPts(2))

        showMenu.addAction(allAct)
        showMenu.addAction(missAct)
        showMenu.addAction(noneAct)

        showMenuGroup = QActionGroup(self)
        showMenuGroup.addAction(allAct)
        showMenuGroup.addAction(missAct)
        showMenuGroup.addAction(noneAct)
        showMenuGroup.setExclusive(True)

        showMenu.addSeparator()

        # highlight action
        hlAct = QAction('Highlight Neighbors', self)
        hlAct.setShortcut("Ctrl+H")
        hlAct.setCheckable(True)
        hlAct.setChecked(False)
        hlAct.triggered.connect(self.setHLNeighbors)
        showMenu.addAction(hlAct)

        freezeAct = QAction('Freeze', self)
        freezeAct.setShortcut("Ctrl+F")
        freezeAct.setCheckable(True)
        freezeAct.setChecked(False)
        freezeAct.triggered.connect(self.freeze_act)
        showMenu.addAction(freezeAct)

        boundaryMapAct = QAction('Show boundary map')
        boundaryMapAct.setShortcut("Ctrl+B")
        boundaryMapAct.setCheckable(True)
        boundaryMapAct.setChecked(True)
        boundaryMapAct.triggered.connect(lambda: self.show_map(self.BMAP))
        showMenu.addAction(boundaryMapAct)

        distMapAct = QAction('Show dist map', self)
        distMapAct.setShortcut("Ctrl+D")
        distMapAct.setCheckable(True)
        distMapAct.setChecked(False)
        distMapAct.triggered.connect(lambda: self.show_map(self.DMAP2D))
        showMenu.addAction(distMapAct)

        distMapNDAct = QAction('Show dist map nD', self)
        distMapNDAct.setCheckable(True)
        distMapNDAct.setChecked(False)
        distMapNDAct.triggered.connect(lambda: self.show_map(self.DMAPND))
        showMenu.addAction(distMapNDAct)

        distMapND2Act = QAction('Show dist map nD2', self)
        distMapND2Act.setCheckable(True)
        distMapND2Act.setChecked(False)
        distMapND2Act.triggered.connect(lambda: self.show_map(self.DMAPND2))
        showMenu.addAction(distMapND2Act)

        distMapND3Act = QAction('Show dist map nD3', self)
        distMapND3Act.setCheckable(True)
        distMapND3Act.setChecked(False)
        distMapND3Act.triggered.connect(lambda: self.show_map(self.DMAPND3))
        showMenu.addAction(distMapND3Act)

        distMenuGroup = QActionGroup(self)
        distMenuGroup.addAction(boundaryMapAct)
        distMenuGroup.addAction(distMapAct)
        distMenuGroup.addAction(distMapNDAct)
        distMenuGroup.addAction(distMapND2Act)
        distMenuGroup.addAction(distMapND3Act)

        openProjAct = QAction("Projection", self)
        openProjAct.setShortcut("Ctrl+O")
        openProjAct.triggered.connect(self.openProjection)

        saveAct = QAction('Save', self)
        saveAct.setShortcut("Ctrl+S")
        saveAct.triggered.connect(self.save_new_samples)

        openMenu.addAction(openProjAct)
        openMenu.addAction(saveAct)

        self.errUI = ErrUI()

        splitter = QSplitter(self)
        splitter.addWidget(self.errUI)
        splitter.addWidget(self.canvas)
        splitter.addWidget(self.newsamplesUI)
        self.setCentralWidget(splitter)
Esempio n. 56
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        widget = QWidget()
        self.setCentralWidget(widget)

        topFiller = QWidget()
        topFiller.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.infoLabel = QLabel(
                "<i>Choose a menu option, or right-click to invoke a context menu</i>",
                alignment=Qt.AlignCenter)
        self.infoLabel.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)

        bottomFiller = QWidget()
        bottomFiller.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        vbox = QVBoxLayout()
        vbox.setContentsMargins(5, 5, 5, 5)
        vbox.addWidget(topFiller)
        vbox.addWidget(self.infoLabel)
        vbox.addWidget(bottomFiller)
        widget.setLayout(vbox)

        self.createActions()
        self.createMenus()

        message = "A context menu is available by right-clicking"
        self.statusBar().showMessage(message)

        self.setWindowTitle("Menus")
        self.setMinimumSize(160,160)
        self.resize(480,320)

    def contextMenuEvent(self, event):
        menu = QMenu(self)
        menu.addAction(self.cutAct)
        menu.addAction(self.copyAct)
        menu.addAction(self.pasteAct)
        menu.exec_(event.globalPos())

    def newFile(self):
        self.infoLabel.setText("Invoked <b>File|New</b>")

    def open(self):
        self.infoLabel.setText("Invoked <b>File|Open</b>")
        	
    def save(self):
        self.infoLabel.setText("Invoked <b>File|Save</b>")

    def print_(self):
        self.infoLabel.setText("Invoked <b>File|Print</b>")

    def undo(self):
        self.infoLabel.setText("Invoked <b>Edit|Undo</b>")

    def redo(self):
        self.infoLabel.setText("Invoked <b>Edit|Redo</b>")

    def cut(self):
        self.infoLabel.setText("Invoked <b>Edit|Cut</b>")

    def copy(self):
        self.infoLabel.setText("Invoked <b>Edit|Copy</b>")

    def paste(self):
        self.infoLabel.setText("Invoked <b>Edit|Paste</b>")

    def bold(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Bold</b>")

    def italic(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Italic</b>")

    def leftAlign(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Left Align</b>")

    def rightAlign(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Right Align</b>")

    def justify(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Justify</b>")

    def center(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Center</b>")

    def setLineSpacing(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Set Line Spacing</b>")

    def setParagraphSpacing(self):
        self.infoLabel.setText("Invoked <b>Edit|Format|Set Paragraph Spacing</b>")

    def about(self):
        self.infoLabel.setText("Invoked <b>Help|About</b>")
        QMessageBox.about(self, "About Menu",
                "The <b>Menu</b> example shows how to create menu-bar menus "
                "and context menus.")

    def aboutQt(self):
        self.infoLabel.setText("Invoked <b>Help|About Qt</b>")

    def createActions(self):
        self.newAct = QAction("&New", self, shortcut=QKeySequence.New,
                statusTip="Create a new file", triggered=self.newFile)

        self.openAct = QAction("&Open...", self, shortcut=QKeySequence.Open,
                statusTip="Open an existing file", triggered=self.open)

        self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save,
                statusTip="Save the document to disk", triggered=self.save)

        self.printAct = QAction("&Print...", self, shortcut=QKeySequence.Print,
                statusTip="Print the document", triggered=self.print_)

        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
                statusTip="Exit the application", triggered=self.close)

        self.undoAct = QAction("&Undo", self, shortcut=QKeySequence.Undo,
                statusTip="Undo the last operation", triggered=self.undo)

        self.redoAct = QAction("&Redo", self, shortcut=QKeySequence.Redo,
                statusTip="Redo the last operation", triggered=self.redo)

        self.cutAct = QAction("Cu&t", self, shortcut=QKeySequence.Cut,
                statusTip="Cut the current selection's contents to the clipboard",
                triggered=self.cut)

        self.copyAct = QAction("&Copy", self, shortcut=QKeySequence.Copy,
                statusTip="Copy the current selection's contents to the clipboard",
                triggered=self.copy)

        self.pasteAct = QAction("&Paste", self, shortcut=QKeySequence.Paste,
                statusTip="Paste the clipboard's contents into the current selection",
                triggered=self.paste)

        self.boldAct = QAction("&Bold", self, checkable=True,
                shortcut="Ctrl+B", statusTip="Make the text bold",
                triggered=self.bold)

        boldFont = self.boldAct.font()
        boldFont.setBold(True)
        self.boldAct.setFont(boldFont)

        self.italicAct = QAction("&Italic", self, checkable=True,
                shortcut="Ctrl+I", statusTip="Make the text italic",
                triggered=self.italic)

        italicFont = self.italicAct.font()
        italicFont.setItalic(True)
        self.italicAct.setFont(italicFont)

        self.setLineSpacingAct = QAction("Set &Line Spacing...", self,
                statusTip="Change the gap between the lines of a paragraph",
                triggered=self.setLineSpacing)

        self.setParagraphSpacingAct = QAction("Set &Paragraph Spacing...",
                self, statusTip="Change the gap between paragraphs",
                triggered=self.setParagraphSpacing)

        self.aboutAct = QAction("&About", self,
                statusTip="Show the application's About box",
                triggered=self.about)

        self.aboutQtAct = QAction("About &Qt", self,
                statusTip="Show the Qt library's About box",
                triggered=self.aboutQt)
        self.aboutQtAct.triggered.connect(QApplication.instance().aboutQt)

        self.leftAlignAct = QAction("&Left Align", self, checkable=True,
                shortcut="Ctrl+L", statusTip="Left align the selected text",
                triggered=self.leftAlign)

        self.rightAlignAct = QAction("&Right Align", self, checkable=True,
                shortcut="Ctrl+R", statusTip="Right align the selected text",
                triggered=self.rightAlign)

        self.justifyAct = QAction("&Justify", self, checkable=True,
                shortcut="Ctrl+J", statusTip="Justify the selected text",
                triggered=self.justify)

        self.centerAct = QAction("&Center", self, checkable=True,
                shortcut="Ctrl+C", statusTip="Center the selected text",
                triggered=self.center)

        self.alignmentGroup = QActionGroup(self)
        self.alignmentGroup.addAction(self.leftAlignAct)
        self.alignmentGroup.addAction(self.rightAlignAct)
        self.alignmentGroup.addAction(self.justifyAct)
        self.alignmentGroup.addAction(self.centerAct)
        self.leftAlignAct.setChecked(True)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newAct)
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.printAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.undoAct)
        self.editMenu.addAction(self.redoAct)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.cutAct)
        self.editMenu.addAction(self.copyAct)
        self.editMenu.addAction(self.pasteAct)
        self.editMenu.addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

        self.formatMenu = self.editMenu.addMenu("&Format")
        self.formatMenu.addAction(self.boldAct)
        self.formatMenu.addAction(self.italicAct)
        self.formatMenu.addSeparator().setText("Alignment")
        self.formatMenu.addAction(self.leftAlignAct)
        self.formatMenu.addAction(self.rightAlignAct)
        self.formatMenu.addAction(self.justifyAct)
        self.formatMenu.addAction(self.centerAct)
        self.formatMenu.addSeparator()
        self.formatMenu.addAction(self.setLineSpacingAct)
        self.formatMenu.addAction(self.setParagraphSpacingAct)
Esempio n. 57
0
class GuiNovelToolBar(QWidget):
    def __init__(self, novelView):
        QTreeWidget.__init__(self, novelView)

        logger.debug("Initialising GuiNovelToolBar ...")

        self.mainConf = novelwriter.CONFIG
        self.novelView = novelView
        self.theProject = novelView.mainGui.theProject
        self.mainTheme = novelView.mainGui.mainTheme

        iPx = self.mainTheme.baseIconSize
        mPx = self.mainConf.pxInt(3)

        self.setContentsMargins(0, 0, 0, 0)
        self.setAutoFillBackground(True)

        qPalette = self.palette()
        qPalette.setBrush(QPalette.Window, qPalette.base())
        self.setPalette(qPalette)

        fadeCol = qPalette.text().color()
        buttonStyle = (
            "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
            "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
        ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())

        # Widget Label
        self.viewLabel = QLabel("<b>%s</b>" % self.tr("Novel Outline"))
        self.viewLabel.setContentsMargins(0, 0, 0, 0)
        self.viewLabel.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        # Refresh Button
        self.tbRefresh = QToolButton(self)
        self.tbRefresh.setToolTip(self.tr("Refresh"))
        self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
        self.tbRefresh.setIconSize(QSize(iPx, iPx))
        self.tbRefresh.setStyleSheet(buttonStyle)
        self.tbRefresh.clicked.connect(self._refreshNovelTree)

        # Novel Root Menu
        self.mRoot = QMenu()
        self.gRoot = QActionGroup(self.mRoot)
        self.aRoot = {}

        self.tbRoot = QToolButton(self)
        self.tbRoot.setToolTip(self.tr("Novel Root"))
        self.tbRoot.setIcon(
            self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
        self.tbRoot.setIconSize(QSize(iPx, iPx))
        self.tbRoot.setStyleSheet(buttonStyle)
        self.tbRoot.setMenu(self.mRoot)
        self.tbRoot.setPopupMode(QToolButton.InstantPopup)

        # More Options Menu
        self.mMore = QMenu()

        self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
        self.gLastCol = QActionGroup(self.mMore)
        self.aLastCol = {}
        self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden"))
        self._addLastColAction(NovelTreeColumn.POV,
                               self.tr("Point of View Character"))
        self._addLastColAction(NovelTreeColumn.FOCUS,
                               self.tr("Focus Character"))
        self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot"))

        self.tbMore = QToolButton(self)
        self.tbMore.setToolTip(self.tr("More Options"))
        self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
        self.tbMore.setIconSize(QSize(iPx, iPx))
        self.tbMore.setStyleSheet(buttonStyle)
        self.tbMore.setMenu(self.mMore)
        self.tbMore.setPopupMode(QToolButton.InstantPopup)

        # Assemble
        self.outerBox = QHBoxLayout()
        self.outerBox.addWidget(self.viewLabel)
        self.outerBox.addWidget(self.tbRefresh)
        self.outerBox.addWidget(self.tbRoot)
        self.outerBox.addWidget(self.tbMore)
        self.outerBox.setContentsMargins(mPx, mPx, 0, mPx)
        self.outerBox.setSpacing(0)

        self.setLayout(self.outerBox)

        logger.debug("GuiNovelToolBar initialisation complete")

        return

    ##
    #  Methods
    ##

    def clearContent(self):
        """Run clearing project tasks.
        """
        self.mRoot.clear()
        self.aRoot = {}
        return

    def buildNovelRootMenu(self):
        """Build the novel root menu.
        """
        self.mRoot.clear()
        self.aRoot = {}
        for n, (tHandle, nwItem) in enumerate(
                self.theProject.tree.iterRoots(nwItemClass.NOVEL)):
            aRoot = self.mRoot.addAction(nwItem.itemName)
            aRoot.setData(tHandle)
            aRoot.setCheckable(True)
            aRoot.triggered.connect(
                lambda n, tHandle=tHandle: self.setCurrentRoot(tHandle))
            self.gRoot.addAction(aRoot)
            self.aRoot[tHandle] = aRoot

        return

    def setCurrentRoot(self, rootHandle):
        """Set the current active root handle.
        """
        if rootHandle in self.aRoot:
            self.aRoot[rootHandle].setChecked(True)
            self.novelView.novelTree.refreshTree(rootHandle=rootHandle,
                                                 overRide=True)
        return

    def setLastColType(self, colType, doRefresh=True):
        """Set the last column type.
        """
        self.aLastCol[colType].setChecked(True)
        self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
        return

    ##
    #  Private Slots
    ##

    @pyqtSlot()
    def _refreshNovelTree(self):
        """Rebuild the current tree.
        """
        rootHandle = self.theProject.lastNovel
        self.novelView.novelTree.refreshTree(rootHandle=rootHandle,
                                             overRide=True)
        return

    ##
    #  Internal Functions
    ##

    def _addLastColAction(self, colType, actionLabel):
        """Add a column selection entry to the last column menu.
        """
        aLast = self.mLastCol.addAction(actionLabel)
        aLast.setCheckable(True)
        aLast.setActionGroup(self.gLastCol)
        aLast.triggered.connect(lambda: self.setLastColType(colType))
        self.aLastCol[colType] = aLast
        return