class Browser(QWidget): """LilyPond documentation browser widget.""" def __init__(self, dockwidget): super(Browser, self).__init__(dockwidget) layout = QVBoxLayout(spacing=0) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.toolbar = tb = QToolBar() self.webview = QWebEngineView(self, contextMenuPolicy=Qt.CustomContextMenu) self.webview.setPage(WebEnginePage(self.webview)) self.chooser = QComboBox(sizeAdjustPolicy=QComboBox.AdjustToContents) self.search = SearchEntry(maximumWidth=200, clearButtonEnabled=True) layout.addWidget(self.toolbar) layout.addWidget(self.webview) ac = dockwidget.actionCollection ac.help_back.triggered.connect(self.webview.back) ac.help_forward.triggered.connect(self.webview.forward) ac.help_home.triggered.connect(self.showHomePage) ac.help_print.triggered.connect(self.slotPrint) self.webview.urlChanged.connect(self.slotUrlChanged) self.webview.customContextMenuRequested.connect( self.slotShowContextMenu) tb.addAction(ac.help_back) tb.addAction(ac.help_forward) tb.addSeparator() tb.addAction(ac.help_home) tb.addAction(ac.help_print) tb.addSeparator() tb.addWidget(self.chooser) tb.addWidget(self.search) self.chooser.activated[int].connect(self.showHomePage) self.search.textChanged.connect(self.slotSearchChanged) self.search.returnPressed.connect(self.slotSearchReturnPressed) dockwidget.mainwindow().iconSizeChanged.connect( self.updateToolBarSettings) dockwidget.mainwindow().toolButtonStyleChanged.connect( self.updateToolBarSettings) app.settingsChanged.connect(self.readSettings) self.readSettings() self.loadDocumentation() self.showInitialPage() app.settingsChanged.connect(self.loadDocumentation) app.translateUI(self) def readSettings(self): s = QSettings() s.beginGroup("documentation") ws = self.webview.page().settings() family = s.value("fontfamily", self.font().family(), str) size = s.value("fontsize", 16, int) ws.setFontFamily(QWebEngineSettings.StandardFont, family) ws.setFontSize(QWebEngineSettings.DefaultFontSize, size) fixed = textformats.formatData('editor').font ws.setFontFamily(QWebEngineSettings.FixedFont, fixed.family()) ws.setFontSize(QWebEngineSettings.DefaultFixedFontSize, int(fixed.pointSizeF() * 96 / 72)) self.webview.page().profile().setHttpAcceptLanguage(','.join( lilydoc.network.langs())) def keyPressEvent(self, ev): if ev.text() == "/": self.search.setFocus() else: super(Browser, self).keyPressEvent(ev) def translateUI(self): try: self.search.setPlaceholderText(_("Search...")) except AttributeError: pass # not in Qt 4.6 def showInitialPage(self): """Shows the preferred start page. If a local documentation instance already has a suitable version, just loads it. Otherwise connects to the allLoaded signal, that is emitted when all the documentation instances have loaded their version information and then shows the start page (if another page wasn't yet loaded). """ if self.webview.url().isEmpty(): docs = lilydoc.manager.docs() version = lilypondinfo.preferred().version() index = -1 if version: for num, doc in enumerate(docs): if doc.version() is not None and doc.version() >= version: index = num # a suitable documentation is found break if index == -1: # nothing found (or LilyPond version not available), # wait for loading or show the most recent version if not lilydoc.manager.loaded(): lilydoc.manager.allLoaded.connect(self.showInitialPage) return index = len(docs) - 1 self.chooser.setCurrentIndex(index) self.showHomePage() def loadDocumentation(self): """Puts the available documentation instances in the combobox.""" i = self.chooser.currentIndex() self.chooser.clear() for doc in lilydoc.manager.docs(): v = doc.versionString() if doc.isLocal(): t = _("(local)") else: t = _("({hostname})").format(hostname=doc.url().host()) self.chooser.addItem("{0} {1}".format(v or _("<unknown>"), t)) self.chooser.setCurrentIndex(i) if not lilydoc.manager.loaded(): lilydoc.manager.allLoaded.connect(self.loadDocumentation, -1) return def updateToolBarSettings(self): mainwin = self.parentWidget().mainwindow() self.toolbar.setIconSize(mainwin.iconSize()) self.toolbar.setToolButtonStyle(mainwin.toolButtonStyle()) def showManual(self): """Invoked when the user presses F1.""" self.slotHomeFrescobaldi() # TEMP def slotUrlChanged(self): ac = self.parentWidget().actionCollection ac.help_back.setEnabled(self.webview.history().canGoBack()) ac.help_forward.setEnabled(self.webview.history().canGoForward()) def openUrl(self, url): if url.path().endswith(('.ily', '.lyi', '.ly')): self.sourceViewer().showReply(lilydoc.network.get(url)) else: self.webview.load(url) def slotUnsupported(self, reply): helpers.openUrl(reply.url()) def slotSearchChanged(self): text = self.search.text() if not text.startswith(':'): self.webview.page().findText(text) def slotSearchReturnPressed(self): text = self.search.text() if not text.startswith(':'): self.slotSearchChanged() else: pass # TODO: implement full doc search def sourceViewer(self): try: return self._sourceviewer except AttributeError: from . import sourceviewer self._sourceviewer = sourceviewer.SourceViewer(self) return self._sourceviewer def showHomePage(self): """Shows the homepage of the LilyPond documentation.""" i = self.chooser.currentIndex() if i < 0: i = 0 doc = lilydoc.manager.docs()[i] url = doc.home() if doc.isLocal(): path = url.toLocalFile() langs = lilydoc.network.langs() if langs: for lang in langs: if os.path.exists(path + '.' + lang + '.html'): path += '.' + lang break url = QUrl.fromLocalFile(path + '.html') self.webview.load(url) def slotPrint(self): printer = self._printer = QPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(app.caption(_("Print"))) if dlg.exec_(): self.webview.page().print(printer, self.slotPrintingDone) def slotPrintingDone(self, success): del self._printer def slotShowContextMenu(self, pos): d = self.webview.page().contextMenuData() menu = QMenu() if d.linkUrl().isValid(): a = self.webview.pageAction(QWebEnginePage.CopyLinkToClipboard) a.setIcon(icons.get("edit-copy")) a.setText(_("Copy &Link")) menu.addAction(a) menu.addSeparator() a = menu.addAction(icons.get("window-new"), _("Open Link in &New Window")) a.triggered.connect( (lambda url: lambda: self.slotNewWindow(url))(d.linkUrl())) else: if d.selectedText(): a = self.webview.pageAction(QWebEnginePage.Copy) a.setIcon(icons.get("edit-copy")) a.setText(_("&Copy")) menu.addAction(a) menu.addSeparator() a = menu.addAction(icons.get("window-new"), _("Open Document in &New Window")) a.triggered.connect((lambda url: lambda: self.slotNewWindow(url))( self.webview.url())) if menu.actions(): menu.exec_(self.webview.mapToGlobal(pos)) def slotNewWindow(self, url): helpers.openUrl(url)
class MainWindow(QMainWindow): def __init__(self, url): super().__init__() self.setAttribute(Qt.WA_DeleteOnClose, True) self.progress = 0 f = QFile() f.setFileName(":/jquery.min.js") f.open(QIODevice.ReadOnly) self.jQuery = f.readAll().data().decode() self.jQuery += "\nvar qt = { 'jQuery': jQuery.noConflict(true) };" f.close() self.view = QWebEngineView(self) self.view.load(url) self.view.loadFinished.connect(self.adjustLocation) self.view.titleChanged.connect(self.adjustTitle) self.view.loadProgress.connect(self.setProgress) self.view.loadFinished.connect(self.finishLoading) self.locationEdit = QLineEdit(self) self.locationEdit.setSizePolicy( QSizePolicy.Expanding, self.locationEdit.sizePolicy().verticalPolicy()) self.locationEdit.returnPressed.connect(self.changeLocation) toolBar = self.addToolBar(self.tr("Navigation")) toolBar.addAction(self.view.pageAction(QWebEnginePage.Back)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Forward)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Reload)) toolBar.addAction(self.view.pageAction(QWebEnginePage.Stop)) toolBar.addWidget(self.locationEdit) viewMenu = self.menuBar().addMenu(self.tr("&View")) viewSourceAction = QAction(self.tr("Page Source"), self) viewSourceAction.triggered.connect(self.viewSource) viewMenu.addAction(viewSourceAction) effectMenu = self.menuBar().addMenu(self.tr("&Effect")) effectMenu.addAction(self.tr("Highlight all links"), self.highlightAllLinks) self.rotateAction = QAction(self) self.rotateAction.setIcon(self.style().standardIcon( QStyle.SP_FileDialogDetailedView)) self.rotateAction.setCheckable(True) self.rotateAction.setText(self.tr("Turn images upside down")) self.rotateAction.toggled.connect(self.rotateImages) effectMenu.addAction(self.rotateAction) toolsMenu = self.menuBar().addMenu(self.tr("&Tools")) toolsMenu.addAction(self.tr("Remove GIF images"), self.removeGifImages) toolsMenu.addAction(self.tr("Remove all inline frames"), self.removeInlineFrames) toolsMenu.addAction(self.tr("Remove all object elements"), self.removeObjectElements) toolsMenu.addAction(self.tr("Remove all embedded elements"), self.removeEmbeddedElements) self.setCentralWidget(self.view) @pyqtSlot() def adjustLocation(self): self.locationEdit.setText(self.view.url().toString()) @pyqtSlot() def changeLocation(self): url = QUrl.fromUserInput(self.locationEdit.text()) self.view.load(url) self.view.setFocus() @pyqtSlot() def adjustTitle(self): if self.progress <= 0 or self.progress >= 100: self.setWindowTitle(self.view.title()) else: self.setWindowTitle("%s (%2d)" % (self.view.title(), self.progress)) @pyqtSlot(int) def setProgress(self, p): self.progress = p self.adjustTitle() @pyqtSlot() def finishLoading(self): self.progress = 100 self.adjustTitle() self.view.page().runJavaScript(self.jQuery) self.rotateImages(self.rotateAction.isChecked()) @pyqtSlot() def viewSource(self): self.textEdit = QTextEdit() self.textEdit.setAttribute(Qt.WA_DeleteOnClose) self.textEdit.adjustSize() self.textEdit.move(self.geometry().center() - self.textEdit.rect().center()) self.textEdit.show() self.view.page().toHtml(self.textEdit.setPlainText) @pyqtSlot() def highlightAllLinks(self): code = "qt.jQuery('a').each( function () { qt.jQuery(this).css('background-color', 'yellow') } )" self.view.page().runJavaScript(code) @pyqtSlot(bool) def rotateImages(self, invert): code = "" if invert: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(180deg)') } )" else: code = "qt.jQuery('img').each( function () { qt.jQuery(this).css('transition', 'transform 2s'); qt.jQuery(this).css('transform', 'rotate(0deg)') } )" self.view.page().runJavaScript(code) @pyqtSlot() def removeGifImages(self): code = "qt.jQuery('[src*=gif]').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeInlineFrames(self): code = "qt.jQuery('iframe').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeObjectElements(self): code = "qt.jQuery('object').remove()" self.view.page().runJavaScript(code) @pyqtSlot() def removeEmbeddedElements(self): code = "qt.jQuery('embed').remove()" self.view.page().runJavaScript(code)