Example #1
1
class SourceViewer(QDialog):
    def __init__(self, browser):
        super(SourceViewer, self).__init__(browser.parentWidget())

        layout = QVBoxLayout()
        layout.setContentsMargins(4, 4, 4, 4)
        self.setLayout(layout)
        
        self.urlLabel = QLabel(wordWrap=True)
        layout.addWidget(self.urlLabel)
        self.textbrowser = QTextBrowser()
        layout.addWidget(self.textbrowser)
        
        self.urlLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.textbrowser.setLineWrapMode(QTextBrowser.NoWrap)
        
        app.settingsChanged.connect(self.readSettings)
        self.readSettings()
        app.translateUI(self)
        qutil.saveDialogSize(self, "helpbrowser/sourceviewer/size", QSize(400, 300))
        
    def translateUI(self):
        self.setWindowTitle(app.caption(_("LilyPond Source")))
        
    def readSettings(self):
        data = textformats.formatData('editor')
        self.textbrowser.setPalette(data.palette())
        self.textbrowser.setFont(data.font)
        highlighter.highlight(self.textbrowser.document())
        
    def showReply(self, reply):
        reply.setParent(self)
        self.urlLabel.setText(reply.url().toString())
        self._reply = reply
        reply.finished.connect(self.loadingFinished)
        self.textbrowser.clear()
        self.show()
        
    def loadingFinished(self):
        data = self._reply.readAll()
        self._reply.close()
        self._reply.deleteLater()
        del self._reply
        self.textbrowser.clear()
        self.textbrowser.setText(str(data, 'utf-8', 'replace'))
        highlighter.highlight(self.textbrowser.document())
Example #2
0
 def _set_browser_text(self, browser: QTextBrowser, text: str):
     browser.document().setDefaultStyleSheet(
         "a { text-decoration: underline; color: palette(window-text); }")
     browser.setText(text)
     fmt = browser.document().rootFrame().frameFormat()
     fmt.setMargin(12)
     browser.document().rootFrame().setFrameFormat(fmt)
Example #3
0
    def add_text(self):
        self.process_text()
        text = QTextBrowser()
        text.setHtml(self.text)
        text.setOpenLinks(False)
        # text.moveCursor(QTextCursor.End)
        text.setMinimumWidth(500)
        text.setMaximumWidth(500)
        text.document().setTextWidth(500)
        text.setMinimumHeight((text.document().size().height())+5)
        text.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)

        text.anchorClicked.connect(self.link_clicked)

        self.lay.addWidget(text, 1, 1, 1, 3)
Example #4
0
class SourceViewer(QDialog):
    def __init__(self, browser):
        super(SourceViewer, self).__init__(browser.parentWidget())

        layout = QVBoxLayout()
        layout.setContentsMargins(4, 4, 4, 4)
        self.setLayout(layout)

        self.urlLabel = QLabel(wordWrap=True)
        layout.addWidget(self.urlLabel)
        self.textbrowser = QTextBrowser()
        layout.addWidget(self.textbrowser)

        self.urlLabel.setSizePolicy(QSizePolicy.Preferred,
                                    QSizePolicy.Preferred)
        self.textbrowser.setLineWrapMode(QTextBrowser.NoWrap)

        app.settingsChanged.connect(self.readSettings)
        self.readSettings()
        app.translateUI(self)
        qutil.saveDialogSize(self, "helpbrowser/sourceviewer/size",
                             QSize(400, 300))

    def translateUI(self):
        self.setWindowTitle(app.caption(_("LilyPond Source")))

    def readSettings(self):
        data = textformats.formatData('editor')
        self.textbrowser.setPalette(data.palette())
        self.textbrowser.setFont(data.font)
        highlighter.highlight(self.textbrowser.document())

    def showReply(self, reply):
        reply.setParent(self)
        self.urlLabel.setText(reply.url().toString())
        self._reply = reply
        reply.finished.connect(self.loadingFinished)
        self.textbrowser.clear()
        self.show()

    def loadingFinished(self):
        data = self._reply.readAll()
        self._reply.close()
        self._reply.deleteLater()
        del self._reply
        self.textbrowser.clear()
        self.textbrowser.setText(str(data, 'utf-8', 'replace'))
        highlighter.highlight(self.textbrowser.document())
Example #5
0
    def __init__(self, parent: WalletWizard) -> None:
        super().__init__(parent)

        self.setTitle(_("Release Notes"))

        release_html = read_resource_text("wallet-wizard", "release-notes.html")

        widget = QTextBrowser()
        widget.document().setDocumentMargin(15)
        widget.setOpenExternalLinks(True)
        widget.setAcceptRichText(True)
        widget.setHtml(release_html)

        layout = QVBoxLayout()
        layout.addWidget(widget)
        self.setLayout(layout)
Example #6
0
class Form(QDialog):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)
        self.broswer = QTextBrowser()  # display
        self.lineedit = QLineEdit(
            "Type an expression and press Enter")  # input
        self.lineedit.selectAll()  # ctr + a
        layout = QVBoxLayout()  # a frame with 'X' and '--' and '[]'
        layout.addWidget(self.broswer)
        layout.addWidget(self.lineedit)
        self.setLayout(layout)
        self.lineedit.setFocus()
        setting = self.broswer.document()
        setting.setMaximumBlockCount(5)
        self.lineedit.returnPressed.connect(self.updateUi)
        self.setWindowTitle("Calculate")

    def updateUi(self):
        try:
            text = str(self.lineedit.text())
            self.broswer.append(
                "%s = <b>%s</b>" %
                (text, eval(text)))  # when fresh the list in broswer
            # maybe append triggle updating ui
        except Exception:
            traceback.print_exc()
            self.broswer.append("<font color=red>%s is invalid</font>" % text)
Example #7
0
class Browser(QWidget):
	def __init__(self,parent_,url,cname,fname):
		super(Browser,self).__init__(parent_)
		self.parent_=parent_
		self.initUI(url,cname,fname)

	def initUI(self,url,cname,fname):
		lbl = QLabel()
		lbl.setText('Forum News - '+cname)
		lbl.setObjectName('hlbl')
		self.browser = QTextBrowser()
		self.browser.document().setDefaultStyleSheet('p{font-size:12px;} div{margin-left:20px;}')

		f = open(url,'r')
		ftext = '<div><br><h3><b>%s</b></h3>'%fname + str(f.read())+'<br></div>'
		self.browser.setHtml(ftext)
		self.browser.setFrameStyle(QFrame.NoFrame)

		self.backBtn = QPushButton(QIcon(':/Assets/close2.png'),'Close')
		self.backBtn.setObjectName('backBtn')
		self.backBtn.clicked.connect(partial(self.parent_.closeTextBrowser))
		frame = topFrame(self.backBtn,lbl)
		frame.setObjectName('nFrameEven')


		self.widget = QWidget(self)
		self.vbox = QVBoxLayout()
		self.vbox.setSpacing(3)
		self.vbox.addWidget(frame)
		self.vbox.addWidget(self.browser)

		self.vbox.setContentsMargins(0,0,0,0)
		self.widget.setLayout(self.vbox)
		self.scroll = QScrollArea(self)
		self.scroll.setWidget(self.widget)
		self.scroll.setWidgetResizable(True)
		self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
		self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

		vbox1 = QVBoxLayout()
		vbox1.setContentsMargins(0,0,0,0)
		vbox1.setSpacing(0)

		vbox1.addWidget(self.scroll)
		self.setLayout(vbox1)
Example #8
0
    def __init__(self, parent: QWizardPage, title: str, help_file_name: str) -> None:
        WindowModalDialog.__init__(self, parent)

        self.setWindowTitle(f"Wallet Wizard - Help for {title}")
        self.setMinimumSize(450, 400)

        release_html = read_resource_text("wallet-wizard", f"{help_file_name}.html")

        widget = QTextBrowser()
        widget.document().setDocumentMargin(15)
        widget.setOpenExternalLinks(True)
        widget.setAcceptRichText(True)
        widget.setHtml(release_html)

        vbox = QVBoxLayout(self)
        # vbox.setSizeConstraint(QVBoxLayout.SetFixedSize)
        vbox.addWidget(widget)
        vbox.addLayout(Buttons(OkButton(self)))
Example #9
0
    def __init__(self, parent: QWidget, title: str, help_dirname: str,
                 help_file_name: str) -> None:
        super().__init__(parent)

        self.setWindowTitle(_("ElectrumSV - Help for {}").format(title))
        self.setMinimumSize(450, 400)

        release_html = read_resource_text(help_dirname,
                                          f"{help_file_name}.html")

        widget = QTextBrowser()
        widget.document().setDocumentMargin(15)
        widget.setOpenExternalLinks(True)
        widget.setAcceptRichText(True)
        widget.setHtml(release_html)

        vbox = QVBoxLayout(self)
        vbox.addWidget(widget)
        vbox.addLayout(Buttons(OkButton(self)))
Example #10
0
    def __init__(self, parent: QWidget, help_dirname: str,
                 help_file_name: str) -> None:
        super().__init__(parent)

        self.setWindowTitle(_("ElectrumSV - In-Wallet Help"))
        self.setMinimumSize(450, 400)

        source_path = text_resource_path(help_dirname,
                                         f"{help_file_name}.html")

        widget = QTextBrowser()
        widget.document().setDocumentMargin(15)
        widget.setOpenLinks(True)
        widget.setOpenExternalLinks(True)
        widget.setAcceptRichText(True)
        widget.setSource(QUrl.fromLocalFile(source_path))

        vbox = QVBoxLayout(self)
        vbox.addWidget(widget)
        vbox.addLayout(Buttons(OkButton(self)))
Example #11
0
class GuiAbout(QDialog):
    def __init__(self, theParent):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiAbout ...")
        self.setObjectName("GuiAbout")

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        self.outerBox = QVBoxLayout()
        self.innerBox = QHBoxLayout()
        self.innerBox.setSpacing(self.mainConf.pxInt(16))

        self.setWindowTitle(self.tr("About novelWriter"))
        self.setMinimumWidth(self.mainConf.pxInt(650))
        self.setMinimumHeight(self.mainConf.pxInt(600))

        nPx = self.mainConf.pxInt(96)
        self.nwIcon = QLabel()
        self.nwIcon.setPixmap(
            self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
        self.lblName = QLabel("<b>novelWriter</b>")
        self.lblVers = QLabel("v%s" % nw.__version__)
        self.lblDate = QLabel(
            datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x"))

        self.leftBox = QVBoxLayout()
        self.leftBox.setSpacing(self.mainConf.pxInt(4))
        self.leftBox.addWidget(self.nwIcon, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblName, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblVers, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblDate, 0, Qt.AlignCenter)
        self.leftBox.addStretch(1)
        self.innerBox.addLayout(self.leftBox)

        # Pages
        self.pageAbout = QTextBrowser()
        self.pageAbout.setOpenExternalLinks(True)
        self.pageAbout.document().setDocumentMargin(self.mainConf.pxInt(16))

        self.pageNotes = QTextBrowser()
        self.pageNotes.setOpenExternalLinks(True)
        self.pageNotes.document().setDocumentMargin(self.mainConf.pxInt(16))

        self.pageLicense = QTextBrowser()
        self.pageLicense.setOpenExternalLinks(True)
        self.pageLicense.document().setDocumentMargin(self.mainConf.pxInt(16))

        # Main Tab Area
        self.tabBox = QTabWidget()
        self.tabBox.addTab(self.pageAbout, self.tr("About"))
        self.tabBox.addTab(self.pageNotes, self.tr("Release"))
        self.tabBox.addTab(self.pageLicense, self.tr("Licence"))
        self.innerBox.addWidget(self.tabBox)

        # OK Button
        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
        self.buttonBox.accepted.connect(self._doClose)

        self.outerBox.addLayout(self.innerBox)
        self.outerBox.addWidget(self.buttonBox)
        self.setLayout(self.outerBox)

        logger.debug("GuiAbout initialisation complete")

        return

    def populateGUI(self):
        """Populate tabs with text.
        """
        qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
        self._setStyleSheet()
        self._fillAboutPage()
        self._fillNotesPage()
        self._fillLicensePage()
        qApp.restoreOverrideCursor()
        return

    def showReleaseNotes(self):
        """Show the release notes.
        """
        self.tabBox.setCurrentWidget(self.pageNotes)
        return

    ##
    #  Internal Functions
    ##

    def _fillAboutPage(self):
        """Generate the content for the About page.
        """
        aboutMsg = (
            "<h2>{title1}</h2>"
            "<p>{copy}</p>"
            "<p>{link}</p>"
            "<p>{intro}</p>"
            "<p>{license1}</p>"
            "<p>{license2}</p>"
            "<p>{license3}</p>"
            "<h3>{title2}</h3>"
            "<p>{credits}</p>"
        ).format(
            title1=self.tr("About novelWriter"),
            copy=nw.__copyright__,
            link=self.tr("Website: {0}").format(
                f"<a href='{nw.__url__}'>{nw.__domain__}</a>"),
            title2=self.tr("Credits"),
            credits=self._wrapTable([
                (self.tr("Developer"), "Veronica Berglyd Olsen"),
                (self.tr("Concept"), "Veronica Berglyd Olsen, Marian Lückhof"),
                (self.tr("i18n"), "Bruno Meneguello"),
            ]),
            intro=self.
            tr("novelWriter is a markdown-like text editor designed for organising and "
               "writing novels. It is written in Python 3 with a Qt5 GUI, using PyQt5."
               ),
            license1=self.
            tr("novelWriter is free software: you can redistribute it and/or modify it "
               "under the terms of the GNU General Public License as published by the "
               "Free Software Foundation, either version 3 of the License, or (at your "
               "option) any later version."),
            license2=self.
            tr("novelWriter is distributed in the hope that it will be useful, but "
               "WITHOUT ANY WARRANTY; without even the implied warranty of "
               "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."),
            license3=self.tr(
                "See the Licence tab for the full licence text, or visit the "
                "GNU website at {0} for more details."
            ).format(
                "<a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a>"
            ),
        )

        aboutMsg += "<h4>%s</h4><p>%s</p>" % (
            self.tr("Translations"),
            self._wrapTable([
                ("English", "Veronica Berglyd Olsen"),
                ("Français", "Jan Lüdke (jyhelle)"),
                ("Norsk Bokmål", "Veronica Berglyd Olsen"),
                ("Português", "Bruno Meneguello"),
            ]))

        theTheme = self.theParent.theTheme
        theIcons = self.theParent.theTheme.theIcons
        if theTheme.themeName and theTheme.themeAuthor != "N/A":
            licURL = f"<a href='{theTheme.themeLicenseUrl}'>{theTheme.themeLicense}</a>"
            aboutMsg += "<h4>%s</h4><p>%s</p>" % (
                self.tr("Theme: {0}").format(theTheme.themeName),
                self._wrapTable([
                    (self.tr("Author"), theTheme.themeAuthor),
                    (self.tr("Credit"), theTheme.themeCredit),
                    (self.tr("Licence"), licURL),
                ]))

        if theIcons.themeName:
            licURL = f"<a href='{theIcons.themeLicenseUrl}'>{theIcons.themeLicense}</a>"
            aboutMsg += "<h4>%s</h4><p>%s</p>" % (
                self.tr("Icons: {0}").format(theIcons.themeName),
                self._wrapTable([
                    (self.tr("Author"), theIcons.themeAuthor),
                    (self.tr("Credit"), theIcons.themeCredit),
                    (self.tr("Licence"), licURL),
                ]))

        if theTheme.syntaxName:
            licURL = f"<a href='{theTheme.syntaxLicenseUrl}'>{theTheme.syntaxLicense}</a>"
            aboutMsg += "<h4>%s</h4><p>%s</p>" % (
                self.tr("Syntax: {0}").format(theTheme.syntaxName),
                self._wrapTable([
                    (self.tr("Author"), theTheme.syntaxAuthor),
                    (self.tr("Credit"), theTheme.syntaxCredit),
                    (self.tr("Licence"), licURL),
                ]))

        self.pageAbout.setHtml(aboutMsg)

        return

    def _fillNotesPage(self):
        """Load the content for the Release Notes page.
        """
        docPath = os.path.join(self.mainConf.assetPath, "text",
                               "release_notes.htm")
        if os.path.isfile(docPath):
            with open(docPath, mode="r", encoding="utf8") as inFile:
                helpText = inFile.read()
            self.pageNotes.setHtml(helpText)
        else:
            self.pageNotes.setHtml("Error loading release notes text ...")
        return

    def _fillLicensePage(self):
        """Load the content for the Licence page.
        """
        docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
        if os.path.isfile(docPath):
            with open(docPath, mode="r", encoding="utf8") as inFile:
                helpText = inFile.read()
            self.pageLicense.setHtml(helpText)
        else:
            self.pageLicense.setHtml("Error loading licence text ...")
        return

    def _wrapTable(self, theData):
        """Wrap a list of label/value tuples in a html table.
        """
        theTable = []
        for aLabel, aValue in theData:
            theTable.append(
                f"<tr><td><b>{aLabel}:</b></td><td>{aValue}</td></tr>")
        return "<table>%s</table>" % "".join(theTable)

    def _setStyleSheet(self):
        """Set stylesheet for all browser tabs
        """
        styleSheet = ("h1, h2, h3, h4 {{"
                      "  color: rgb({hColR},{hColG},{hColB});"
                      "}}\n"
                      "a {{"
                      "  color: rgb({hColR},{hColG},{hColB});"
                      "}}\n"
                      "td {{"
                      "  padding-right: 0.8em;"
                      "}}\n").format(
                          hColR=self.theParent.theTheme.colHead[0],
                          hColG=self.theParent.theTheme.colHead[1],
                          hColB=self.theParent.theTheme.colHead[2],
                      )
        self.pageAbout.document().setDefaultStyleSheet(styleSheet)
        self.pageNotes.document().setDefaultStyleSheet(styleSheet)
        self.pageLicense.document().setDefaultStyleSheet(styleSheet)

        return

    def _doClose(self):
        self.close()
        return
Example #12
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)

        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)

        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()

        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)

        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)

        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)

        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a

        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))

        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)

        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)

        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)

        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)

        # set shortcut action
        a = self.shortcutAction = act(
            self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)

        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)

        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)

        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)

        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())

        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)

        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())

        # complete on snippet variables
        self.searchEntry.setCompleter(
            QCompleter([
                ':icon', ':indent', ':menu', ':name', ':python', ':selection',
                ':set', ':symbol', ':template', ':template-run'
            ], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)

    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()

    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass  # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(
                key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(
                key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(
            _("Enter text to search in the snippets list.\n"
              "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(
            map("<p>{0}</p>\n".format, (
                _("Enter text to search in the snippets list, and "
                  "press Enter to apply the currently selected snippet."),
                _("If the search text fully matches the value of the '{name}' variable "
                  "of a snippet, that snippet is selected.").format(
                      name="name"),
                _("If the search text starts with a colon ':', the rest of the "
                  "search text filters snippets that define the given variable. "
                  "After a space a value can also be entered, snippets will then "
                  "match if the value of the given variable contains the text after "
                  "the space."),
                _("E.g. entering {menu} will show all snippets that are displayed "
                  "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))

    def sizeHint(self):
        return self.parent().mainwindow().size() / 4

    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))

    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide()  # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()

    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)

    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)

    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)

    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(
                self, cb, (collection, name))

            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()

    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()),
                      reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()

    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)

    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename,
                                               filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)

    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [
            row for row in range(model.model().rowCount())
            if not self.treeView.isRowHidden(row, QModelIndex())
        ]
        selectedrows = [
            i.row() for i in self.treeView.selectedIndexes()
            if i.column() == 0 and i.row() in allrows
        ]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]

        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        n = len(names)
        caption = app.caption(
            _("dialog title", "Export {num} Snippet", "Export {num} Snippets",
              n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None,
                                               filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(
                    self, _("Error"),
                    _("Can't write to destination:\n\n{url}\n\n{error}").
                    format(url=filename, error=e.strerror))

    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)

    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")

    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None
                                                  ) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(
                    i, QItemSelectionModel.SelectCurrent
                    | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()

    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)

    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
