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())
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)
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)
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)
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)
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)
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)))
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)))
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)))
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
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)
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ébastien Gosselin <br> <a href="mailto:[email protected]"> [email protected] </a> <br> <br> Developped and maintained by Jean-Sé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())
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()
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)
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])
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 = " • " 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
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)
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ébastien Gosselin <br> <a href="mailto:[email protected]"> [email protected] </a> <br> <br> Developped and maintained by Jean-Sé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())
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
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)
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()
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)
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()