Example #13
0
class AboutWhat(QDialog):

    def __init__(self, parent=None, pytesting=False):
        super(AboutWhat, self).__init__(parent)
        self._pytesting = pytesting
        self.setWindowTitle('About %s' % __appname__)
        self.setWindowIcon(icons.get_icon('master'))
        self.setMinimumSize(800, 700)
        self.setWindowFlags(Qt.Window |
                            Qt.CustomizeWindowHint |
                            Qt.WindowCloseButtonHint)

        self.__initUI__()

    def __initUI__(self):
        """Initialize the GUI."""
        self.manager_updates = None

        # ---- AboutTextBox

        self.AboutTextBox = QTextBrowser()
        self.AboutTextBox.installEventFilter(self)
        self.AboutTextBox.setReadOnly(True)
        self.AboutTextBox.setFixedWidth(850)
        self.AboutTextBox.setFrameStyle(0)
        self.AboutTextBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.AboutTextBox.setOpenExternalLinks(True)

        self.AboutTextBox.setStyleSheet('QTextEdit {background-color:"white"}')
        self.AboutTextBox.document().setDocumentMargin(0)
        self.set_html_in_AboutTextBox()

        # ---- toolbar

        self.ok_btn = QPushButton('OK')
        self.ok_btn.clicked.connect(self.close)

        self.btn_check_updates = QPushButton(' Check for Updates ')
        self.btn_check_updates.clicked.connect(
                self._btn_check_updates_isclicked)

        toolbar = QGridLayout()
        toolbar.addWidget(self.btn_check_updates, 0, 1)
        toolbar.addWidget(self.ok_btn, 0, 2)
        toolbar.setContentsMargins(0, 0, 0, 0)
        toolbar.setColumnStretch(0, 100)

        # ---- Main Grid

        grid = QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(self.AboutTextBox, 0, 1)
        grid.addLayout(toolbar, 1, 1)

        grid.setColumnStretch(1, 500)
        grid.setContentsMargins(10, 10, 10, 10)

        self.setLayout(grid)
        self.ok_btn.setFocus()

    @QSlot()
    def _btn_check_updates_isclicked(self):
        """Handles when the button to check for updates is clicked."""
        self.manager_updates = ManagerUpdates(self, self._pytesting)

    def set_html_in_AboutTextBox(self):
        """Set the text in the About GWHAT text browser widget."""

        # ---- Header logo

        width = 750
        filename = os.path.join(
                __rootdir__, 'ressources', 'WHAT_banner_750px.png')

        # http://doc.qt.io/qt-4.8/richtext-html-subset.html

        if platform.system() == 'Windows':
            fontfamily = "Segoe UI"  # "Cambria" #"Calibri" #"Segoe UI""
        elif platform.system() == 'Linux':
            fontfamily = "Ubuntu"

        html = """
               <html>
               <head>
               <style>
               p {font-size: 16px;
                  font-family: "%s";
                  margin-right:50px;
                  margin-left:50px;
                  }
               p1 {font-size: 16px;
                   font-family: "%s";
                   margin-right:50px;
                   margin-left:50px;
                   }
               </style>
               </head>
               <body>
               """ % (fontfamily, fontfamily)

        # ---- Banner

        html += """
                <p align="left">
                  <br><img src="file:///%s" width="%d"><br>
                </p>
                """ % (filename, width)

        # ---- Copyrights

        html += """
                <br>
                <p1 align="right">
                  GWHAT version %s released on %s<br>
                  Copyright 2014-2018
                  <a href="https://github.com/jnsebgosselin/gwhat/graphs/contributors">
                    GWHAT Project Contributors
                  </a>
                  <br>
                  Licensed under the terms of the GNU General Public License Version 3
                  <br>
                  <a href="%s">%s</a>
                  <br>
                  <br>
                  Created by Jean-S&eacute;bastien Gosselin
                  <br>
                  <a href="mailto:[email protected]">
                    [email protected]
                  </a>
                  <br>
                  <br>
                  Developped and maintained by Jean-S&eacute;bastien Gosselin
                  <br>
                  Institut National de la Recherche Scientifique<br>
                  Research Center Eau-Terre-Environnement, Quebec City,
                  QC, Canada<br>
                  <a href="http://www.ete.inrs.ca/">
                    http://www.ete.inrs.ca
                  </a>
                  <br>
                </p1>
                """ % (__version__, __date__,
                       __project_url__, __project_url__)

        # ---- License

        html += """
                <p align="justify">
                  %s is free software: you can redistribute it and/or
                  modify it under the terms
                  of the GNU General Public License as published by the
                  Free Software Foundation, either version 3 of the
                  License, or (at your option) any later version.
                </p>
                <p align="justify">
                  This program is distributed in the hope that it will be
                  useful, but WITHOUT ANY WARRANTY; without even the
                  implied warranty of MERCHANTABILITY or FITNESS FOR A
                  PARTICULAR PURPOSE. See the GNU General Public
                  License for more details.
                </p>
                <p align="justify">
                  You should have received a copy of the GNU General
                  Public License along with this program.  If not, see
                  <a href="http://www.gnu.org/licenses">
                    http://www.gnu.org/licenses
                  </a>.
                </p>
                </body>
                """ % __namever__

        self.AboutTextBox.setHtml(html)

    # =========================================================================

    def eventFilter(self, obj, event):
        # http://stackoverflow.com/questions/13788452/
        # pyqt-how-to-handle-event-without-inheritance

        # https://srinikom.github.io/pyside-docs/PySide/QtCore/QObject.
        # html#PySide.QtCore.PySide.QtCore.QObject.installEventFilter

        if event.type() == QEvent.FontChange:
            return True  # Eat the event to disable zooming
        else:
            return QWidget.eventFilter(self, obj, event)

    def show(self):
        super(AboutWhat, self).show()
        self.setFixedSize(self.size())
Example #14
0
class DidYouKnowDialog(QDialog):
    def __init__(self, prefs: Preferences, parent=None) -> None:

        super().__init__(parent)
        self.rapidApp = parent
        self.prefs = prefs

        self.setWindowTitle(_("Tip of the Day"))

        self.setSizeGripEnabled(True)

        titleFont = QFont()
        titleFont.setPointSize(titleFont.pointSize() + 3)
        pixsize = int(QFontMetrics(QFont()).height() * 1.75)

        title = QLabel(_('Did you know...?'))
        title.setFont(titleFont)
        pixmap = QIcon(':/tips/did-you-know.svg').pixmap(
            QSize(pixsize, pixsize))  # type: QPixmap

        icon = QLabel()
        icon.setPixmap(pixmap)
        titleLayout = QHBoxLayout()
        titleLayout.addWidget(icon)
        titleLayout.addWidget(title)
        titleLayout.addStretch()

        self.text = QTextBrowser()
        self.text.setOpenExternalLinks(True)
        self.text.setViewportMargins(10, 10, 10, 10)
        self.text.setStyleSheet("""
        QTextEdit { background: palette(base); }
        """)

        self.text.document().setDefaultStyleSheet("""
            b {color: grey;}
            tt {color: darkRed; font-weight: bold;}
            """)

        self.tips = Tips()

        self.showTips = QCheckBox(_('Show tips on startup'))
        self.showTips.setChecked(self.prefs.did_you_know_on_startup)
        self.showTips.stateChanged.connect(self.showTipsChanged)

        self.nextButton = QPushButton(_('&Next'))
        self.previousButton = QPushButton(_('&Previous'))

        buttons = QDialogButtonBox(QDialogButtonBox.Close)
        translateDialogBoxButtons(buttons)
        buttons.addButton(self.previousButton, QDialogButtonBox.ActionRole)
        buttons.addButton(self.nextButton, QDialogButtonBox.ActionRole)
        self.previousButton.clicked.connect(self.previousButtonClicked)
        self.nextButton.clicked.connect(self.nextButtonClicked)
        buttons.rejected.connect(self.close)

        layout = QVBoxLayout()
        self.setLayout(layout)

        layout.addLayout(titleLayout)
        layout.addWidget(self.text)
        layout.addWidget(self.showTips)
        layout.addWidget(buttons)

        settings = QSettings()
        settings.beginGroup("DidYouKnowWindow")

        default_width = 570
        default_height = 350

        size = settings.value("windowSize", QSize(default_width,
                                                  default_height))

        settings.endGroup()
        self.resize(size)

        self.showTip()

    def incrementTip(self) -> None:
        if self.prefs.did_you_know_index + 1 == len(self.tips):
            self.prefs.did_you_know_index = 0
        else:
            self.prefs.did_you_know_index = self.prefs.did_you_know_index + 1

    def decrementTip(self) -> None:
        if self.prefs.did_you_know_index == 0:
            self.prefs.did_you_know_index = len(self.tips) - 1
        else:
            self.prefs.did_you_know_index = self.prefs.did_you_know_index - 1

    def showTip(self) -> None:
        self.text.clear()
        self.text.append(self.tips[self.prefs.did_you_know_index])
        self.text.moveCursor(QTextCursor.Start)

    def showEvent(self, event: QShowEvent) -> None:
        self.nextButton.setDefault(True)
        self.nextButton.setFocus(Qt.OtherFocusReason)
        event.accept()

    @pyqtSlot(int)
    def showTipsChanged(self, state: int) -> None:
        self.prefs.did_you_know_on_startup = state == Qt.Checked

    @pyqtSlot()
    def nextButtonClicked(self) -> None:
        self.incrementTip()
        self.showTip()

    @pyqtSlot()
    def previousButtonClicked(self) -> None:
        self.decrementTip()
        self.showTip()

    @pyqtSlot()
    def activate(self) -> None:
        self.showTip()
        self.setVisible(True)
        self.activateWindow()
        self.raise_()

    def reject(self) -> None:
        """
        Called when user hits escape key
        """

        self.saveSettings()
        if self.rapidApp is None:
            super().reject()

    def closeEvent(self, event: QCloseEvent) -> None:
        self.saveSettings()
        if self.rapidApp is None:
            event.accept()
        else:
            event.ignore()
            self.hide()

    def saveSettings(self) -> None:
        self.incrementTip()
        settings = QSettings()
        settings.beginGroup("DidYouKnowWindow")
        settings.setValue("windowSize", self.size())
        settings.endGroup()
Example #15
0
class mainWindow(QTabWidget):

    sendMessage = pyqtSignal(list, name='sendMessage')
    selectFriend = pyqtSignal(list, name='selectFriend')
    selectAutoGroup = pyqtSignal(list, name='selectAutoGroup')
    imgHeadRequest = pyqtSignal(str, name='imgHeadRequest')
    friendAutoReply = pyqtSignal(int, name='friendAutoReply')  # 朋友自动回复

    chatroom_num = 0  # 群个数
    selectGroupAutoReply = []  # 自动回复的群
    '''
    通讯录信息
    | NickName ,Sex,Province,City,signature,FromUserName|
    '''
    AllFriendsInfo = {}

    def __init__(self):
        super().__init__()

        self.focusID = 0
        self.setStyle('qrc/black.qss')
        self.createActions()
        self.createTrayIcon()
        self.init()

    def setStyle(self, _qssPath):
        with open(_qssPath, encoding='UTF-8') as file:
            str = file.read()
            qss = ''.join(str)
            self.setStyleSheet(qss)

    def init(self):
        self.tabChat = QWidget()
        self.tabContact = QWidget()
        self.tabSet = QWidget()

        self.addTab(self.tabChat, '微信')
        self.addTab(self.tabContact, '通讯录')
        self.addTab(self.tabSet, '设置')

        self.tabChatInit()
        self.setInit()
        self.contactInit()
        # self.leftLayout = QVBoxLayout()
        # self.rightLayout = QVBoxLayout()
        # mainLayout = QGridLayout()
        #
        # self.contact = QListWidget()
        # self.leftLayout.addWidget(self.contact)
        #
        # self.chatroom = QLineEdit()
        # self.chatroom.setText('This is ChatRoom')
        # self.chatlog = QLabel()
        # self.chatlog.setText('This is ChatLog')
        #
        # self.rightLayout.addWidget(self.chatlog)
        # self.rightLayout.addWidget(self.chatroom)
        #
        # mainLayout.addLayout(self.leftLayout, 0, 0, 1, 1)
        # mainLayout.addLayout(self.rightLayout, 0, 1, 1, 3)
        #
        # self.setLayout(mainLayout)
        self.setWindowTitle(self.tr('Wechat_alpha'))

    def addChatFriend(self, _NickName, _RemarkName):

        item = QListWidgetItem()
        str = _NickName
        if _RemarkName is not '':
            str += '[' + _RemarkName + ']'

        item.setText(str)

        self.listChatting.addItem(item)

    # 通讯录写入名单
    def fillContact(self, _fullContact):

        # self.AllFriendsInfo = _fullContact
        for each in _fullContact:
            item = QListWidgetItem()
            str = each['RemarkName']
            if str is '':
                str = each['NickName']
            item.setText(str)
            self.contactList.addItem(item)
            # | NickName, Sex, Province, City, signature, FromUserName |
            self.AllFriendsInfo[str] = [
                each['NickName'], each['Sex'], each['Province'], each['City'],
                each['Signature'], each['UserName']
            ]

    # 群自动回复----获得群名
    def setChatroomFill(self, _chatroom):

        self.chatroom_num = 0
        for each in _chatroom:
            self.chatroom_num += 1
            #self.chatroomInfo[each['NickName']] = each['UserName']
            item = QListWidgetItem()
            str = each['NickName']
            item.setText(str)
            self.allGroupList.addItem(item)
        #print(self.chatroomInfo)

    def contactInit(self):

        size = self.size()

        self.contactList = QListWidget()
        self.contactList.setFixedSize(size.width() / 3, size.height())
        self.contactList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)
        self.contactList.itemClicked.connect(self.contactListClick)

        infoWidget = QWidget()
        infoWidget.setFixedSize(size.width() * 2 / 3, size.height())

        topLayout = QGridLayout()
        midLayout = QVBoxLayout()
        bottomLayout = QHBoxLayout()

        # top
        self.headLabel = QLabel()  # 头像
        self.headLabel.setFixedSize(150, 150)
        self.headLabel.setScaledContents(True)

        self.signatureLabel = QLabel()  # 签名
        self.signatureLabel.setAlignment(QtCore.Qt.AlignVCenter)
        self.nickNameLabel = QLabel()  # 微信名
        self.nickNameLabel.setAlignment(QtCore.Qt.AlignVCenter)

        topLayout.addWidget(self.nickNameLabel, 1, 0, 1, 3)
        topLayout.addWidget(self.signatureLabel, 2, 0, 1, 3)
        topLayout.addWidget(self.headLabel, 0, 1, 1, 1)

        # mid
        self.remarkNameLabel = QLabel()  # 备注
        self.cityLabel = QLabel()  # 城市

        midLayout.addWidget(self.remarkNameLabel)
        midLayout.addWidget(self.cityLabel)

        # bottom
        self.sendMsgBtn = QPushButton('发消息')

        bottomLayout.addWidget(self.sendMsgBtn)

        layout = QGridLayout()

        infoLayout = QVBoxLayout()
        infoLayout.addLayout(topLayout)
        infoLayout.addLayout(midLayout)
        infoLayout.addLayout(bottomLayout)
        infoLayout.addSpacing(10)

        infoWidget.setLayout(infoLayout)
        layout.addWidget(self.contactList, 0, 0, 1, 1)
        layout.addWidget(infoWidget, 0, 1, 1, 2)

        self.tabContact.setLayout(layout)

    def setInit(self):

        setTab = QTabWidget(self.tabSet)
        setTab.setTabPosition(QTabWidget.West)  # 方向

        size = self.size()

        #############################自动回复################################
        btnAutoSet = QPushButton('应用')
        btnAutoCancel = QPushButton('取消')
        btnAutoCancel.clicked.connect(self.clearSelectList)
        btnAutoSet.clicked.connect(self.setSelectList)

        btnLayout = QHBoxLayout()
        btnLayout.addWidget(btnAutoSet)
        btnLayout.addSpacing(5)
        btnLayout.addWidget(btnAutoCancel)

        self.allGroupList = QListWidget()
        self.selectGroupList = QListWidget()  # 选定自动回复的

        self.allGroupList.setFixedSize(size.width() * 3 / 7,
                                       size.height() * 2 / 3)
        self.selectGroupList.setFixedSize(size.width() * 3 / 7,
                                          size.height() * 2 / 3)

        self.allGroupList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)
        self.selectGroupList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)

        self.allGroupList.itemDoubleClicked.connect(self.aGroupDoubleClick)
        self.selectGroupList.itemDoubleClicked.connect(self.sGroupDoubleClick)

        self.setAutoLayout = QGridLayout()
        self.autoReplyFriend = QCheckBox('自动回复')
        self.autoReplyFriend.stateChanged.connect(self.setFriendAutoReply)

        self.setAutoLayout.setSpacing(10)

        self.setAutoLayout.addWidget(self.autoReplyFriend, 0, 0, 1, 1)
        self.setAutoLayout.addWidget(self.allGroupList, 1, 0, 10, 1)
        self.setAutoLayout.addWidget(self.selectGroupList, 1, 1, 10, 1)
        self.setAutoLayout.addLayout(btnLayout, 12, 1, 1, 1)

        # for each in self.ChatroomCheckBoxList:
        #     self.setAutoLayout.addWidget(each)
        tabAuto = QWidget()
        tabAuto.setLayout(self.setAutoLayout)
        #####################################################################
        # 其他
        self.showLabel = QLabel()
        self.showLabel.setScaledContents(True)
        self.showLabel.setFixedSize(size.width() * 2 / 3, size.width() * 2 / 3)
        sexDisttibutionBtn = QPushButton('性别分布')
        wordCouldBtn = QPushButton('签名词图')

        sexDisttibutionBtn.clicked.connect(self.calSex)
        wordCouldBtn.clicked.connect(self.generateWordCloud)

        layout = QGridLayout()

        layout.addWidget(self.showLabel, 0, 0, 2, 2)
        layout.addWidget(sexDisttibutionBtn, 2, 0, 1, 1)
        layout.addWidget(wordCouldBtn, 2, 1, 1, 1)
        tabFun = QWidget()
        tabFun.setLayout(layout)
        #####################################################################
        setTab.addTab(tabAuto, '自动回复')
        setTab.addTab(tabFun, '特色功能')
        # setTab.addTab('其他')

    def tabChatInit(self):

        size = self.size()

        layout = QGridLayout()
        self.listChatting = QListWidget()
        self.listChatting.setFixedSize(size.width() / 3, size.height())

        self.chatLog = QTextBrowser()
        self.chatLog.document().setMaximumBlockCount(1000)  # 限制1000行
        self.chatLog.setFixedSize(size.width() * 2 / 3, size.height() * 2 / 3)

        self.textInput = QTextEdit()
        self.textInput.setFixedSize(size.width() * 2 / 3, size.height() / 4)

        self.btnSend = QPushButton()
        self.btnSend.setText('发送')

        # 显示正在聊天的朋友
        self.chattingFri = QLabel('当前聊天朋友:_____')

        self.btnSend.clicked.connect(self.sendMsg)
        self.listChatting.itemClicked.connect(self.listClick)

        self.chatLog.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.chatLog.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        layout.addWidget(self.listChatting, 0, 0, 6, 1)
        layout.addWidget(self.chatLog, 0, 1, 3, 3)
        layout.addWidget(self.textInput, 3, 1, 2, 3)
        layout.addWidget(self.chattingFri, 5, 1, 1, 1)
        layout.addWidget(self.btnSend, 5, 3, 1, 1)

        self.tabChat.setLayout(layout)

    def showChatLog(self, _Msg):

        # count = -1
        #         # for count, line in enumerate(open(thefilepath, 'rU')):
        #         #     pass
        #         # count += 1
        msg_time = time.strftime("%Y-%m-%d %H:%M:%S",
                                 time.localtime(_Msg['time']))
        content = _Msg['content']

        if _Msg['fromusr'] == _Msg['selfusr']:
            self.chatLog.append(msg_time + '\n' + '我' + ':' + content + '\n')
        else:
            fromFriend = _Msg['remarkname']
            self.chatLog.append(msg_time + '\n' + fromFriend + ':' + content +
                                '\n')

    def showSendChatLog(self, _Msg):
        msg_time = time.strftime("%Y-%m-%d %H:%M:%S",
                                 time.localtime(int(time.time())))
        content = _Msg[0]
        self.chatLog.append(msg_time + '\n' + '我' + ':' + content + '\n')

    @pyqtSlot()
    def sendMsg(self):

        sMsg = self.textInput.toPlainText()
        if sMsg != '':
            self.textInput.clear()
            self.sendMessage.emit([sMsg])

    @pyqtSlot(QListWidgetItem)
    def listClick(self, item):
        self.selectFriend.emit([item.text()])

    @pyqtSlot(QListWidgetItem)
    def contactListClick(self, item):
        global curTmpImg
        # | NickName, Sex, Province, City, signature, FromUserName |
        cur = self.AllFriendsInfo[item.text()]
        self.imgHeadRequest.emit(cur[5])

        if curTmpImg:
            png = QtGui.QPixmap()
            png.loadFromData(curTmpImg)
            #png.scaled((50,50))
            self.headLabel.setPixmap(png)
            curTmpImg = None

        self.signatureLabel.setText('签名      ' + ''.join(cur[4]))  # 签名
        str = ''.join(cur[0])
        if cur[1] == 1:
            str += ' ♂'
        else:
            str += '  ♀'
        self.nickNameLabel.setText('微信      ' + str)  # 微信名
        self.remarkNameLabel.setText('备注        ' + item.text())  # 备注
        self.cityLabel.setText('地区      ' +
                               ''.join(cur[2] + ' ' + cur[3]))  # 城市

    # add to select list
    @pyqtSlot(QListWidgetItem)
    def aGroupDoubleClick(self, item):
        select = item.text()
        item = QListWidgetItem()
        item.setText(select)
        self.selectGroupList.addItem(item)
        self.selectGroupAutoReply.append(select)

    # remove select item from list
    @pyqtSlot(QListWidgetItem)
    def sGroupDoubleClick(self, item):

        select = item.text()
        self.selectGroupList.removeItemWidget(
            self.selectGroupList.takeItem(self.selectGroupList.row(item)))
        self.selectGroupAutoReply.remove(select)

    @pyqtSlot(int)
    def setFriendAutoReply(self, _state):
        self.friendAutoReply.emit(_state)

    # 清空选定
    def clearSelectList(self):
        self.selectGroupList.clear()
        self.selectGroupAutoReply.clear()

    # 应用群自动回复
    def setSelectList(self):
        self.selectAutoGroup.emit(self.selectGroupAutoReply)

    # 获取头像
    def postUserHead(self, _img):
        global curTmpImg
        curTmpImg = _img
        #print(_img)

    # 更改当前聊天朋友名字显示
    def changeChattingFri(self, _str):
        self.chattingFri.setText('当前发送:' + _str[0])

    #  计算性别
    def calSex(self):

        # 设置全局字体
        plt.rcParams['font.sans-serif'] = ['SimHei']
        # 解决‘-’表现为方块的问题
        plt.rcParams['axes.unicode_minus'] = False

        female = 0
        total = len(self.AllFriendsInfo)
        for each in self.AllFriendsInfo.values():

            if each[1] is 2:
                female += 1
        male = total - female

        data = {'男性(人)': (male, '#7199cf'), '女性(人)': (female, '#ffff10')}

        # 设置绘图对象的大小
        fig = plt.figure(figsize=(8, 8))

        sex = data.keys()
        values = [x[0] for x in data.values()]
        colors = [x[1] for x in data.values()]

        ax1 = fig.add_subplot(111)
        ax1.set_title('性别比例')

        labels = [
            '{}:{}'.format(city, value) for city, value in zip(sex, values)
        ]

        # 设置饼图的凸出显示
        explode = [0, 0.1]

        # 画饼状图, 并且指定标签和对应的颜色
        # 指定阴影效果
        ax1.pie(values,
                labels=labels,
                colors=colors,
                explode=explode,
                shadow=True)

        pngPath = 'cache/_sd/sd.jpg'
        plt.savefig(pngPath)
        # plt.show()
        if os.path.exists(pngPath):
            png = QtGui.QPixmap(pngPath)
            self.showLabel.setPixmap(png)

    # 生成词云
    def generateWordCloud(self):

        signature = [each[4] for each in self.AllFriendsInfo.values()]

        text = ','.join(signature)
        pattern = re.compile('<span.*?</span>')  # 匹配表情
        text = re.sub(repl='', string=text, pattern=pattern)  # 删除表情

        coloring = np.array(Image.open("qrc/back.jpg"))
        my_wordcloud = wordcloud.WordCloud(
            background_color="white",
            max_words=2000,
            mask=coloring,
            max_font_size=60,
            random_state=42,
            scale=2,
            font_path="qrc/FZSTK.ttf"
        ).generate(
            text
        )  # 生成词云。font_path="C:\Windows\Fonts\msyhl.ttc"指定字体,有些字不能解析中文,这种情况下会出现乱码。

        file_name_p = 'cache/word/wc.jpg'
        my_wordcloud.to_file(file_name_p)  # 保存图片

        if os.path.exists(file_name_p):
            png = QtGui.QPixmap(file_name_p)
            self.showLabel.setPixmap(png)

    def createTrayIcon(self):
        '''
        创建托盘图标,可以让程序最小化到windows托盘中运行
        :return:
        '''
        self.trayIconMenu = QMenu(self)
        self.trayIconMenu.addAction(self.restoreAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.quitAction)
        self.trayIcon = QSystemTrayIcon(self)
        self.trayIcon.setContextMenu(self.trayIconMenu)
        self.trayIcon.setIcon(QIcon('qrc/icon.png'))
        self.setWindowIcon(QIcon('qrc/icon.png'))
        self.trayIcon.show()

    def createActions(self):
        '''
        为托盘图标添加功能
        :return:
        '''
        self.restoreAction = QAction("来喽", self, triggered=self.showNormal)
        self.quitAction = QAction("告辞",
                                  self,
                                  triggered=QApplication.instance().quit)

    def iconActivated(self, reason):
        '''
        激活托盘功能
        :param reason:
        :return:
        '''
        if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick):
            self.showNormal()

    # 弹窗提醒
    def msgWarning(self, _message, _type):

        if _type == 0:
            QMessageBox.information(self, "红包提醒", _message, QMessageBox.Yes)
        else:
            QMessageBox.information(self, "撤回提醒", _message, QMessageBox.Yes)
Example #16
0
class ClientUI(QWidget):
    def __init__(self):
        super().__init__()
        # self.window = QWidget()
        self.resize(1000, 600)
        self.setWindowTitle("拍一拍聊天室")

        # 聊天窗口
        self.messageBrowser = QTextBrowser()

        # 字体选择
        self.fontComboBox = QFontComboBox()
        self.fontComboBox.setObjectName("fontComboBox")
        self.fontComboBox.currentFontChanged.connect(
            self.on_fontComboBox_currentFontChanged)

        # 字体大小
        self.sizeComboBox = QComboBox()
        self.sizeComboBox.setObjectName("SizeComboBox")
        self.sizeComboBox.setCurrentIndex(0)
        for i in range(31):
            self.sizeComboBox.addItem("")
        _translate = QCoreApplication.translate
        self.sizeComboBox.setCurrentText(_translate("Widget", "10"))
        for i in range(31):
            self.sizeComboBox.setItemText(i, _translate("Widget", str(i + 10)))
        self.sizeComboBox.currentIndexChanged.connect(
            self.on_SizeComboBox_currentIndexChanged)

        # 加粗
        self.boldToolBtn = QToolButton()
        self.boldToolBtn.setContextMenuPolicy(Qt.DefaultContextMenu)
        icon = QIcon()
        root = QFileInfo(__file__).absolutePath()  # 获取根目录
        icon.addPixmap(QPixmap(root + "/image/bold.png"), QIcon.Normal,
                       QIcon.Off)
        self.boldToolBtn.setIcon(icon)
        self.boldToolBtn.setIconSize(QSize(22, 22))
        self.boldToolBtn.setCheckable(True)
        self.boldToolBtn.setAutoRaise(True)
        self.boldToolBtn.setObjectName("boldToolBtn")
        self.boldToolBtn.setToolTip(_translate("Widget", "加粗"))
        self.boldToolBtn.setText(_translate("Widget", "..."))
        self.boldToolBtn.clicked.connect(self.on_boldToolBtn_clicked)

        # 斜体
        self.italicToolBtn = QToolButton()
        icon1 = QIcon()
        icon1.addPixmap(QPixmap(root + "/image/italic.png"), QIcon.Normal,
                        QIcon.Off)
        self.italicToolBtn.setIcon(icon1)
        self.italicToolBtn.setIconSize(QSize(22, 22))
        self.italicToolBtn.setCheckable(True)
        self.italicToolBtn.setAutoRaise(True)
        self.italicToolBtn.setObjectName("italicToolBtn")
        self.italicToolBtn.setToolTip(_translate("Widget", "倾斜"))
        self.italicToolBtn.setText(_translate("Widget", "..."))
        self.italicToolBtn.clicked.connect(self.on_italicToolBtn_clicked)

        # 下划线
        self.underlineToolBtn = QToolButton()
        icon2 = QIcon()
        icon2.addPixmap(QPixmap(root + "/image/under.png"), QIcon.Normal,
                        QIcon.Off)
        self.underlineToolBtn.setIcon(icon2)
        self.underlineToolBtn.setIconSize(QSize(22, 22))
        self.underlineToolBtn.setCheckable(True)
        self.underlineToolBtn.setAutoRaise(True)
        self.underlineToolBtn.setObjectName("underlineToolBtn")
        self.underlineToolBtn.setToolTip(_translate("Widget", "下划线"))
        self.underlineToolBtn.setText(_translate("Widget", "..."))
        self.underlineToolBtn.clicked.connect(self.on_underlineToolBtn_clicked)

        # 颜色
        self.colorToolBtn = QToolButton()
        icon3 = QIcon()
        icon3.addPixmap(QPixmap(root + "/image/color.png"), QIcon.Normal,
                        QIcon.Off)
        self.colorToolBtn.setIcon(icon3)
        self.colorToolBtn.setIconSize(QSize(22, 22))
        self.colorToolBtn.setAutoRaise(True)
        self.colorToolBtn.setObjectName("colorToolBtn")
        self.colorToolBtn.setToolTip(_translate("Widget", "更改字体颜色"))
        self.colorToolBtn.setText(_translate("Widget", "..."))
        self.colorToolBtn.clicked.connect(self.on_colorToolBtn_clicked)

        # 聊天记录
        self.saveToolBtn = QToolButton()
        icon4 = QIcon()
        icon4.addPixmap(QPixmap(root + "/image/save.png"), QIcon.Normal,
                        QIcon.Off)
        self.saveToolBtn.setIcon(icon4)
        self.saveToolBtn.setIconSize(QSize(22, 22))
        self.saveToolBtn.setAutoRaise(True)
        self.saveToolBtn.setObjectName("saveToolBtn")
        self.saveToolBtn.setToolTip(_translate("Widget", "保存聊天记录"))
        self.saveToolBtn.setText(_translate("Widget", "..."))
        self.saveToolBtn.clicked.connect(self.on_saveToolBtn_clicked)

        self.clearToolBtn = QToolButton()
        icon5 = QIcon()
        icon5.addPixmap(QPixmap(root + "/image/clear.png"), QIcon.Normal,
                        QIcon.Off)
        self.clearToolBtn.setIcon(icon5)
        self.clearToolBtn.setIconSize(QSize(22, 22))
        self.clearToolBtn.setAutoRaise(True)
        self.clearToolBtn.setObjectName("clearToolBtn")
        self.clearToolBtn.setToolTip(_translate("Widget", "清空聊天记录"))
        self.clearToolBtn.setText(_translate("Widget", "..."))
        self.clearToolBtn.clicked.connect(self.on_clearToolBtn_clicked)

        # 发送按钮
        self.sendButton = QPushButton('发送')
        self.sendButton.clicked.connect(self.sendChatMsg)

        # 发送功能横向布局
        functionboxLayout = QHBoxLayout()
        functionboxLayout.addWidget(self.fontComboBox)
        functionboxLayout.addWidget(self.sizeComboBox)
        functionboxLayout.addWidget(self.boldToolBtn)
        functionboxLayout.addWidget(self.italicToolBtn)
        functionboxLayout.addWidget(self.underlineToolBtn)
        functionboxLayout.addWidget(self.colorToolBtn)
        functionboxLayout.addWidget(self.saveToolBtn)
        functionboxLayout.addWidget(self.clearToolBtn)
        functionboxLayout.addStretch(1)
        functionboxLayout.addWidget(self.sendButton)

        # 输入框
        self.messageEdit = QTextEdit()

        # 左侧竖向布局,三行
        vhoxLayout_left = QVBoxLayout()
        vhoxLayout_left.addWidget(self.messageBrowser)
        vhoxLayout_left.addLayout(functionboxLayout)
        vhoxLayout_left.addWidget(self.messageEdit)
        vhoxLayout_left.setStretch(0, 4)
        vhoxLayout_left.setStretch(1, 1)
        vhoxLayout_left.setStretch(2, 2)

        # 在线用户列表
        self.userView = QTreeWidget()
        self.userView.setHeaderLabels(["用户列表"])
        self.userView_online_node = QTreeWidgetItem(self.userView)
        self.userView_online_node.setText(0, "在线用户")
        self.userView_all_node = QTreeWidgetItem(self.userView)
        self.userView_all_node.setText(0, "所有用户")
        self.userView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.userView.customContextMenuRequested.connect(self.userView_menu)

        # 公共栏Label
        self.announcement_label = QLabel()
        self.announcement_label.setText("公告栏")

        # 公告栏
        self.announcement_edit = QTextEdit()
        self.announcement_edit.setEnabled(False)

        # 左侧竖向布局,一整块
        vhoxLayout_right = QVBoxLayout()
        vhoxLayout_right.addWidget(self.userView)
        vhoxLayout_right.addWidget(self.announcement_label)
        vhoxLayout_right.addWidget(self.announcement_edit)

        # 最大布局,横向两列
        hboxLayout = QHBoxLayout(self)
        hboxLayout.addLayout(vhoxLayout_left)
        hboxLayout.addLayout(vhoxLayout_right)
        hboxLayout.setStretch(0, 3)
        hboxLayout.setStretch(1, 1)

        self.show()

    def update_admin_UI(self):
        self.announcement_edit.setEnabled(True)
        self.announcement_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.announcement_edit.customContextMenuRequested.connect(
            lambda: handler.announcement(self.announcement_edit.toPlainText()))

    def userView_menu(self):
        item = self.userView.currentItem()
        menu = QMenu(self.userView)
        if item.parent().text(0) == '在线用户':
            menu.addAction('拍一拍').triggered.connect(
                lambda: handler.pai_yi_pai(myname, item.text(0)))
        elif admin:
            menu.addAction('删除').triggered.connect(
                lambda: handler.delete_user(item.text(0)))
        menu.exec_(QCursor.pos())

    def mergeFormatDocumentOrSelection(self, format):

        cursor = self.messageEdit.textCursor()
        if not cursor.hasSelection():
            cursor.select(QTextCursor.Document)
        cursor.mergeCharFormat(format)
        self.messageEdit.mergeCurrentCharFormat(format)

    def on_fontComboBox_currentFontChanged(self, p0):
        fmt = QTextCharFormat()
        fmt.setFont(p0)
        # fmt.setFontFamily(p0)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    # 字体大小
    def on_SizeComboBox_currentIndexChanged(self, p0):
        fmt = QTextCharFormat()
        p0 += 10
        fmt.setFontPointSize(p0)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    def on_boldToolBtn_clicked(self, checked):
        fmt = QTextCharFormat()
        fmt.setFontWeight(checked and QFont.Bold or QFont.Normal)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    def on_italicToolBtn_clicked(self, checked):
        fmt = QTextCharFormat()
        fmt.setFontItalic(checked)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    def on_underlineToolBtn_clicked(self, checked):
        fmt = QTextCharFormat()
        fmt.setFontUnderline(checked)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    def on_colorToolBtn_clicked(self):
        col = QColorDialog.getColor(self.messageEdit.textColor(), self)
        if not col.isValid():
            return
        fmt = QTextCharFormat()
        fmt.setForeground(col)
        self.mergeFormatDocumentOrSelection(fmt)
        self.messageEdit.setFocus()

    def on_saveToolBtn_clicked(self):
        print(self.messageBrowser.document().isEmpty())
        if self.messageBrowser.document().isEmpty():
            QMessageBox.warning(self, "警告", "聊天记录为空,无法保存!", QMessageBox.Ok)
        else:
            fileName = QFileDialog.getSaveFileName(self, "保存聊天记录", "./聊天记录",
                                                   ("HTML-Files (*.html)"))

            if fileName[0]:
                if self.saveFile(fileName[0]):
                    QMessageBox.information(self, "聊天记录保存", "保存成功!")

    def saveFile(self, fileName):

        SuffixFileName = fileName.split(".")[1]
        if SuffixFileName in ("html"):
            content = self.messageBrowser.toHtml()
        else:
            content = self.messageBrowser.toPlainText()
        try:
            with codecs.open(fileName, 'w', encoding="utf-8") as f:
                f.write(content)
            return True
        except IOError:
            QMessageBox.critical(self, "保存错误", "聊天记录保存失败!")
            return False

    def on_clearToolBtn_clicked(self):
        self.messageBrowser.clear()

    def getMessage(self):
        msg = self.messageEdit.toHtml()
        self.messageEdit.clear()
        self.messageEdit.setFocus()
        return msg

    def sendChatMsg(self):
        sendMsg(ALL_MSG, self.getMessage(), myname)
        self.messageEdit.clear()

    def showMsg(self, msg, name):
        time = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss")
        if name == '拍拍':
            self.messageBrowser.setTextColor(Qt.darkGreen)
            self.messageBrowser.append("[" + name + "] " + time)
            self.messageBrowser.append(msg)
        elif name != myname:
            self.messageBrowser.setTextColor(Qt.blue)
            self.messageBrowser.append("[" + name + "] " + time)
            self.messageBrowser.append(msg)
        else:
            self.messageBrowser.setTextColor(Qt.red)
            self.messageBrowser.append("[我] " + time)
            self.messageBrowser.append(msg)

    def addUserNode(self, group, name):
        self.userView.expandAll()
        newUser = QTreeWidgetItem(group)
        newUser.setText(0, name)
        newUser.setIcon(0, QIcon('image/user.jpg'))
        if (name == myname):
            newUser.setBackground(0, QColor(255, 0, 0, 100))
        if name == '拍拍':
            newUser.setBackground(0, QColor(0, 255, 0, 100))

    def removeUserNode(self, name):
        n = self.userView_online_node.childCount()
        for i in range(n):
            if self.userView_online_node.child(i).text(0) == name:
                self.userView_online_node.removeChild(
                    self.userView_online_node.child(i))
                break

    def removeAllNode(self, name):
        n = self.userView_all_node.childCount()
        for i in range(n):
            if self.userView_all_node.child(i).text(0) == name:
                self.userView_all_node.removeChild(
                    self.userView_all_node.child(i))
                break

    def userJoin(self, name):
        self.messageBrowser.setTextColor(Qt.gray)
        self.messageBrowser.append('系统消息:' + name + '上线了')
        self.addUserNode(self.userView_online_node, name)

    def userLeft(self, name):
        self.messageBrowser.setTextColor(Qt.gray)
        self.messageBrowser.append('系统消息:' + name + '离开了')
        self.removeUserNode(name)

    def pai_yi_pai(self, name, toname):
        self.messageBrowser.setTextColor(Qt.gray)
        self.messageBrowser.append(name + ' 拍了拍 ' + toname)
        if toname == myname:
            app.alert(self, 2000)

    def setHandler(self, handler):
        self.handler = handler

    def handler(self, msg):
        global admin
        type = msg[0]
        if type == ALL_MSG:
            self.showMsg(msg[1], msg[2])
        elif type == USR_JOIN:
            self.userJoin(msg[1])
        elif type == USR_LEFT:
            self.userLeft(msg[1])
        elif type == USR_LOGIN:
            admin = msg[2]
            if admin:
                self.update_admin_UI()
        elif type == ALL_ONLINE_USR:
            for i in msg[1]:
                self.userJoin(i)
            self.announcement_edit.setText(msg[2])
            for i in msg[3]:
                self.addUserNode(self.userView_all_node, i)
        elif type == PAI_YI_PAI:
            self.pai_yi_pai(msg[1], msg[2])
        elif type == ANNOUNCEMENT:
            self.announcement_edit.setText(msg[1])
        elif type == DELETE_USR:
            self.removeUserNode(msg[1])
            self.removeAllNode(msg[1])
Example #17
0
class GuiAbout(QDialog):
    def __init__(self, theParent):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiAbout ...")
        self.setObjectName("GuiAbout")

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        self.outerBox = QVBoxLayout()
        self.innerBox = QHBoxLayout()
        self.innerBox.setSpacing(self.mainConf.pxInt(16))

        self.setWindowTitle("About novelWriter")
        self.setMinimumWidth(self.mainConf.pxInt(650))
        self.setMinimumHeight(self.mainConf.pxInt(600))

        nPx = self.mainConf.pxInt(96)
        self.nwIcon = QLabel()
        self.nwIcon.setPixmap(
            self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
        self.lblName = QLabel("<b>novelWriter</b>")
        self.lblVers = QLabel("v%s" % nw.__version__)
        self.lblDate = QLabel(
            datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x"))

        self.leftBox = QVBoxLayout()
        self.leftBox.setSpacing(self.mainConf.pxInt(4))
        self.leftBox.addWidget(self.nwIcon, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblName, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblVers, 0, Qt.AlignCenter)
        self.leftBox.addWidget(self.lblDate, 0, Qt.AlignCenter)
        self.leftBox.addStretch(1)
        self.innerBox.addLayout(self.leftBox)

        # Pages
        self.pageAbout = QTextBrowser()
        self.pageAbout.setOpenExternalLinks(True)
        self.pageAbout.document().setDocumentMargin(self.mainConf.pxInt(16))

        self.pageLicense = QTextBrowser()
        self.pageLicense.setOpenExternalLinks(True)
        self.pageLicense.document().setDocumentMargin(self.mainConf.pxInt(16))

        # Main Tab Area
        self.tabBox = QTabWidget()
        self.tabBox.addTab(self.pageAbout, "About")
        self.tabBox.addTab(self.pageLicense, "License")
        self.innerBox.addWidget(self.tabBox)

        # OK Button
        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
        self.buttonBox.accepted.connect(self._doClose)

        self.outerBox.addLayout(self.innerBox)
        self.outerBox.addWidget(self.buttonBox)
        self.setLayout(self.outerBox)

        self._setStyleSheet()
        self._fillAboutPage()
        self._fillLicensePage()

        logger.debug("GuiAbout initialisation complete")

        return

    ##
    #  Internal Functions
    ##

    def _fillAboutPage(self):
        """Generate the content for the About page.
        """
        listPrefix = "&nbsp;&nbsp;&bull;&nbsp;&nbsp;"
        aboutMsg = (
            "<h2>About novelWriter</h2>"
            "<p>{copyright:s}.</p>"
            "<p>Website: <a href='{website:s}'>{domain:s}</a></p>"
            "<p>novelWriter is a markdown-like text editor designed for "
            "organising and writing novels. It is written in Python 3 with a "
            "Qt5 GUI, using PyQt5.</p>"
            "<p>novelWriter is free software: you can redistribute it and/or "
            "modify it under the terms of the GNU General Public License as "
            "published by the Free Software Foundation, either version 3 of "
            "the License, or (at your option) any later version.</p>"
            "<p>novelWriter is distributed in the hope that it will be useful, "
            "but WITHOUT ANY WARRANTY; without even the implied warranty of "
            "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p>"
            "<p>See the License tab for the full text, or visit  the GNU website "
            "at <a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a> "
            "for more details.</p>"
            "<h3>Credits</h3>"
            "<p>{credits:s}</p>").format(
                copyright=nw.__copyright__,
                website=nw.__url__,
                domain=nw.__domain__,
                credits="<br/>".join(
                    ["%s%s" % (listPrefix, x) for x in nw.__credits__]),
            )

        theTheme = self.theParent.theTheme
        theIcons = self.theParent.theTheme.theIcons
        if theTheme.themeName:
            aboutMsg += (
                "<h4>Theme: {name:s}</h4>"
                "<p>"
                "<b>Author:</b> {author:s}<br/>"
                "<b>Credit:</b> {credit:s}<br/>"
                "<b>License:</b> <a href='{lic_url:s}'>{license:s}</a>"
                "</p>").format(
                    name=theTheme.themeName,
                    author=theTheme.themeAuthor,
                    credit=theTheme.themeCredit,
                    license=theTheme.themeLicense,
                    lic_url=theTheme.themeLicenseUrl,
                )
        if theIcons.themeName:
            aboutMsg += (
                "<h4>Icons: {name:s}</h4>"
                "<p>"
                "<b>Author:</b> {author:s}<br/>"
                "<b>Credit:</b> {credit:s}<br/>"
                "<b>License:</b> <a href='{lic_url:s}'>{license:s}</a>"
                "</p>").format(
                    name=theIcons.themeName,
                    author=theIcons.themeAuthor,
                    credit=theIcons.themeCredit,
                    license=theIcons.themeLicense,
                    lic_url=theIcons.themeLicenseUrl,
                )
        if theTheme.syntaxName:
            aboutMsg += (
                "<h4>Syntax: {name:s}</h4>"
                "<p>"
                "<b>Author:</b> {author:s}<br/>"
                "<b>Credit:</b> {credit:s}<br/>"
                "<b>License:</b> <a href='{lic_url:s}'>{license:s}</a>"
                "</p>").format(
                    name=theTheme.syntaxName,
                    author=theTheme.syntaxAuthor,
                    credit=theTheme.syntaxCredit,
                    license=theTheme.syntaxLicense,
                    lic_url=theTheme.syntaxLicenseUrl,
                )

        self.pageAbout.setHtml(aboutMsg)

        return

    def _fillLicensePage(self):
        """Load the content for the License page.
        """
        docName = "gplv3_%s.htm" % self.mainConf.guiLang
        docPath = os.path.join(self.mainConf.assetPath, "text", docName)
        if os.path.isfile(docPath):
            with open(docPath, mode="r", encoding="utf8") as inFile:
                helpText = inFile.read()
            self.pageLicense.setHtml(helpText)
        else:
            self.pageLicense.setHtml("Error loading license text ...")
        return

    def _setStyleSheet(self):
        """Set stylesheet for all browser tabs
        """
        styleSheet = ("h1, h2, h3, h4 {{"
                      "  color: rgb({hColR},{hColG},{hColB});"
                      "}}\n"
                      "a {{"
                      "  color: rgb({hColR},{hColG},{hColB});"
                      "}}\n").format(
                          hColR=self.theParent.theTheme.colHead[0],
                          hColG=self.theParent.theTheme.colHead[1],
                          hColB=self.theParent.theTheme.colHead[2],
                      )
        self.pageAbout.document().setDefaultStyleSheet(styleSheet)
        self.pageLicense.document().setDefaultStyleSheet(styleSheet)

        return

    def _doClose(self):
        self.close()
        return
Example #18
0
class Widget(QWidget):
    def __init__(self, panel):
        super(Widget, self).__init__(panel)
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        layout.setSpacing(0)
        
        self.searchEntry = SearchLineEdit()
        self.treeView = QTreeView(contextMenuPolicy=Qt.CustomContextMenu)
        self.textView = QTextBrowser()
        
        applyButton = QToolButton(autoRaise=True)
        editButton = QToolButton(autoRaise=True)
        addButton = QToolButton(autoRaise=True)
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)
        
        splitter = QSplitter(Qt.Vertical)
        top = QHBoxLayout()
        layout.addLayout(top)
        splitter.addWidget(self.treeView)
        splitter.addWidget(self.textView)
        layout.addWidget(splitter)
        splitter.setSizes([200, 100])
        splitter.setCollapsible(0, False)
        
        top.addWidget(self.searchEntry)
        top.addWidget(applyButton)
        top.addSpacing(10)
        top.addWidget(addButton)
        top.addWidget(editButton)
        top.addWidget(self.menuButton)
        
        # action generator for actions added to search entry
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            a.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            icon and a.setIcon(icons.get(icon))
            return a
        
        # hide if ESC pressed in lineedit
        a = act(self.slotEscapePressed)
        a.setShortcut(QKeySequence(Qt.Key_Escape))
        
        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)
        
        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)
        
        # apply button
        a = self.applyAction = act(self.slotApply, 'edit-paste')
        applyButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # add button
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        a.setShortcut(QKeySequence(Qt.Key_Insert))
        addButton.setDefaultAction(a)
        menu.addSeparator()
        menu.addAction(a)
        
        # edit button
        a = self.editAction = act(self.slotEdit, 'document-edit')
        a.setShortcut(QKeySequence(Qt.Key_F2))
        editButton.setDefaultAction(a)
        menu.addAction(a)
        
        # set shortcut action
        a = self.shortcutAction = act(self.slotShortcut, 'preferences-desktop-keyboard-shortcuts')
        menu.addAction(a)
        
        # delete action
        a = self.deleteAction = act(self.slotDelete, 'list-remove')
        a.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Delete))
        menu.addAction(a)
        
        # restore action
        a = self.restoreAction = act(self.slotRestore)
        menu.addSeparator()
        menu.addAction(a)
        
        # help button
        a = self.helpAction = act(self.slotHelp, 'help-contents')
        menu.addSeparator()
        menu.addAction(a)
        
        self.treeView.setSelectionBehavior(QTreeView.SelectRows)
        self.treeView.setSelectionMode(QTreeView.ExtendedSelection)
        self.treeView.setRootIsDecorated(False)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setModel(model.model())
        self.treeView.setCurrentIndex(QModelIndex())
        
        # signals
        self.searchEntry.returnPressed.connect(self.slotReturnPressed)
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.treeView.doubleClicked.connect(self.slotDoubleClicked)
        self.treeView.customContextMenuRequested.connect(self.showContextMenu)
        self.treeView.selectionModel().currentChanged.connect(self.updateText)
        self.treeView.model().dataChanged.connect(self.updateFilter)
        
        # highlight text
        self.highlighter = highlight.Highlighter(self.textView.document())
        
        # complete on snippet variables
        self.searchEntry.setCompleter(QCompleter([
            ':icon', ':indent', ':menu', ':name', ':python', ':selection',
            ':set', ':symbol', ':template', ':template-run'], self.searchEntry))
        self.readSettings()
        app.settingsChanged.connect(self.readSettings)
        app.translateUI(self)
        self.updateColumnSizes()
        self.setAcceptDrops(True)

    def dropEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            filename = ev.mimeData().urls()[0].toLocalFile()
            if filename:
                ev.accept()
                from . import import_export
                import_export.load(filename, self)
        
    def dragEnterEvent(self, ev):
        if not ev.source() and ev.mimeData().hasUrls():
            ev.accept()
        
    def translateUI(self):
        try:
            self.searchEntry.setPlaceholderText(_("Search..."))
        except AttributeError:
            pass # not in Qt 4.6
        shortcut = lambda a: a.shortcut().toString(QKeySequence.NativeText)
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.addAction_.setToolTip(
            _("Add a new snippet. ({key})").format(key=shortcut(self.addAction_)))
        self.editAction.setText(_("&Edit..."))
        self.editAction.setToolTip(
            _("Edit the current snippet. ({key})").format(key=shortcut(self.editAction)))
        self.shortcutAction.setText(_("Configure Keyboard &Shortcut..."))
        self.deleteAction.setText(_("&Remove"))
        self.deleteAction.setToolTip(_("Remove the selected snippets."))
        self.applyAction.setText(_("A&pply"))
        self.applyAction.setToolTip(_("Apply the current snippet."))
        self.importAction.setText(_("&Import..."))
        self.importAction.setToolTip(_("Import snippets from a file."))
        self.exportAction.setText(_("E&xport..."))
        self.exportAction.setToolTip(_("Export snippets to a file."))
        self.restoreAction.setText(_("Restore &Built-in Snippets..."))
        self.restoreAction.setToolTip(
            _("Restore deleted or changed built-in snippets."))
        self.helpAction.setText(_("&Help"))
        self.searchEntry.setToolTip(_(
            "Enter text to search in the snippets list.\n"
            "See \"What's This\" for more information."))
        self.searchEntry.setWhatsThis(''.join(map("<p>{0}</p>\n".format, (
            _("Enter text to search in the snippets list, and "
              "press Enter to apply the currently selected snippet."),
            _("If the search text fully matches the value of the '{name}' variable "
              "of a snippet, that snippet is selected.").format(name="name"),
            _("If the search text starts with a colon ':', the rest of the "
              "search text filters snippets that define the given variable. "
              "After a space a value can also be entered, snippets will then "
              "match if the value of the given variable contains the text after "
              "the space."),
            _("E.g. entering {menu} will show all snippets that are displayed "
              "in the insert menu.").format(menu="<code>:menu</code>"),
            ))))
    
    def sizeHint(self):
        return self.parent().mainwindow().size() / 4
        
    def readSettings(self):
        data = textformats.formatData('editor')
        self.textView.setFont(data.font)
        self.textView.setPalette(data.palette())

    def showContextMenu(self, pos):
        """Called when the user right-clicks the tree view."""
        self.menuButton.menu().popup(self.treeView.viewport().mapToGlobal(pos))
    
    def slotReturnPressed(self):
        """Called when the user presses Return in the search entry. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
            self.parent().hide() # make configurable?
            view.setFocus()

    def slotEscapePressed(self):
        """Called when the user presses ESC in the search entry. Hides the panel."""
        self.parent().hide()
        self.parent().mainwindow().currentView().setFocus()
    
    def slotDoubleClicked(self, index):
        name = self.treeView.model().name(index)
        view = self.parent().mainwindow().currentView()
        insert.insert(name, view)
        
    def slotAdd(self):
        """Called when the user wants to add a new snippet."""
        edit.Edit(self, None)
        
    def slotEdit(self):
        """Called when the user wants to edit a snippet."""
        name = self.currentSnippet()
        if name:
            edit.Edit(self, name)
        
    def slotShortcut(self):
        """Called when the user selects the Configure Shortcut action."""
        from widgets import shortcuteditdialog
        name = self.currentSnippet()
        if name:
            collection = self.parent().snippetActions
            action = actions.action(name, None, collection)
            default = collection.defaults().get(name)
            mgr = actioncollectionmanager.manager(self.parent().mainwindow())
            cb = mgr.findShortcutConflict
            dlg = shortcuteditdialog.ShortcutEditDialog(self, cb, (collection, name))
            
            if dlg.editAction(action, default):
                mgr.removeShortcuts(action.shortcuts())
                collection.setShortcuts(name, action.shortcuts())
                self.treeView.update()
            
    def slotDelete(self):
        """Called when the user wants to delete the selected rows."""
        rows = sorted(set(i.row() for i in self.treeView.selectedIndexes()), reverse=True)
        if rows:
            for row in rows:
                name = self.treeView.model().names()[row]
                self.parent().snippetActions.setShortcuts(name, [])
                self.treeView.model().removeRow(row)
            self.updateFilter()
    
    def slotApply(self):
        """Called when the user clicks the apply button. Applies current snippet."""
        name = self.currentSnippet()
        if name:
            view = self.parent().mainwindow().currentView()
            insert.insert(name, view)
    
    def slotImport(self):
        """Called when the user activates the import action."""
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        caption = app.caption(_("dialog title", "Import Snippets"))
        filename = None
        filename = QFileDialog.getOpenFileName(self, caption, filename, filetypes)[0]
        if filename:
            from . import import_export
            import_export.load(filename, self)
        
    def slotExport(self):
        """Called when the user activates the export action."""
        allrows = [row for row in range(model.model().rowCount())
                       if not self.treeView.isRowHidden(row, QModelIndex())]
        selectedrows = [i.row() for i in self.treeView.selectedIndexes()
                                if i.column() == 0 and i.row() in allrows]
        names = self.treeView.model().names()
        names = [names[row] for row in selectedrows or allrows]
        
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        n = len(names)
        caption = app.caption(_("dialog title",
            "Export {num} Snippet", "Export {num} Snippets", n).format(num=n))
        filename = QFileDialog.getSaveFileName(self, caption, None, filetypes)[0]
        if filename:
            from . import import_export
            try:
                import_export.save(names, filename)
            except (IOError, OSError) as e:
                QMessageBox.critical(self, _("Error"), _(
                    "Can't write to destination:\n\n{url}\n\n{error}").format(
                    url=filename, error=e.strerror))
        
    def slotRestore(self):
        """Called when the user activates the Restore action."""
        from . import restore
        dlg = restore.RestoreDialog(self)
        dlg.setWindowModality(Qt.WindowModal)
        dlg.populate()
        dlg.show()
        dlg.finished.connect(dlg.deleteLater)
        
    def slotHelp(self):
        """Called when the user clicks the small help button."""
        userguide.show("snippets")
        
    def currentSnippet(self):
        """Returns the name of the current snippet if it is visible."""
        row = self.treeView.currentIndex().row()
        if row != -1 and not self.treeView.isRowHidden(row, QModelIndex()):
            return self.treeView.model().names()[row]

    def updateFilter(self):
        """Called when the text in the entry changes, updates search results."""
        text = self.searchEntry.text()
        ltext = text.lower()
        filterVars = text.startswith(':')
        if filterVars:
            try:
                fvar, fval = text[1:].split(None, 1)
                fhide = lambda v: v.get(fvar) in (True, None) or fval not in v.get(fvar)
            except ValueError:
                fvar = text[1:].strip()
                fhide = lambda v: not v.get(fvar)
        for row in range(self.treeView.model().rowCount()):
            name = self.treeView.model().names()[row]
            nameid = snippets.get(name).variables.get('name', '')
            if filterVars:
                hide = fhide(snippets.get(name).variables)
            elif nameid == text:
                i = self.treeView.model().createIndex(row, 0)
                self.treeView.selectionModel().setCurrentIndex(i, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
                hide = False
            elif nameid.lower().startswith(ltext):
                hide = False
            elif ltext in snippets.title(name).lower():
                hide = False
            else:
                hide = True
            self.treeView.setRowHidden(row, QModelIndex(), hide)
        self.updateText()
            
    def updateText(self):
        """Called when the current snippet changes."""
        name = self.currentSnippet()
        self.textView.clear()
        if name:
            s = snippets.get(name)
            self.highlighter.setPython('python' in s.variables)
            self.textView.setPlainText(s.text)
        
    def updateColumnSizes(self):
        self.treeView.resizeColumnToContents(0)
        self.treeView.resizeColumnToContents(1)
Example #19
0
class AboutWhat(QDialog):
    def __init__(self, parent=None, pytesting=False):
        super(AboutWhat, self).__init__(parent)
        self._pytesting = pytesting
        self.setWindowTitle('About %s' % __appname__)
        self.setWindowIcon(icons.get_icon('master'))
        self.setMinimumSize(800, 700)
        self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint
                            | Qt.WindowCloseButtonHint)

        self.__initUI__()

    def __initUI__(self):
        """Initialize the GUI."""
        self.manager_updates = None

        # ---- AboutTextBox

        self.AboutTextBox = QTextBrowser()
        self.AboutTextBox.installEventFilter(self)
        self.AboutTextBox.setReadOnly(True)
        self.AboutTextBox.setFixedWidth(850)
        self.AboutTextBox.setFrameStyle(0)
        self.AboutTextBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.AboutTextBox.setOpenExternalLinks(True)

        self.AboutTextBox.setStyleSheet('QTextEdit {background-color:"white"}')
        self.AboutTextBox.document().setDocumentMargin(0)
        self.set_html_in_AboutTextBox()

        # ---- toolbar

        self.ok_btn = QPushButton('OK')
        self.ok_btn.clicked.connect(self.close)

        self.btn_check_updates = QPushButton(' Check for Updates ')
        self.btn_check_updates.clicked.connect(
            self._btn_check_updates_isclicked)

        toolbar = QGridLayout()
        toolbar.addWidget(self.btn_check_updates, 0, 1)
        toolbar.addWidget(self.ok_btn, 0, 2)
        toolbar.setContentsMargins(0, 0, 0, 0)
        toolbar.setColumnStretch(0, 100)

        # ---- Main Grid

        grid = QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(self.AboutTextBox, 0, 1)
        grid.addLayout(toolbar, 1, 1)

        grid.setColumnStretch(1, 500)
        grid.setContentsMargins(10, 10, 10, 10)

        self.setLayout(grid)
        self.ok_btn.setFocus()

    @QSlot()
    def _btn_check_updates_isclicked(self):
        """Handles when the button to check for updates is clicked."""
        self.manager_updates = ManagerUpdates(self, self._pytesting)

    def set_html_in_AboutTextBox(self):
        """Set the text in the About GWHAT text browser widget."""

        # ---- Header logo

        width = 750
        filename = os.path.join(__rootdir__, 'ressources',
                                'WHAT_banner_750px.png')

        # http://doc.qt.io/qt-4.8/richtext-html-subset.html

        if platform.system() == 'Windows':
            fontfamily = "Segoe UI"  # "Cambria" #"Calibri" #"Segoe UI""
        elif platform.system() == 'Linux':
            fontfamily = "Ubuntu"

        html = """
               <html>
               <head>
               <style>
               p {font-size: 16px;
                  font-family: "%s";
                  margin-right:50px;
                  margin-left:50px;
                  }
               p1 {font-size: 16px;
                   font-family: "%s";
                   margin-right:50px;
                   margin-left:50px;
                   }
               </style>
               </head>
               <body>
               """ % (fontfamily, fontfamily)

        # ---- Banner

        html += """
                <p align="left">
                  <br><img src="file:///%s" width="%d"><br>
                </p>
                """ % (filename, width)

        # ---- Copyrights

        html += """
                <br>
                <p1 align="right">
                  GWHAT version %s released on %s<br>
                  Copyright 2014-2018
                  <a href="https://github.com/jnsebgosselin/gwhat/graphs/contributors">
                    GWHAT Project Contributors
                  </a>
                  <br>
                  Licensed under the terms of the GNU General Public License Version 3
                  <br>
                  <a href="%s">%s</a>
                  <br>
                  <br>
                  Created by Jean-S&eacute;bastien Gosselin
                  <br>
                  <a href="mailto:[email protected]">
                    [email protected]
                  </a>
                  <br>
                  <br>
                  Developped and maintained by Jean-S&eacute;bastien Gosselin
                  <br>
                  Institut National de la Recherche Scientifique<br>
                  Research Center Eau-Terre-Environnement, Quebec City,
                  QC, Canada<br>
                  <a href="http://www.ete.inrs.ca/">
                    http://www.ete.inrs.ca
                  </a>
                  <br>
                </p1>
                """ % (__version__, __date__, __project_url__, __project_url__)

        # ---- License

        html += """
                <p align="justify">
                  %s is free software: you can redistribute it and/or
                  modify it under the terms
                  of the GNU General Public License as published by the
                  Free Software Foundation, either version 3 of the
                  License, or (at your option) any later version.
                </p>
                <p align="justify">
                  This program is distributed in the hope that it will be
                  useful, but WITHOUT ANY WARRANTY; without even the
                  implied warranty of MERCHANTABILITY or FITNESS FOR A
                  PARTICULAR PURPOSE. See the GNU General Public
                  License for more details.
                </p>
                <p align="justify">
                  You should have received a copy of the GNU General
                  Public License along with this program.  If not, see
                  <a href="http://www.gnu.org/licenses">
                    http://www.gnu.org/licenses
                  </a>.
                </p>
                </body>
                """ % __namever__

        self.AboutTextBox.setHtml(html)

    # =========================================================================

    def eventFilter(self, obj, event):
        # http://stackoverflow.com/questions/13788452/
        # pyqt-how-to-handle-event-without-inheritance

        # https://srinikom.github.io/pyside-docs/PySide/QtCore/QObject.
        # html#PySide.QtCore.PySide.QtCore.QObject.installEventFilter

        if event.type() == QEvent.FontChange:
            return True  # Eat the event to disable zooming
        else:
            return QWidget.eventFilter(self, obj, event)

    def show(self):
        super(AboutWhat, self).show()
        self.setFixedSize(self.size())
Example #20
0
class Page(qMainWindow):
    def __init__(self, vi_obj):
        super().__init__(vi_obj)
        self.splitter = QSplitter(self)
        self.setCentralWidget(self.splitter)
        self.styles = {}
        self.values = None
        self.choicer = None
        self.separate_items = False
        self.close_lock = None
        self.form_edas = []

    def draw_shape(self, colnames, lvalue, styles):
        message_area = QWidget()
        layout = QVBoxLayout(message_area)
        self.form = QFormLayout()
        layout.addLayout(self.form)
        self.textEdit = QTextBrowser(self)
        self.textEdit.setOpenExternalLinks(True)
        sk = QShortcut(QKeySequence("Ctrl+="), self.textEdit)
        sk.activated.connect(self.textEdit.zoomIn)
        sk = QShortcut(QKeySequence("Ctrl+-"), self.textEdit)
        sk.activated.connect(self.textEdit.zoomOut)
        layout.addWidget(self.textEdit)
        self.splitter.addWidget(message_area)
        if colnames is not None:
            self.vislist = vl = VisualList(self, colnames, lvalue, styles)
            self.splitter.addWidget(vl)
        else:
            self.vislist = None
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.setStretchFactor(0, 0)
        self.splitter.setStretchFactor(1, 1)

    def set_choicer(self, choicer, separate_items=False):
        if self.vislist is not None:
            self.vislist.set_choicer(choicer, separate_items)

    def set_context_menu(self, cmenu):
        if self.vislist is not None:
            self.vislist.set_context_menu(cmenu)

    def set_close_lock(self, close_lock):
        self.close_lock = close_lock

    def set_icon(self, icon):
        self.setWindowIcon(QIcon(icon))

    def load(self, data, editable=False):
        if isinstance(data, str):
            unistr = data
        else:
            codec = QTextCodec.codecForHtml(data)
            unistr = codec.toUnicode(data)
        if Qt.mightBeRichText(unistr):
            self.textEdit.setHtml(unistr)
        else:
            self.textEdit.setPlainText(unistr)
        self.textEdit.setReadOnly(not editable)

    def getHTML(self):
        return self.textEdit.document().toHtml()

    def getText(self):
        return self.textEdit.document().toPlainText()

    def scroll_down(self):
        scroll = self.textEdit.verticalScrollBar()
        scroll.setValue(scroll.maximum())

    def update_form(self, form, is_editable):
        layout = self.form
        self.form_edas.clear()
        clearLayout(layout)
        for n, f in form:
            ew = get_widget_from_value(f)
            try:
                ew.setReadOnly(not is_editable)
            except AttributeError:
                pass
            if callable(n):
                call = n
                n = QPushButton(n.name)
                n.clicked.connect(
                    lambda x, y=ew, c=call: c(get_widget_value(y)))
                if hasattr(ew, "returnPressed"):
                    ew.returnPressed.connect(
                        lambda y=ew, c=call: c(get_widget_value(y)))
            if ew is None:
                layout.addRow(n)
            else:
                layout.addRow(n, ew)
                self.form_edas.append(ew)

    def get_form_vals(self):
        return tuple(get_widget_value(e) for e in self.form_edas)

    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape:
            self.parent.close()

    def closeEvent(self, event=None):
        if event is not None:
            if super().closeEvent(event):
                return
        self.vi_obj.currently_alive = False
Example #21
0
class MainForm(QTabWidget):

    sendMessage = pyqtSignal(list, name='sendMessage')
    selectFriend = pyqtSignal(list, name='selectFriend')
    selectAutoGroup = pyqtSignal(list, name='selectAutoGroup')
    imgHeadRequest = pyqtSignal(str, name='imgHeadRequest')
    friendAutoReply = pyqtSignal(int, name='friendAutoReply')  # 朋友自动回复

    chatroom_num = 0  # 群个数
    selectGroupAutoReply = []  # 自动回复的群
    '''
    通讯录信息
    | NickName ,Sex,Province,City,signature,FromUserName|
    '''
    AllFriendsInfo = {}

    def __init__(self):
        super(MainForm, self).__init__()

        self.focusID = 0
        self.setStyle('qrc/black.qss')
        self.setWindowIcon(QIcon('qrc/icon.png'))
        self.init()

    def setStyle(self, _qssPath):
        with open(_qssPath, encoding='UTF-8') as file:
            str = file.read()
            qss = ''.join(str)
            self.setStyleSheet(qss)

    def init(self):
        self.tabChat = QWidget()
        self.tabContact = QWidget()
        self.tabSet = QWidget()

        self.addTab(self.tabChat, '微信')
        self.addTab(self.tabContact, '通讯录')
        self.addTab(self.tabSet, '设置')

        self.tabChatInit()
        self.setInit()
        self.contactInit()
        self.setWindowTitle(self.tr('微信机器人'))

    def addChatFriend(self, _NickName, _RemarkName):

        item = QListWidgetItem()
        str = _NickName
        if _RemarkName is not '':
            str += '[' + _RemarkName + ']'

        item.setText(str)

        self.listChatting.addItem(item)

    # 通讯录写入名单
    def fillContact(self, _fullContact):

        # self.AllFriendsInfo = _fullContact
        for each in _fullContact:
            item = QListWidgetItem()
            str = each['RemarkName']
            if str is '':
                str = each['NickName']
            item.setText(str)
            self.contactList.addItem(item)
            # | NickName, Sex, Province, City, signature, FromUserName |
            self.AllFriendsInfo[str] = [
                each['NickName'], each['Sex'], each['Province'], each['City'],
                each['Signature'], each['UserName']
            ]

    # 群自动回复----获得群名
    def setChatroomFill(self, _chatroom):

        self.chatroom_num = 0
        for each in _chatroom:
            self.chatroom_num += 1
            #self.chatroomInfo[each['NickName']] = each['UserName']
            item = QListWidgetItem()
            str = each['NickName']
            item.setText(str)
            self.allGroupList.addItem(item)
        #print(self.chatroomInfo)

    def contactInit(self):

        size = self.size()

        self.contactList = QListWidget()
        self.contactList.setFixedSize(size.width() / 3, size.height())
        self.contactList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)
        self.contactList.itemClicked.connect(self.contactListClick)

        infoWidget = QWidget()
        infoWidget.setFixedSize(size.width() * 2 / 3, size.height())

        topLayout = QGridLayout()
        midLayout = QVBoxLayout()
        bottomLayout = QHBoxLayout()

        # top
        self.headLabel = QLabel()  # 头像
        self.headLabel.setFixedSize(150, 150)
        self.headLabel.setScaledContents(True)

        self.signatureLabel = QLabel()  # 签名
        self.signatureLabel.setAlignment(QtCore.Qt.AlignVCenter)
        self.nickNameLabel = QLabel()  # 微信名
        self.nickNameLabel.setAlignment(QtCore.Qt.AlignVCenter)

        topLayout.addWidget(self.nickNameLabel, 1, 0, 1, 3)
        topLayout.addWidget(self.signatureLabel, 2, 0, 1, 3)
        topLayout.addWidget(self.headLabel, 0, 1, 1, 1)

        # mid
        self.remarkNameLabel = QLabel()  # 备注
        self.cityLabel = QLabel()  # 城市

        midLayout.addWidget(self.remarkNameLabel)
        midLayout.addWidget(self.cityLabel)

        # bottom
        self.sendMsgBtn = QPushButton('发消息')

        bottomLayout.addWidget(self.sendMsgBtn)

        layout = QGridLayout()

        infoLayout = QVBoxLayout()
        infoLayout.addLayout(topLayout)
        infoLayout.addLayout(midLayout)
        infoLayout.addLayout(bottomLayout)
        infoLayout.addSpacing(10)

        infoWidget.setLayout(infoLayout)
        layout.addWidget(self.contactList, 0, 0, 1, 1)
        layout.addWidget(infoWidget, 0, 1, 1, 2)

        self.tabContact.setLayout(layout)

    def setInit(self):

        setTab = QTabWidget(self.tabSet)
        setTab.setTabPosition(QTabWidget.West)  # 方向

        size = self.size()

        #############################自动回复################################
        btnAutoSet = QPushButton('应用')
        btnAutoCancel = QPushButton('取消')
        btnAutoCancel.clicked.connect(self.clearSelectList)
        btnAutoSet.clicked.connect(self.setSelectList)

        btnLayout = QHBoxLayout()
        btnLayout.addWidget(btnAutoSet)
        btnLayout.addSpacing(5)
        btnLayout.addWidget(btnAutoCancel)

        self.allGroupList = QListWidget()
        self.selectGroupList = QListWidget()  # 选定自动回复的

        self.allGroupList.setFixedSize(size.width() * 3 / 7,
                                       size.height() * 2 / 3)
        self.selectGroupList.setFixedSize(size.width() * 3 / 7,
                                          size.height() * 2 / 3)

        self.allGroupList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)
        self.selectGroupList.setVerticalScrollBarPolicy(
            QtCore.Qt.ScrollBarAlwaysOn)

        self.allGroupList.itemDoubleClicked.connect(self.aGroupDoubleClick)
        self.selectGroupList.itemDoubleClicked.connect(self.sGroupDoubleClick)

        self.setAutoLayout = QGridLayout()
        self.autoReplyFriend = QCheckBox('自动回复')
        self.autoReplyFriend.stateChanged.connect(self.setFriendAutoReply)

        self.setAutoLayout.setSpacing(10)

        self.setAutoLayout.addWidget(self.autoReplyFriend, 0, 0, 1, 1)
        self.setAutoLayout.addWidget(self.allGroupList, 1, 0, 10, 1)
        self.setAutoLayout.addWidget(self.selectGroupList, 1, 1, 10, 1)
        self.setAutoLayout.addLayout(btnLayout, 12, 1, 1, 1)

        # for each in self.ChatroomCheckBoxList:
        #     self.setAutoLayout.addWidget(each)
        tabAuto = QWidget()
        tabAuto.setLayout(self.setAutoLayout)
        #####################################################################
        #####################################################################
        setTab.addTab(tabAuto, '自动回复')
        # setTab.addTab('其他')

    def tabChatInit(self):

        size = self.size()

        layout = QGridLayout()
        self.listChatting = QListWidget()
        self.listChatting.setFixedSize(size.width() / 3, size.height())

        self.chatLog = QTextBrowser()
        self.chatLog.document().setMaximumBlockCount(1000)  # 限制1000行
        self.chatLog.setFixedSize(size.width() * 2 / 3, size.height() * 2 / 3)

        self.textInput = QTextEdit()
        self.textInput.setFixedSize(size.width() * 2 / 3, size.height() / 4)

        self.btnSend = QPushButton()
        self.btnSend.setText('发送')

        # 显示正在聊天的朋友
        self.chattingFri = QLabel('当前聊天朋友:_____')

        self.btnSend.clicked.connect(self.sendMsg)
        self.listChatting.itemClicked.connect(self.listClick)

        self.chatLog.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
        self.chatLog.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        layout.addWidget(self.listChatting, 0, 0, 6, 1)
        layout.addWidget(self.chatLog, 0, 1, 3, 3)
        layout.addWidget(self.textInput, 3, 1, 2, 3)
        layout.addWidget(self.chattingFri, 5, 1, 1, 1)
        layout.addWidget(self.btnSend, 5, 3, 1, 1)

        self.tabChat.setLayout(layout)

    def showChatLog(self, _Msg):

        # count = -1
        #         # for count, line in enumerate(open(thefilepath, 'rU')):
        #         #     pass
        #         # count += 1
        msg_time = time.strftime("%Y-%m-%d %H:%M:%S",
                                 time.localtime(_Msg['time']))
        content = _Msg['content']

        if _Msg['fromusr'] == _Msg['selfusr']:
            self.chatLog.append(msg_time + '\n' + '我' + ':' + content + '\n')
        else:
            fromFriend = _Msg['remarkname']
            self.chatLog.append(msg_time + '\n' + fromFriend + ':' + content +
                                '\n')

    def showSendChatLog(self, _Msg):
        msg_time = time.strftime("%Y-%m-%d %H:%M:%S",
                                 time.localtime(int(time.time())))
        content = _Msg[0]
        self.chatLog.append(msg_time + '\n' + '我' + ':' + content + '\n')

    @pyqtSlot()
    def sendMsg(self):

        sMsg = self.textInput.toPlainText()
        if sMsg != '':
            self.textInput.clear()
            self.sendMessage.emit([sMsg])

    @pyqtSlot(QListWidgetItem)
    def listClick(self, item):
        self.selectFriend.emit([item.text()])

    @pyqtSlot(QListWidgetItem)
    def contactListClick(self, item):
        global curTmpImg
        # | NickName, Sex, Province, City, signature, FromUserName |
        cur = self.AllFriendsInfo[item.text()]
        self.imgHeadRequest.emit(cur[5])

        if curTmpImg:
            png = QtGui.QPixmap()
            png.loadFromData(curTmpImg)
            #png.scaled((50,50))
            self.headLabel.setPixmap(png)
            curTmpImg = None

        self.signatureLabel.setText('签名      ' + ''.join(cur[4]))  # 签名
        str = ''.join(cur[0])
        if cur[1] == 1:
            str += ' ♂'
        else:
            str += '  ♀'
        self.nickNameLabel.setText('微信      ' + str)  # 微信名
        self.remarkNameLabel.setText('备注        ' + item.text())  # 备注
        self.cityLabel.setText('地区      ' +
                               ''.join(cur[2] + ' ' + cur[3]))  # 城市

    # add to select list
    @pyqtSlot(QListWidgetItem)
    def aGroupDoubleClick(self, item):
        select = item.text()
        item = QListWidgetItem()
        item.setText(select)
        self.selectGroupList.addItem(item)
        self.selectGroupAutoReply.append(select)

    # remove select item from list
    @pyqtSlot(QListWidgetItem)
    def sGroupDoubleClick(self, item):

        select = item.text()
        self.selectGroupList.removeItemWidget(
            self.selectGroupList.takeItem(self.selectGroupList.row(item)))
        self.selectGroupAutoReply.remove(select)

    @pyqtSlot(int)
    def setFriendAutoReply(self, _state):
        self.friendAutoReply.emit(_state)

    # 清空选定
    def clearSelectList(self):
        self.selectGroupList.clear()
        self.selectGroupAutoReply.clear()

    # 应用群自动回复
    def setSelectList(self):
        self.selectAutoGroup.emit(self.selectGroupAutoReply)

    # 获取头像
    def postUserHead(self, _img):
        global curTmpImg
        curTmpImg = _img
        #print(_img)

    # 更改当前聊天朋友名字显示
    def changeChattingFri(self, _str):
        self.chattingFri.setText('当前发送:' + _str[0])

    # 弹窗提醒
    def msgWarning(self, _message, _type):

        if _type == 0:
            QMessageBox.information(self, "红包提醒", _message, QMessageBox.Yes)
        else:
            QMessageBox.information(self, "撤回提醒", _message, QMessageBox.Yes)
Example #22
0
class MainInterface(QMainWindow):
    def __init__(self, screen_scale_rate, user):

        super(MainInterface, self).__init__()

        self.rate = screen_scale_rate  # 屏幕缩放比例
        self.lock_sign = 0
        self.user = user
        self.get_settin()
        self.init_ui()

        self._padding = 5  # 设置边界宽度为5
        # 设置鼠标跟踪判断扳机默认值
        # reference: https://blog.csdn.net/qq_38528972/article/details/78573591
        self._move_drag = False
        self._corner_drag = False
        self._bottom_drag = False
        self._right_drag = False

        self._right_rect = []
        self._bottom_rect = []
        self._corner_rect = []
        self.image = None
        self.load_local_img = False
        self.load_local_img_path = folder_path
        self.save_result_path = [folder_path]  # 写成list, 传入方式为引用, 便于修改

    def init_ui(self):

        # 窗口尺寸
        self.resize(800 * self.rate, 120 * self.rate)
        self.setMouseTracking(True)  # 设置widget鼠标跟踪

        # 窗口无标题栏、窗口置顶、窗口透明
        self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
        # Mac系统下隐藏标题栏同时窗口透明会造成窗口不能伸缩
        self.setAttribute(Qt.WA_TranslucentBackground)
        # self.setWindowOpacity(0.7)

        # 窗口图标
        self.icon = QIcon()
        self.icon.addPixmap(QPixmap(folder_path + "/config/logo.ico"),
                            QIcon.Normal, QIcon.On)
        self.setWindowIcon(self.icon)

        # 系统托盘
        self.tray = QSystemTrayIcon(self)
        self.tray.setIcon(self.icon)
        self.tray.activated.connect(self.show)
        self.tray.show()

        # 鼠标样式
        # self.pixmap = QPixmap(folder_path + '/config/光标.png')
        # self.cursor = QCursor(self.pixmap, 0, 0)
        # self.setCursor(self.cursor)

        # 工具栏标签
        self.titleLabel = QLabel(self)
        self.titleLabel.setGeometry(0, 0, 800 * self.rate, 30 * self.rate)
        self.titleLabel.setStyleSheet(
            "background-color:rgba(62, 62, 62, 0.01)")

        self.Font = QFont()
        self.Font.setFamily("华康方圆体W7")
        self.Font.setPointSize(15)

        # 翻译框
        self.translateText = QTextBrowser(self)
        self.translateText.setGeometry(0, 30 * self.rate, 1500 * self.rate,
                                       90 * self.rate)
        self.translateText.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.translateText.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.translateText.setStyleSheet("border-width:0;\
                                          border-style:outset;\
                                          border-top:0px solid #e8f3f9;\
                                          color:white;\
                                          font-weight: bold;\
                                          background-color:rgba(62, 62, 62, %s)"
                                         % (self.horizontal))
        self.translateText.setFont(self.Font)

        # 翻译框加入描边文字
        self.format = QTextCharFormat()
        self.format.setTextOutline(
            QPen(QColor('#1E90FF'), 0.7, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.translateText.mergeCurrentCharFormat(self.format)
        self.translateText.append("欢迎~ 么么哒~")
        self.format.setTextOutline(
            QPen(QColor('#FF69B4'), 0.7, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.translateText.mergeCurrentCharFormat(self.format)
        self.translateText.append("点击设置修改待识别语言类型")
        self.format.setTextOutline(
            QPen(QColor('#674ea7'), 0.7, Qt.SolidLine, Qt.RoundCap,
                 Qt.RoundJoin))
        self.translateText.mergeCurrentCharFormat(self.format)
        self.translateText.append("点击截屏按钮选择识图区域")

        # 翻译框根据内容自适应大小
        self.document = self.translateText.document()
        self.document.contentsChanged.connect(self.textAreaChanged)

        # 此Label用于当鼠标进入界面时给出颜色反应
        self.dragLabel = QLabel(self)
        self.dragLabel.setObjectName("dragLabel")
        self.dragLabel.setGeometry(0, 0, 4000 * self.rate, 2000 * self.rate)

        # 截屏范围按钮
        self.RangeButton = QPushButton(qticon('fa.crop', color='white'), "",
                                       self)
        self.RangeButton.setIconSize(QSize(20, 20))
        self.RangeButton.setGeometry(
            QRect(193 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.RangeButton.setToolTip(
            '<b>截屏识别图片 ScreenShot Range</b><br>框选要识别的区域')
        self.RangeButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.RangeButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.RangeButton.hide()

        # 运行按钮
        self.StartButton = QPushButton(qticon('fa.play', color='white'), "",
                                       self)
        self.StartButton.setIconSize(QSize(20, 20))
        self.StartButton.setGeometry(
            QRect(233 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.StartButton.setToolTip(
            '<b>识别 Recognize</b><br>点击开始(手动)<br>开始/停止(自动)')
        self.StartButton.setStyleSheet("background: transparent")
        self.StartButton.clicked.connect(self.start_login)
        self.StartButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.StartButton.hide()

        # 手动打开文件按钮
        self.OpenButton = QPushButton(
            qticon('fa.folder-open-o', color='white'), "", self)
        self.OpenButton.setIconSize(QSize(20, 20))
        self.OpenButton.setGeometry(
            QRect(273 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.OpenButton.setToolTip('<b>打开识别图片 Open image</b><br> 识别本地图片')
        self.OpenButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.OpenButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.OpenButton.clicked.connect(self.open_image)
        self.OpenButton.hide()

        # 复制按钮
        self.CopyButton = QPushButton(qticon('fa.copy', color='white'), "",
                                      self)
        self.CopyButton.setIconSize(QSize(20, 20))
        self.CopyButton.setGeometry(
            QRect(313 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.CopyButton.setToolTip('<b>复制 Copy</b><br>将当前识别到的文本<br>复制至剪贴板')
        self.CopyButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.CopyButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.CopyButton.clicked.connect(lambda: copy(self.original))
        self.CopyButton.hide()

        # 朗读原文按钮
        self.playVoiceButton = QPushButton(qticon('fa.music', color='white'),
                                           "", self)
        self.playVoiceButton.setIconSize(QSize(20, 20))
        self.playVoiceButton.setGeometry(
            QRect(353 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.playVoiceButton.setToolTip('<b>朗读原文 Play Voice</b><br>朗读识别到的原文')
        self.playVoiceButton.setStyleSheet("background: transparent")
        self.playVoiceButton.clicked.connect(self.play_voice)
        self.playVoiceButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.playVoiceButton.hide()

        # 翻译模式按钮
        self.switchBtn = SwitchBtn(self)
        self.switchBtn.setGeometry(393 * self.rate, 5 * self.rate,
                                   50 * self.rate, 20 * self.rate)
        self.switchBtn.setToolTip('<b>模式 Mode</b><br>手动识别/自动识别')
        self.switchBtn.checkedChanged.connect(self.getState)
        self.switchBtn.setCursor(QCursor(Qt.PointingHandCursor))
        self.switchBtn.hide()

        # 识别原文字类型提示框
        languageFont = QFont()
        languageFont.setFamily("华康方圆体W7")
        languageFont.setPointSize(10)
        self.languageText = QPushButton(self)
        self.languageText.setIconSize(QSize(20, 20))
        self.languageText.setGeometry(
            QRect(463 * self.rate, 5 * self.rate, 45 * self.rate,
                  20 * self.rate))
        self.languageText.setToolTip(
            '<b>待识别的原文类型 </b><br>Original Language Type')
        self.languageText.setStyleSheet("border-width:0;\
                                                  border-style:outset;\
                                                  border-top:0px solid #e8f3f9;\
                                                  color:white;\
                                                  background-color:rgba(143, 143, 143, 0)"
                                        )
        self.languageText.setCursor(QCursor(Qt.PointingHandCursor))
        self.languageText.setText(
            config.letter_chinese_dict[self.data["language"]])
        self.languageText.setFont(languageFont)
        self.languageText.hide()

        # 设置按钮
        self.SettinButton = QPushButton(qticon('fa.cog', color='white'), "",
                                        self)
        self.SettinButton.setIconSize(QSize(20, 20))
        self.SettinButton.setGeometry(
            QRect(518 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.SettinButton.setToolTip('<b>设置 Settin</b>')
        self.SettinButton.setStyleSheet(
            "background-color:rgba(62, 62, 62, 0);")
        self.SettinButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.SettinButton.hide()

        # 锁按钮
        self.LockButton = QPushButton(qticon('fa.lock', color='white'), "",
                                      self)
        self.LockButton.setIconSize(QSize(20, 20))
        self.LockButton.setGeometry(
            QRect(562 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.LockButton.setToolTip('<b>锁定翻译界面 Lock</b>')
        self.LockButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.LockButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.LockButton.clicked.connect(self.lock)
        self.LockButton.hide()

        # 最小化按钮
        self.MinimizeButton = QPushButton(qticon('fa.minus', color='white'),
                                          "", self)
        self.MinimizeButton.setIconSize(QSize(20, 20))
        self.MinimizeButton.setGeometry(
            QRect(602 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.MinimizeButton.setToolTip('<b>最小化 Minimize</b>')
        self.MinimizeButton.setStyleSheet(
            "background-color:rgba(62, 62, 62, 0);")
        self.MinimizeButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.MinimizeButton.clicked.connect(self.showMinimized)
        self.MinimizeButton.hide()

        # 退出按钮
        self.QuitButton = QPushButton(qticon('fa.times', color='white'), "",
                                      self)
        self.QuitButton.setIconSize(QSize(20, 20))
        self.QuitButton.setGeometry(
            QRect(642 * self.rate, 5 * self.rate, 20 * self.rate,
                  20 * self.rate))
        self.QuitButton.setToolTip('<b>退出程序 Quit</b>')
        self.QuitButton.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.QuitButton.setCursor(QCursor(Qt.PointingHandCursor))
        self.QuitButton.hide()

        # 右下角用于拉伸界面的控件 mac系统应该注释掉
        self.statusbar = QStatusBar(self)
        self.statusbar.setStyleSheet("background-color:rgba(62, 62, 62, 0);")
        self.setStatusBar(self.statusbar)

    # 锁定界面
    def lock(self):

        try:
            if self.lock_sign == 0:
                self.LockButton.setIcon(qticon('fa.unlock', color='white'))
                self.dragLabel.hide()
                self.lock_sign = 1

                if self.horizontal == 0.01:
                    self.horizontal = 0
            else:
                self.LockButton.setIcon(qticon('fa.lock', color='white'))
                self.LockButton.setStyleSheet(
                    "background-color:rgba(62, 62, 62, 0);")
                self.dragLabel.show()
                self.lock_sign = 0

                if self.horizontal == 0:
                    self.horizontal = 0.01

            self.translateText.setStyleSheet("border-width:0;\
                                              border-style:outset;\
                                              border-top:0px solid #e8f3f9;\
                                              color:white;\
                                              font-weight: bold;\
                                              background-color:rgba(62, 62, 62, %s)"
                                             % (self.horizontal))
        except Exception:
            write_error(format_exc())

    # 当翻译内容改变时界面自适应窗口大小
    def textAreaChanged(self):

        newHeight = self.document.size().height()
        width = self.width()
        self.resize(width, newHeight + 30 * self.rate)
        self.translateText.setGeometry(0, 30 * self.rate, width, newHeight)

    # 判断翻译模式键状态
    def getState(self, checked):

        if checked:
            self.mode = True
        else:
            self.mode = False

            with open(folder_path + '/config/settin.json') as file:
                data = load(file)
                data["sign"] = 1
            with open(folder_path + '/config/settin.json', 'w') as file:
                dump(data, file, indent=2)
            self.StartButton.setIcon(qticon('fa.play', color='white'))

    # 鼠标移动事件
    def mouseMoveEvent(self, e: QMouseEvent):

        if self.lock_sign == 1:
            return

        try:
            self._endPos = e.pos() - self._startPos
            self.move(self.pos() + self._endPos)
        except Exception:
            write_error(format_exc())

    # 鼠标移动事件 mac
    # def mouseMoveEvent(self, QMouseEvent):
    #     if self.lock_sign == 1:
    #         return
    #
    #     # 判断鼠标位置切换鼠标手势
    #     if QMouseEvent.pos() in self._corner_rect:
    #         self.setCursor(Qt.SizeFDiagCursor)
    #     elif QMouseEvent.pos() in self._bottom_rect:
    #         self.setCursor(Qt.SizeVerCursor)
    #     elif QMouseEvent.pos() in self._right_rect:
    #         self.setCursor(Qt.SizeHorCursor)
    #     else:
    #         self.setCursor(self.cursor)
    #         # self.setCursor(Qt.ArrowCursor)
    #
    #     # 当鼠标左键点击不放及满足点击区域的要求后,分别实现不同的窗口调整
    #     if Qt.LeftButton and self._right_drag:
    #         # 右侧调整窗口宽度
    #         self.resize(QMouseEvent.pos().x(), self.height())
    #         QMouseEvent.accept()
    #     elif Qt.LeftButton and self._bottom_drag:
    #         # 下侧调整窗口高度
    #         self.resize(self.width(), QMouseEvent.pos().y())
    #         QMouseEvent.accept()
    #     elif Qt.LeftButton and self._corner_drag:
    #         # 右下角同时调整高度和宽度
    #         self.resize(QMouseEvent.pos().x(), QMouseEvent.pos().y())
    #         QMouseEvent.accept()
    #     elif Qt.LeftButton and self._move_drag:
    #         # 标题栏拖放窗口位置
    #         self.move(QMouseEvent.globalPos() - self.move_drag_position)
    #         QMouseEvent.accept()

    # # 鼠标按下事件
    def mousePressEvent(self, e: QMouseEvent):

        if self.lock_sign == 1:
            return

        try:
            if e.button() == Qt.LeftButton:
                self._isTracking = True
                self._startPos = QPoint(e.x(), e.y())
        except Exception:
            write_error(format_exc())

    # 鼠标按下事件 mac
    # def mousePressEvent(self, event):
    #
    #     if self.lock_sign == 1:
    #         return
    #
    #     try:
    #         # 重写鼠标点击的事件
    #         if (event.button() == Qt.LeftButton) and (event.pos() in self._corner_rect):
    #             # 鼠标左键点击右下角边界区域
    #             self._corner_drag = True
    #             event.accept()
    #         elif (event.button() == Qt.LeftButton) and (event.pos() in self._right_rect):
    #             # 鼠标左键点击右侧边界区域
    #             self._right_drag = True
    #             event.accept()
    #         elif (event.button() == Qt.LeftButton) and (event.pos() in self._bottom_rect):
    #             # 鼠标左键点击下侧边界区域
    #             self._bottom_drag = True
    #             event.accept()
    #         elif (event.button() == Qt.LeftButton) and (event.y() < self.height()):
    #             # 鼠标左键点击区域
    #             self._move_drag = True
    #             self.move_drag_position = event.globalPos() - self.pos()
    #             event.accept()
    #     except Exception:
    #         write_error(format_exc())

    # # 鼠标松开事件
    def mouseReleaseEvent(self, e: QMouseEvent):

        if self.lock_sign == 1:
            return

        try:
            if e.button() == Qt.LeftButton:
                self._isTracking = False
                self._startPos = None
                self._endPos = None
        except Exception:
            write_error(format_exc())

    # 鼠标松开事件 mac
    # def mouseReleaseEvent(self, e: QMouseEvent):
    #     if self.lock_sign == 1:
    #         return
    #
    #     try:
    #         if e.button() == Qt.LeftButton:
    #             self._isTracking = False
    #             self._startPos = None
    #             self._endPos = None
    #
    #             # 鼠标释放后,各扳机复位
    #             self._move_drag = False
    #             self._corner_drag = False
    #             self._bottom_drag = False
    #             self._right_drag = False
    #     except Exception:
    #         write_error(format_exc())

    # 鼠标进入控件事件
    def enterEvent(self, QEvent):

        if self.lock_sign == 1:
            self.LockButton.show()
            self.LockButton.setStyleSheet(
                "background-color:rgba(62, 62, 62, 0.7);")
            return

        try:
            # 显示所有顶部工具栏控件
            self.switchBtn.show()
            self.StartButton.show()
            self.SettinButton.show()
            self.RangeButton.show()
            self.OpenButton.show()
            self.CopyButton.show()
            self.QuitButton.show()
            self.MinimizeButton.show()
            self.playVoiceButton.show()
            self.LockButton.show()
            self.languageText.show()

            self.setStyleSheet(
                'QLabel#dragLabel {background-color:rgba(62, 62, 62, 0.3)}')

        except Exception:
            write_error(format_exc())

    def resizeEvent(self, QResizeEvent):
        # 重新调整边界范围以备实现鼠标拖放缩放窗口大小,采用三个列表生成式生成三个列表
        self._right_rect = [
            QPoint(x, y) for x in range(self.width() - self._padding,
                                        self.width() + 1)
            for y in range(1,
                           self.height() - self._padding)
        ]
        self._bottom_rect = [
            QPoint(x, y) for x in range(1,
                                        self.width() - self._padding)
            for y in range(self.height() - self._padding,
                           self.height() + 1)
        ]
        self._corner_rect = [
            QPoint(x, y) for x in range(self.width() - self._padding,
                                        self.width() + 1)
            for y in range(self.height() - self._padding,
                           self.height() + 1)
        ]

    # 鼠标离开控件事件
    def leaveEvent(self, QEvent):

        try:
            # 重置所有控件的位置和大小
            width = (self.width() * 213) / 800
            height = self.height() - 30

            self.RangeButton.setGeometry(
                QRect(width - 20 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.StartButton.setGeometry(
                QRect(width + 20 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.OpenButton.setGeometry(
                QRect(width + 60 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.CopyButton.setGeometry(
                QRect(width + 100 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.playVoiceButton.setGeometry(
                QRect(width + 140 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.switchBtn.setGeometry(
                QRect(width + 180 * self.rate, 5 * self.rate, 50 * self.rate,
                      20 * self.rate))
            self.languageText.setGeometry(width + 250 * self.rate,
                                          5 * self.rate, 45 * self.rate,
                                          20 * self.rate)
            self.SettinButton.setGeometry(
                QRect(width + 305 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.LockButton.setGeometry(
                QRect(width + 345 * self.rate, 5 * self.rate, 24 * self.rate,
                      20 * self.rate))
            self.MinimizeButton.setGeometry(
                QRect(width + 389 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.QuitButton.setGeometry(
                QRect(width + 429 * self.rate, 5 * self.rate, 20 * self.rate,
                      20 * self.rate))
            self.translateText.setGeometry(0, 30 * self.rate, self.width(),
                                           height * self.rate)

            # 隐藏所有顶部工具栏控件
            self.switchBtn.hide()
            self.StartButton.hide()
            self.SettinButton.hide()
            self.RangeButton.hide()
            self.OpenButton.hide()
            self.CopyButton.hide()
            self.QuitButton.hide()
            self.MinimizeButton.hide()
            self.playVoiceButton.hide()
            self.LockButton.hide()
            self.languageText.hide()

            self.setStyleSheet('QLabel#dragLabel {background-color:none}')

        except Exception:
            write_error(format_exc())

    # 获取界面预设参数
    def get_settin(self):

        with open(folder_path + '/config/settin.json') as file:
            self.data = load(file)

        # 翻译模式预设
        self.mode = False
        # 原文预设值
        self.original = ''

        # 透明度预设
        self.horizontal = (self.data["horizontal"]) / 100
        if self.horizontal == 0:
            self.horizontal = 0.01

        # 各翻译源线程状态标志
        self.thread_state = 0

    def start_login(self):

        with open(folder_path + '/config/settin.json') as file:
            data = load(file)

        if data["sign"] % 2 == 0:
            data["sign"] += 1
            with open(folder_path + '/config/settin.json', 'w') as file:
                dump(data, file, indent=2)

            self.StartButton.setIcon(qticon('fa.play', color='white'))
        else:
            thread = TranslateThread(self, self.mode)
            thread.use_translate_signal.connect(self.use_translate)
            thread.start()
            thread.exec()

    # 创造翻译线程
    def creat_thread(self, fun, original, data, translate_type):

        self.thread_state += 1  # 线程开始,增加线程数
        translation_source = UseTranslateThread(fun, original, data,
                                                translate_type)
        thread = Thread(target=translation_source.run)
        thread.setDaemon(True)
        translation_source.use_translate_signal.connect(self.display_text)
        thread.start()

    # 并发执行所有翻译源
    def use_translate(self, signal_list, original, data, result_with_location,
                      translate_result):

        # 翻译界面清屏
        self.translateText.clear()
        # 设定翻译时的字体类型和大小
        self.Font.setFamily(data["fontType"])
        self.Font.setPointSize(data["fontSize"])
        self.translateText.setFont(self.Font)

        if "original" in signal_list or "error" in signal_list:
            if data["vis_result"] == "True":
                self.vis_res = VisResult(np_img=self.image,
                                         result=result_with_location,
                                         configs=data,
                                         translate_result=translate_result,
                                         save_path=self.save_result_path)
                self.vis_res.result_signal.connect(self.display_modify_text)
                self.vis_res.show()
            if translate_result == '':
                self.creat_thread(None, original, data, "original")
            else:
                if data['showOriginal'] == "True":
                    self.creat_thread(None, original, data, "original")
            self.creat_thread(None, translate_result, data, "translated")

    # 将翻译结果打印
    def display_text(self, result, data, translate_type):

        try:
            if data["showColorType"] == "False":
                self.format.setTextOutline(
                    QPen(QColor(data["fontColor"][translate_type]), 0.7,
                         Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
                self.translateText.mergeCurrentCharFormat(self.format)
                self.translateText.append(result)
            else:
                self.translateText.append(
                    "<font color=%s>%s</font>" %
                    (data["fontColor"][translate_type], result))

            # 保存译文
            self.save_text(result, translate_type)
            self.thread_state -= 1  # 线程结束,减少线程数
        except Exception:
            write_error(format_exc())

    # 将修改后的结果打印
    def display_modify_text(self, result, data, translate_type,
                            translate_result):
        self.translateText.clear()

        if data["showColorType"] == "False":
            if translate_result == '':
                self.format.setTextOutline(
                    QPen(QColor(data["fontColor"][translate_type]), 0.7,
                         Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
                self.translateText.mergeCurrentCharFormat(self.format)
                self.translateText.append(result)
            else:
                if data["showOriginal"] == "True":
                    self.format.setTextOutline(
                        QPen(QColor(data["fontColor"][translate_type]), 0.7,
                             Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
                    self.translateText.mergeCurrentCharFormat(self.format)
                    self.translateText.append(result)

            self.format.setTextOutline(
                QPen(QColor(data["fontColor"]['translated']), 0.7,
                     Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
            self.translateText.mergeCurrentCharFormat(self.format)
            self.translateText.append(translate_result)
        else:
            if translate_result == '':
                self.translateText.append(
                    "<font color=%s>%s</font>" %
                    (data["fontColor"][translate_type], result))
            else:
                if data["showOriginal"] == "True":
                    self.translateText.append(
                        "<font color=%s>%s</font>" %
                        (data["fontColor"][translate_type], result))
            self.translateText.append(
                "<font color=%s>%s</font>" %
                (data["fontColor"]['translated'], translate_result))
        self.original = result

    # 语音朗读
    def play_voice(self):
        if not self.original:
            return
        try:
            # thread = Thread(target=Voice, args=(self.original,))
            # thread.setDaemon(True)
            # thread.start()

            self.player = None
            flag, voice_file = Voice(self.original).save_voice()
            if flag:
                file = QUrl.fromLocalFile(voice_file)
                content = QMediaContent(file)
                self.player = QMediaPlayer()
                self.player.setMedia(content)
                self.player.play()

        except Exception:
            write_error(format_exc())

    def save_text(self, text, translate_type):

        if translate_type == "caiyunPrivate":
            content = "\n[私人翻译]\n%s" % text
        else:
            content = ""

        with open(folder_path + "/config/识别结果.txt", "a+",
                  encoding="utf-8") as file:
            file.write(content)

    def open_image(self):
        file_choose, file_type = QFileDialog.getOpenFileName(
            self, "选取文件", self.load_local_img_path,
            "jpg (*.jpg);; png (*.png);; jpeg (*.jpeg);;All Files (*)")
        img = imread(file_choose)
        if img is not None:
            self.load_local_img_path = dirname(file_choose)
            self.image = img
            self.load_local_img = True
            self.start_login()
Example #23
0
class ErrorReport(QDialog):
    """
    Display error messages from the download in a dialog.

    Search/find feature is live, like Firefox. However it's pretty slow
    with a large amount of data, so don't initiate a new search each
    and every time data is appended to the log window. Instead, if a search
    is active, wait for one second after text has been appended before
    doing the search.
    """

    dialogShown = pyqtSignal()
    dialogActivated = pyqtSignal()

    def __init__(self, rapidApp, parent=None) -> None:
        super().__init__(parent=parent)

        self.uris = []
        self.get_href = re.compile('<a href="?\'?([^"\'>]*)')

        self.setModal(False)
        self.setSizeGripEnabled(True)

        self.search_pending = False
        self.add_queue = deque()

        self.rapidApp = rapidApp

        layout = QVBoxLayout()
        self.setWindowTitle(_('Error Reports - Rapid Photo Downloader'))

        self.log = QTextBrowser()
        self.log.setReadOnly(True)

        sheet = """
        h1 {
            font-size: large;
            font-weight: bold;
        }
        """

        document = self.log.document()  # type: QTextDocument
        document.setDefaultStyleSheet(sheet)
        # document.setIndentWidth(QFontMetrics(QFont()).boundingRect('200').width())

        self.highlightColor = QColor('#cb1dfa')
        self.textHighlightColor = QColor(Qt.white)

        self.noFindPalette = QPalette()
        self.noFindPalette.setColor(QPalette.WindowText,
                                    QPalette().color(QPalette.Mid))
        self.foundPalette = QPalette()
        self.foundPalette.setColor(QPalette.WindowText,
                                   QPalette().color(QPalette.WindowText))

        self.find_cursors = []
        self.current_find_index = -1

        self.log.anchorClicked.connect(self.anchorClicked)
        self.log.setOpenLinks(False)

        self.defaultFont = QFont()
        self.defaultFont.setPointSize(QFont().pointSize() - 1)
        self.log.setFont(self.defaultFont)
        self.log.textChanged.connect(self.textChanged)

        message = _('Find in reports')
        self.find = QFindLineEdit(find_text=message)
        self.find.textEdited.connect(self.onFindChanged)
        style = self.find.style()  # type: QStyle
        frame_width = style.pixelMetric(QStyle.PM_DefaultFrameWidth)
        button_margin = style.pixelMetric(QStyle.PM_ButtonMargin)
        spacing = (frame_width + button_margin) * 2 + 8

        self.find.setMinimumWidth(
            QFontMetrics(QFont()).boundingRect(message).width() + spacing)

        font_height = QFontMetrics(self.font()).height()
        size = QSize(font_height, font_height)

        self.up = QPushButton()
        self.up.setIcon(QIcon(':/icons/up.svg'))
        self.up.setIconSize(size)
        self.up.clicked.connect(self.upClicked)
        self.up.setToolTip(_('Find the previous occurrence of the phrase'))
        self.down = QPushButton()
        self.down.setIcon(QIcon(':/icons/down.svg'))
        self.down.setIconSize(size)
        self.down.clicked.connect(self.downClicked)
        self.down.setToolTip(_('Find the next occurrence of the phrase'))

        self.highlightAll = QPushButton(_('&Highlight All'))
        self.highlightAll.setToolTip(
            _('Highlight all occurrences of the phrase'))
        self.matchCase = QPushButton(_('&Match Case'))
        self.matchCase.setToolTip(_('Search with case sensitivity'))
        self.wholeWords = QPushButton(_('&Whole Words'))
        self.wholeWords.setToolTip(_('Search whole words only'))
        for widget in (self.highlightAll, self.matchCase, self.wholeWords):
            widget.setCheckable(True)
            widget.setFlat(True)
        self.highlightAll.toggled.connect(self.highlightAllToggled)
        self.matchCase.toggled.connect(self.matchCaseToggled)
        self.wholeWords.toggled.connect(self.wholeWordsToggled)

        self.findResults = QLabel()
        self.findResults.setMinimumWidth(
            QFontMetrics(QFont()).boundingRect(
                _('%s of %s matches') % (1000, 1000)).width() + spacing)
        # Translators: match number of total matches in a search, e.g. 1 of 10 matches
        _('%(matchnumber)s of %(total)s matches')

        #TODO implement this once translations done

        findLayout = QHBoxLayout()
        findLayout.setSpacing(0)
        spacing = 8
        findLayout.addWidget(self.find)
        findLayout.addWidget(self.up)
        findLayout.addWidget(self.down)
        findLayout.addSpacing(spacing)
        findLayout.addWidget(self.highlightAll)
        findLayout.addSpacing(spacing)
        findLayout.addWidget(self.matchCase)
        findLayout.addSpacing(spacing)
        findLayout.addWidget(self.wholeWords)
        findLayout.addSpacing(spacing)
        findLayout.addWidget(self.findResults)

        buttons = QDialogButtonBox(QDialogButtonBox.Close)
        translateDialogBoxButtons(buttons)
        self.clear = buttons.addButton(
            _('Clear'), QDialogButtonBox.ActionRole)  # type: QPushButton
        buttons.rejected.connect(self.reject)
        self.clear.clicked.connect(self.clearClicked)
        self.clear.setEnabled(False)

        layout.addWidget(self.log)
        layout.addLayout(findLayout)
        layout.addSpacing(6)
        layout.addWidget(buttons)

        self.setLayout(layout)

        self.onFindChanged('')

        self.icon_lookup = {
            ErrorType.warning: ':/report/warning.svg',
            ErrorType.serious_error: ':/report/error.svg',
            ErrorType.critical_error: ':/report/critical.svg'
        }

    @pyqtSlot()
    def textChanged(self) -> None:
        self.clear.setEnabled(bool(self.log.document().characterCount()))

    def _makeFind(self, back: bool = False) -> int:
        flags = QTextDocument.FindFlags()
        if self.matchCase.isChecked():
            flags |= QTextDocument.FindCaseSensitively
        if self.wholeWords.isChecked():
            flags |= QTextDocument.FindWholeWords
        if back:
            flags |= QTextDocument.FindBackward
        return flags

    def _clearSearch(self) -> None:
        cursor = self.log.textCursor()  # type: QTextCursor
        if cursor.hasSelection():
            cursor.clearSelection()
            self.log.setTextCursor(cursor)
        self.find_cursors = []
        self.log.setExtraSelections([])

    @pyqtSlot()
    def _doFind(self) -> None:
        """
        Do the find / search.

        If text needs to be appended, delay the search for one second.
        """

        if self.add_queue:
            while self.add_queue:
                self._addProblems(problems=self.add_queue.popleft())
            QTimer.singleShot(1000, self._doFind)
            return

        cursor = self.log.textCursor()  # type: QTextCursor
        text = self.find.getText()
        highlight = self.highlightAll.isChecked()

        if self.find.empty or not text:
            self._clearSearch()
            self.findResults.setText('')
            return

        initial_position = cursor.selectionStart()  # type: int

        self.log.moveCursor(QTextCursor.Start)

        flags = self._makeFind()
        extraSelections = deque()

        count = 0
        index = None
        self.find_cursors = []

        while self.log.find(text, flags):
            cursor = self.log.textCursor()  # type: QTextCursor
            self.find_cursors.append(cursor)

            if index is None and cursor.selectionStart() >= initial_position:
                index = count
            count += 1

            if highlight:
                extra = QTextEdit.ExtraSelection()
                extra.format.setBackground(self.highlightColor)
                extra.format.setForeground(self.textHighlightColor)
                extra.cursor = cursor
                extraSelections.append(extra)

        self.log.setExtraSelections(extraSelections)

        if index is None:
            index = len(self.find_cursors) - 1

        if not self.find_cursors:
            cursor.setPosition(initial_position)
            self.log.setTextCursor(cursor)
            if not self.find.empty:
                self.findResults.setText(_('Phrase not found'))
                self.findResults.setPalette(self.noFindPalette)

        else:
            self.goToMatch(index=index)

        self.search_pending = False

    def goToMatch(self, index: int) -> None:
        if self.find_cursors:
            cursor = self.find_cursors[index]
            self.current_find_index = index
            self.log.setTextCursor(cursor)
            self.findResults.setText(
                _('%s of %s matches') % (index + 1, len(self.find_cursors)))
            self.findResults.setPalette(self.foundPalette)

    @pyqtSlot(bool)
    def upClicked(self, checked: bool) -> None:
        if self.current_find_index >= 0:
            if self.current_find_index == 0:
                index = len(self.find_cursors) - 1
            else:
                index = self.current_find_index - 1
            self.goToMatch(index=index)

    @pyqtSlot(bool)
    def downClicked(self, checked: bool) -> None:
        if self.current_find_index >= 0:
            if self.current_find_index == len(self.find_cursors) - 1:
                index = 0
            else:
                index = self.current_find_index + 1
            self.goToMatch(index=index)

    @pyqtSlot(str)
    def onFindChanged(self, text: str) -> None:
        self.up.setEnabled(not self.find.empty)
        self.down.setEnabled(not self.find.empty)

        self._doFind()

    @pyqtSlot(bool)
    def highlightAllToggled(self, toggled: bool) -> None:
        if self.find_cursors:
            extraSelections = deque()
            if self.highlightAll.isChecked():
                for cursor in self.find_cursors:
                    extra = QTextEdit.ExtraSelection()
                    extra.format.setBackground(self.highlightColor)
                    extra.format.setForeground(self.textHighlightColor)
                    extra.cursor = cursor
                    extraSelections.append(extra)
            self.log.setExtraSelections(extraSelections)

    @pyqtSlot(bool)
    def matchCaseToggled(self, toggled: bool) -> None:
        self._doFind()

    @pyqtSlot(bool)
    def wholeWordsToggled(self, toggled: bool) -> None:
        self._doFind()

    @pyqtSlot(bool)
    def clearClicked(self, toggled: bool) -> None:
        self.log.clear()
        self.clear.setEnabled(False)
        self._doFind()

    @pyqtSlot(QUrl)
    def anchorClicked(self, url: QUrl) -> None:
        if self.rapidApp.file_manager:
            # see documentation for self._saveUrls()
            fake_uri = url.url()
            index = int(fake_uri[fake_uri.find('///') + 3:])
            uri = self.uris[index]

            open_in_file_manager(
                file_manager=self.rapidApp.file_manager,
                file_manager_type=self.rapidApp.file_manager_type,
                uri=uri)

    def _saveUrls(self, text: str) -> str:
        """
        Sadly QTextBrowser uses QUrl, which doesn't understand the kind of URIs
        used by Gnome. It totally mangles them, in fact.

        So solution is to substitute in a dummy uri and then
        replace it in self.anchorClicked() when the user clicks on it
        """

        anchor_start = '<a href="'
        anchor_end = '</a>'

        start = text.find(anchor_start)
        if start < 0:
            return text
        new_text = text[:start]
        while start >= 0:
            href_end = text.find('">', start + 9)
            href = text[start + 9:href_end]
            end = text.find(anchor_end, href_end + 2)
            next_start = text.find(anchor_start, end + 4)
            if next_start >= end + 4:
                extra_text = text[end + 4:next_start]
            else:
                extra_text = text[end + 4:]
            new_text = '{}<a href="file:///{}">{}</a>{}'.format(
                new_text, len(self.uris), text[href_end + 2:end], extra_text)
            self.uris.append(href)
            start = next_start

        return new_text

    def _getBody(self, problem: Problem) -> str:
        """
        Get the body (subject) of the problem, and any details
        """

        line = self._saveUrls(problem.body)

        if len(problem.details) == 1:
            line = '{}<br><i>{}</i>'.format(line,
                                            self._saveUrls(problem.details[0]))
        elif len(problem.details) > 1:
            for detail in problem.details:
                line = '{}<br><i>{}</i>'.format(line, self._saveUrls(detail))

        return line

    def _addProblems(self, problems: Problems) -> None:
        """
        Add problems to the log window
        """

        title = self._saveUrls(problems.title)
        html = '<h1>{}</h1><p></p>'.format(title)
        html = '{}<table>'.format(html)
        for problem in problems:
            line = self._getBody(problem=problem)
            icon = self.icon_lookup[problem.severity]
            icon = '<img src="{}" height=16 width=16>'.format(icon)
            html = '{}<tr><td width=32 align=center>{}</td><td style="padding-bottom: 6px;">' \
                   '{}</td></tr>'.format(html, icon, line)
        html = '{}</table>'.format(html)

        html = '{}<p></p><p></p>'.format(html)
        self.log.append(html)

    def addProblems(self, problems: Problems) -> None:
        if not self.find.empty and self.find_cursors:
            self._clearSearch()

        if not self.find.empty and self.search_pending:
            self.add_queue.append(problems)
        else:
            self._addProblems(problems=problems)

        if not self.find.empty and not self.search_pending:
            self.search_pending = True
            self.findResults.setText(_('Search pending...'))
            self.findResults.setPalette(self.noFindPalette)
            QTimer.singleShot(1000, self._doFind)

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.matches(QKeySequence.Find):
            self.find.setFocus()
        else:
            super().keyPressEvent(event)

    @pyqtSlot()
    def activate(self) -> None:
        self.setVisible(True)
        self.activateWindow()
        self.raise_()

    def showEvent(self, event: QShowEvent) -> None:
        super().showEvent(event)
        self.dialogShown.emit()

    def changeEvent(self, event: QEvent) -> None:
        if event.type() == QEvent.ActivationChange and self.isActiveWindow():
            self.dialogActivated.emit()
        super().changeEvent(event)
Example #24
0
class MainWindow(QMainWindow):
    '''The main window of the application.'''
    def __init__(self, config: Config):
        '''Initializes a MainWindow instance.'''
        super().__init__()

        # Store configuration.
        self._config = config

        # Create model.
        self._model = MainModel()
        self._model.matchCase = config.matchCase
        self._model.includePrivateMembers = config.includePrivateMembers
        self._model.includeInheritedMembers = config.includeInheritedMembers
        self._model.sortByType = config.sortByType
        self._model.setModuleNames(config.moduleNames)

        # Configure window.
        self.setWindowTitle('pyspector')
        self.setGeometry(100, 100, 1200, 800)

        # Crete user interface widgets.
        self._searchEdit = SearchEdit()
        self._searchEdit.textChanged.connect(self._searchEditTextChanged)
        self._searchEdit.delayedTextChanged.connect(self._selectFirstMatch)

        matchCaseCheckBox = QCheckBox()
        matchCaseCheckBox.setText('Match case')
        matchCaseCheckBox.setCheckState(
            Qt.Checked if self._model.matchCase else Qt.Unchecked)
        matchCaseCheckBox.stateChanged.connect(
            self._matchCaseCheckBoxStateChanged)

        includePrivateCheckBox = QCheckBox()
        includePrivateCheckBox.setText('Include private members')
        includePrivateCheckBox.setCheckState(
            Qt.Checked if self._model.includePrivateMembers else Qt.Unchecked)
        includePrivateCheckBox.stateChanged.connect(
            self._includePrivateCheckBoxStateChanged)

        includeInheritedCheckBox = QCheckBox()
        includeInheritedCheckBox.setText('Include inherited members')
        includeInheritedCheckBox.setCheckState(
            Qt.Checked if self._model.includeInheritedMembers else Qt.Unchecked
        )
        includeInheritedCheckBox.stateChanged.connect(
            self._includeInheritedCheckBoxStateChanged)

        sortByTypeCheckBox = QCheckBox()
        sortByTypeCheckBox.setText('Sort by type')
        sortByTypeCheckBox.setCheckState(
            Qt.Checked if self._model.sortByType else Qt.Unchecked)
        sortByTypeCheckBox.stateChanged.connect(
            self._sortByTypeCheckBoxStateChanged)

        self._treeView = TreeView()
        self._treeView.setUniformRowHeights(True)
        self._treeView.setAlternatingRowColors(True)
        self._treeView.setHeaderHidden(True)
        self._treeView.setModel(self._model.filteredTreeModel)
        self._treeView.hideColumn(1)
        self._treeView.hideColumn(2)
        selectionModel = self._treeView.selectionModel()
        selectionModel.currentChanged.connect(self._treeViewSelectionChanged)

        selectModulesButton = QPushButton()
        selectModulesButton.setText('Select modules')
        selectModulesButton.clicked.connect(self._selectModulesButtonClicked)

        leftLayout = QVBoxLayout()
        leftLayout.addWidget(self._searchEdit)
        leftLayout.addWidget(matchCaseCheckBox)
        leftLayout.addWidget(includePrivateCheckBox)
        leftLayout.addWidget(includeInheritedCheckBox)
        leftLayout.addWidget(sortByTypeCheckBox)
        leftLayout.addWidget(self._treeView)
        leftLayout.addWidget(selectModulesButton)
        leftLayout.setContentsMargins(0, 0, 0, 0)
        leftWidget = QWidget()
        leftWidget.setLayout(leftLayout)

        self._textBrowser = QTextBrowser()
        self._textBrowser.setOpenLinks(False)
        self._textBrowser.anchorClicked.connect(self._linkClicked)

        fontFamily = 'Menlo' if platform.system() == 'Darwin' else 'Consolas'
        fixedPitchFont = QFont(fontFamily)
        fixedPitchFont.setFixedPitch(True)
        self._sourceTextViewer = QPlainTextEdit()
        self._sourceTextViewer.setReadOnly(True)
        self._sourceTextViewer.setFont(fixedPitchFont)
        self._sourceTextViewer.setLineWrapMode(QPlainTextEdit.NoWrap)
        self._sourceTextHighlighter = PythonSyntaxHighlighter(
            self._sourceTextViewer.document())

        rightSplitter = QSplitter()
        rightSplitter.setOrientation(Qt.Vertical)
        rightSplitter.setHandleWidth(20)
        rightSplitter.setChildrenCollapsible(False)
        rightSplitter.addWidget(self._textBrowser)
        rightSplitter.addWidget(self._sourceTextViewer)

        splitter = QSplitter()
        splitter.setHandleWidth(20)
        splitter.setChildrenCollapsible(False)
        splitter.addWidget(leftWidget)
        splitter.addWidget(rightSplitter)
        splitter.setSizes([300, 900])

        centralLayout = QHBoxLayout()
        centralLayout.addWidget(splitter)
        centralWidget = QWidget()
        centralWidget.setLayout(centralLayout)
        self.setCentralWidget(centralWidget)

        # Create keyboard shortcuts.
        findShortcut = QShortcut(QKeySequence.Find, centralWidget)
        findShortcut.activated.connect(self._findShortcutActivated)

        # Make sure colors are correct for current palette.
        self._updateColors()

        # Show the window.
        self.show()

    def _findShortcutActivated(self) -> None:
        self._searchEdit.selectAll()
        self._searchEdit.setFocus()

    def _searchEditTextChanged(self, text: str) -> None:
        '''Filters the tree view to show just those items relevant to the search text.'''
        self._model.searchText = text

    def _matchCaseCheckBoxStateChanged(self, state: Qt.CheckState) -> None:
        '''Determines whether matching is case-sensitive.'''
        isChecked = state == Qt.Checked
        self._config.matchCase = isChecked
        self._model.matchCase = isChecked
        self._selectFirstMatch()

    def _includePrivateCheckBoxStateChanged(self,
                                            state: Qt.CheckState) -> None:
        ''' Includes or excludes private members from the tree view.'''
        isChecked = state == Qt.Checked
        self._config.includePrivateMembers = isChecked
        self._model.includePrivateMembers = isChecked
        self._selectFirstMatch()

    def _includeInheritedCheckBoxStateChanged(self,
                                              state: Qt.CheckState) -> None:
        ''' Includes or excludes inherited members from the tree view.'''
        isChecked = state == Qt.Checked
        self._config.includeInheritedMembers = isChecked
        self._model.includeInheritedMembers = isChecked
        self._selectFirstMatch()

    def _sortByTypeCheckBoxStateChanged(self, state: Qt.CheckState) -> None:
        ''' Includes or excludes private members from the tree view.'''
        isChecked = state == Qt.Checked
        self._config.sortByType = isChecked
        self._model.sortByType = isChecked

    def _selectFirstMatch(self) -> None:
        # Select the first match to the current search text (if any).
        searchText = self._model.searchText
        index = self._model.findItemByName(searchText) if len(
            searchText) else QModelIndex()
        if index.isValid():
            self._treeView.expandAll()
            self._treeView.scrollTo(index)
            self._treeView.selectionModel().select(
                index, QItemSelectionModel.ClearAndSelect)
            self._updateInfo(index)
        else:
            self._treeView.collapseAll()
            selectedIndexes = self._treeView.selectedIndexes()
            if len(selectedIndexes):
                self._treeView.scrollTo(selectedIndexes[0])

    def changeEvent(self, event: QEvent) -> None:
        '''Updates colors when palette change events occur.'''
        if event.type() == QEvent.PaletteChange:
            self._updateColors()

    def _updateColors(self) -> None:
        '''Modifies colors when the palette changes from dark to light or vice versa.'''
        isDark = self.palette().window().color().valueF() < 0.5
        textColor = 'silver' if isDark else 'black'
        linkColor = 'steelBlue' if isDark else 'blue'
        self._textBrowser.document().setDefaultStyleSheet(
            f'* {{ color: {textColor}; }} a {{ color: {linkColor}; }}')
        self._updateInfo()
        self._sourceTextViewer.setStyleSheet(
            f'QPlainTextEdit {{ color: {textColor}; }}')
        self._sourceTextHighlighter.theme = Theme.DARK if isDark else Theme.LIGHT

    def _treeViewSelectionChanged(self, index: QModelIndex,
                                  oldIndex: QModelIndex) -> None:
        '''Displays appropriate information whenever the tree view selection changes.'''
        self._updateInfo(index)

    def _updateInfo(self, index: QModelIndex = QModelIndex()) -> None:
        '''Determines which object is selected and displays appropriate info.'''
        if not index.isValid():
            selectedIndexes = self._treeView.selectedIndexes()
            if len(selectedIndexes):
                index = selectedIndexes[0]

        item = utilities.getItemFromIndex(self._model.filteredTreeModel, index)
        if item:
            self._displayInfo(item)
        else:
            self._textBrowser.clear()

    def _displayInfo(self, item: QStandardItem) -> None:
        '''Updates the detailed view to show information about the selected object.'''
        data = item.data()
        memberType = data['type']
        memberValue = data['value']
        error = data['error']

        # Display the fully qualified name of the item.
        # TODO: Use __qualname__?
        fullName = item.text()
        tempItem = item.parent()
        while tempItem:
            fullName = tempItem.text() + '.' + fullName
            tempItem = tempItem.parent()
        html = f'<h2>{fullName}</h2>'
        if hasattr(memberValue, '__qualname__'):
            html += f'<h2>{memberValue.__qualname__}</h2>'

        # Display the type.
        displayType = memberType
        if memberType == 'object':
            displayType = str(type(memberValue))
        html += f'<p><b>Type:</b> {escape(displayType)}</p>'

        # Display object value.
        if memberType == 'object':
            html += f'<p><b>Value:</b> {escape(repr(memberValue))}'

        # Display error message.
        if len(error):
            html += f'<p><b>Error:</b> {escape(error)}'

        # Display the filename for modules.
        # See if we can find the source file for other objects.
        if memberType == 'module' and hasattr(memberValue, '__file__'):
            html += f'<p><b>File:</b> <a href="file:{memberValue.__file__}">{memberValue.__file__}</a></p>'
            self._displaySource(memberValue.__file__)
        else:
            try:
                # Substitute the getter, setter, or deleter for a property instance.
                # TODO: Generalize this to data descriptors other than just the 'property' class.
                if isinstance(memberValue, property):
                    if memberValue.fget:
                        memberValue = memberValue.fget
                    elif memberValue.fset:
                        memberValue = memberValue.fset
                    elif memberValue.fdel:
                        memberValue = memberValue.fdel

                # Note that inspect.getsourcelines calls unwrap, while getsourcefile does not.
                sourceFile = inspect.getsourcefile(inspect.unwrap(memberValue))
                lines = inspect.getsourcelines(memberValue)
                html += f'<p><b>File:</b> <a href="file:{sourceFile}">{sourceFile} ({lines[1]})</a></p>'
                self._displaySource(sourceFile, lines[1], len(lines[0]))
            except:
                self._displaySourceError('Could not locate source code.')

        # Display the inheritance hierarchy of classes.
        if 'class' in memberType:
            try:
                baseClasses = inspect.getmro(memberValue)
                if len(baseClasses) > 1:
                    html += '<p><b>Base classes:</b></p><ul>'
                    baseClasses = list(baseClasses)[1:]  # omit the first entry
                    for baseClass in reversed(baseClasses):
                        moduleName = baseClass.__module__
                        className = baseClass.__qualname__
                        html += f'<li><a href="item:{moduleName}/{className}">{className}</a> from {moduleName}</li>'
                    html += '</ul>'
                derivedClasses = memberValue.__subclasses__()
                if len(derivedClasses) > 0:
                    html += '<p><b>Derived classes:</b></p><ul>'
                    for derivedClass in derivedClasses:
                        moduleName = derivedClass.__module__
                        className = derivedClass.__qualname__
                        html += f'<li><a href="item:{moduleName}/{className}">{className}</a> from {moduleName}</li>'
                    html += '</ul>'
            except:
                pass

        # Display the signature of callable objects.
        try:
            signature = str(inspect.signature(memberValue))
            html += f'<p><b>Signature:</b> {memberValue.__name__}{signature}</p>'
        except:
            pass

        # Display documentation for non-object types, converting from reStructuredText or markdown
        # to HTML.
        if memberType != 'object':
            doc = inspect.getdoc(data['value'])
            if doc:
                # Check for special cases where docstrings are plain text.
                if fullName in ['sys']:
                    docHtml = f'<pre>{escape(doc)}</pre>'
                else:
                    # If we encounter improper reStructuredText markup leading to an exception
                    # or a "problematic" span, just treat the input as markdown.
                    try:
                        docHtml = rstToHtml(doc)
                        if '<span class="problematic"' in docHtml:
                            docHtml = markdown(doc)
                    except:
                        docHtml = markdown(doc)
                html += f'<hr>{docHtml}'

        self._textBrowser.setHtml(html)

    def _displaySource(self,
                       filename: str,
                       startLine: int = None,
                       lineCount: int = None) -> None:
        '''Shows source code within the source text viewer.'''
        try:
            # Read the file and populate the source text viewer.
            with open(filename) as fp:
                lines = fp.readlines()
                self._sourceTextViewer.setPlainText(''.join(lines))

            # Restart the Python syntax highlighter.
            self._sourceTextHighlighter.setDocument(
                self._sourceTextViewer.document())

            # If line numbers are available, highlight the lines encompassing the currently
            # selected member.
            if startLine != None:
                cursor = QTextCursor(self._sourceTextViewer.document())
                cursor.movePosition(QTextCursor.Start)
                if startLine > 1:
                    cursor.movePosition(QTextCursor.NextBlock,
                                        QTextCursor.MoveAnchor, startLine - 1)
                self._sourceTextViewer.setTextCursor(cursor)
                cursor.movePosition(QTextCursor.NextBlock,
                                    QTextCursor.KeepAnchor, lineCount)
                lineColor = QColor(255, 255, 0, 48)
                extraSelection = QTextEdit.ExtraSelection()
                extraSelection.format.setBackground(lineColor)
                extraSelection.format.setProperty(
                    QTextFormat.FullWidthSelection, True)
                extraSelection.cursor = cursor
                self._sourceTextViewer.setExtraSelections([extraSelection])
                self._sourceTextViewer.centerCursor()
            else:
                self._sourceTextViewer.setExtraSelections([])
        except:
            self._displaySourceError('Could not open file.')

    def _displaySourceError(self, errorMessage: str) -> None:
        '''Displays an error message within the source text viewer.'''
        self._sourceTextViewer.setPlainText(errorMessage)
        self._sourceTextViewer.setExtraSelections([])
        self._sourceTextHighlighter.setDocument(None)

    def _linkClicked(self, url: QUrl) -> None:
        scheme = url.scheme()
        if scheme == 'file':
            # Open the file in an external application.
            utilities.openFile(url.path())
        elif scheme == 'http' or scheme == 'https':
            # Open the web page in the default browser.
            webbrowser.open_new_tab(url.toString())
        elif scheme == 'item':
            # Clear the search and select the item (if present in the tree).
            self._searchEdit.clear()
            self._model.searchText = ''
            index = self._model.findItemById(url.path())
            if index.isValid():
                self._treeView.setCurrentIndex(index)

    def _selectModulesButtonClicked(self) -> None:
        self._moduleSelectionDialog = ModuleSelectionDialog(
            self, self._config.moduleNames)
        self._moduleSelectionDialog.finished.connect(
            self._moduleSelectionDialogFinished)
        self._moduleSelectionDialog.open()

    def _moduleSelectionDialogFinished(self, result: int) -> None:
        if result == ModuleSelectionDialog.Accepted:
            moduleNames = self._moduleSelectionDialog.selectedModuleNames
            self._model.setModuleNames(moduleNames)
            self._config.moduleNames = moduleNames
            self._selectFirstMatch()