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 = QWebView(contextMenuPolicy=Qt.CustomContextMenu) self.chooser = QComboBox(sizeAdjustPolicy=QComboBox.AdjustToContents) self.search = SearchEntry(maximumWidth=200) 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.page().setNetworkAccessManager( lilydoc.network.accessmanager()) self.webview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.webview.page().linkClicked.connect(self.openUrl) self.webview.page().setForwardUnsupportedContent(True) self.webview.page().unsupportedContent.connect(self.slotUnsupported) 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.textEdited.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(QWebSettings.StandardFont, family) ws.setFontSize(QWebSettings.DefaultFontSize, size) fixed = textformats.formatData('editor').font ws.setFontFamily(QWebSettings.FixedFont, fixed.family()) ws.setFontSize(QWebSettings.DefaultFixedFontSize, fixed.pointSizeF() * 96 / 72) 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, QWebPage.FindWrapsAroundDocument) 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 = QPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(app.caption(_("Print"))) if dlg.exec_(): self.webview.print_(printer) def slotShowContextMenu(self, pos): hit = self.webview.page().currentFrame().hitTestContent(pos) menu = QMenu() if hit.linkUrl().isValid(): a = self.webview.pageAction(QWebPage.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))(hit.linkUrl())) else: if hit.isContentSelected(): a = self.webview.pageAction(QWebPage.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 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 = QWebView(contextMenuPolicy=Qt.CustomContextMenu) self.chooser = QComboBox(sizeAdjustPolicy=QComboBox.AdjustToContents) self.search = SearchEntry(maximumWidth=200) 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.page().setNetworkAccessManager(lilydoc.network.accessmanager()) self.webview.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) self.webview.page().linkClicked.connect(self.openUrl) self.webview.page().setForwardUnsupportedContent(True) self.webview.page().unsupportedContent.connect(self.slotUnsupported) 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.textEdited.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(QWebSettings.StandardFont, family) ws.setFontSize(QWebSettings.DefaultFontSize, size) fixed = textformats.formatData('editor').font ws.setFontFamily(QWebSettings.FixedFont, fixed.family()) ws.setFontSize(QWebSettings.DefaultFixedFontSize, fixed.pointSizeF() * 96 / 72) 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, QWebPage.FindWrapsAroundDocument) 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 = QPrinter() dlg = QPrintDialog(printer, self) dlg.setWindowTitle(app.caption(_("Print"))) if dlg.exec_(): self.webview.print_(printer) def slotShowContextMenu(self, pos): hit = self.webview.page().currentFrame().hitTestContent(pos) menu = QMenu() if hit.linkUrl().isValid(): a = self.webview.pageAction(QWebPage.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))(hit.linkUrl())) else: if hit.isContentSelected(): a = self.webview.pageAction(QWebPage.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 HelpWidget(QWidget): def __init__(self): super().__init__() # no parent; we are a standalone window # Set window title to translated value self.setWindowTitle( _TR("title of help viewer window", "PPQT Help Viewer")) # Initialize saved geometry, see showEvent() self.last_shape = None # Initialize find string, see find_action(). self.find_text = None self.view = QWebView() hb = QHBoxLayout() hb.addWidget(self.view) self.setLayout(hb) # Look for the help text file and load it if found. paths.notify_me(self.path_change) self.help_url = None self.path_change() # force a load now # Slot to receive the pathChanged signal from the paths module. That # signal is given when any of the standard paths are changed. Use the # extras path to (re)load the help file. # # This is also called from __init__ to do the initial load, and from # keyEvent processing to implement Back when the history stack is empty. # # The file we need to load is not actually ppqt2help.html but the # file sphinx/index.html. QWebView only shows the nice trimmings if we # open that. def path_change(self, code='extras'): if code == 'extras': extras_path = paths.get_extras_path() help_path = os.path.join(extras_path, 'ppqt2help.html') sphinx_path = os.path.join(extras_path, 'sphinx', 'index.html') if os.access(help_path, os.R_OK) and os.access( sphinx_path, os.R_OK): # the help file exists. Save a QUrl describing it, and load it. self.help_url = QUrl.fromLocalFile(sphinx_path) self.view.load(self.help_url) else: self.help_url = None self.view.setHtml(DEFAULT_HTML) def closeEvent(self, event): self.last_shape = self.saveGeometry() event.ignore() self.hide() return super().closeEvent(event) def showEvent(self, event): if self.last_shape: # is not None self.restoreGeometry(self.last_shape) def keyPressEvent(self, event): kkey = int(int(event.modifiers()) & C.KEYPAD_MOD_CLEAR) | int( event.key()) if kkey == C.CTL_F: event.accept() self.find_action() elif kkey == C.CTL_G: event.accept() self.find_next_action() elif kkey == C.CTL_SHFT_G: event.accept() self.find_prior_action() elif kkey in C.KEYS_WEB_BACK: event.accept() if self.view.history().canGoBack(): self.view.history().back() elif self.help_url is not None: self.view.load(self.help_url) else: # try to reload the default text self.path_change() elif kkey in C.KEYS_WEB_FORWARD: if self.view.history().canGoForward(): self.view.history().forward() else: utilities.beep() else: super().keyPressEvent(event) # Methods for a basic Find operation. Other modules e.g. noteview have a # custom Edit menu with Find/Next as menu actions. Here we do not support # any editing actions, so support Find/Next/Prior only via keystrokes. # # Use the simple find dialog in utilities to get a string to look for. # Initialize the dialog with up to 40 chars of the current selection. If # the user clicks Cancel in the Find dialog, self.find_text is None; if # the user just clears the input area and clicks OK the find_text is an # empty string. In either case do nothing. # # Following a successful find, the found text is selected in the view, so # if you do ^f again without disturbing that selection, that text is back # in the dialog to be used or replaced. So ^f+Enter is the same as ^g. FIND_NORMAL = QWebPage.FindWrapsAroundDocument FIND_PRIOR = QWebPage.FindWrapsAroundDocument | QWebPage.FindBackward def find_action(self): prep_text = self.view.selectedText()[:40] self.find_text = utilities.get_find_string( _TR('Help viewer find dialog', 'Text to find'), self, prep_text) if self.find_text: # is not None nor an empty string self._do_find(self.FIND_NORMAL) # For ^g Find Next, if there is no active find-text pretend that ^f was # hit. If there was a prior search, repeat the search. def find_next_action(self): if self.find_text: # is not None nor an empty string self._do_find(self.FIND_NORMAL) else: self.find_action() # for ^G Find Prior, same as for ^g but backward. def find_prior_action(self): if self.find_text: self._do_find(self.FIND_PRIOR) else: self.find_action() # The actual search, factored out of the above actions. Because the # search wraps around, if it fails, it fails, and that's that. def _do_find(self, find_flags): if not self.view.page().findText(self.find_text, find_flags): utilities.beep()
class HelpWidget(QWidget) : def __init__( self ) : super().__init__() # no parent; we are a standalone window # Set window title to translated value self.setWindowTitle( _TR( "title of help viewer window", "PPQT Help Viewer" ) ) # Initialize saved geometry, see showEvent() self.last_shape = None # Initialize find string, see find_action(). self.find_text = None self.view = QWebView() hb = QHBoxLayout() hb.addWidget(self.view) self.setLayout(hb) # Look for the help text file and load it if found. paths.notify_me( self.path_change ) self.help_url = None self.path_change( ) # force a load now # Slot to receive the pathChanged signal from the paths module. That # signal is given when any of the standard paths are changed. Use the # extras path to (re)load the help file. # # This is also called from __init__ to do the initial load, and from # keyEvent processing to implement Back when the history stack is empty. # # The file we need to load is not actually ppqt2help.html but the # file sphinx/index.html. QWebView only shows the nice trimmings if we # open that. def path_change( self, code='extras' ) : if code == 'extras' : extras_path = paths.get_extras_path() help_path = os.path.join( extras_path , 'ppqt2help.html' ) sphinx_path = os.path.join( extras_path, 'sphinx', 'index.html' ) if os.access( help_path, os.R_OK ) and os.access( sphinx_path, os.R_OK ) : # the help file exists. Save a QUrl describing it, and load it. self.help_url = QUrl.fromLocalFile( sphinx_path ) self.view.load( self.help_url ) else : self.help_url = None self.view.setHtml( DEFAULT_HTML ) def closeEvent( self, event ) : self.last_shape = self.saveGeometry() event.ignore() self.hide() return super().closeEvent(event) def showEvent( self, event ) : if self.last_shape : # is not None self.restoreGeometry( self.last_shape ) def keyPressEvent( self, event ) : kkey = int( int(event.modifiers()) & C.KEYPAD_MOD_CLEAR) | int(event.key() ) if kkey == C.CTL_F : event.accept() self.find_action() elif kkey == C.CTL_G : event.accept() self.find_next_action() elif kkey == C.CTL_SHFT_G : event.accept() self.find_prior_action() elif kkey in C.KEYS_WEB_BACK : event.accept() if self.view.history().canGoBack() : self.view.history().back() elif self.help_url is not None: self.view.load( self.help_url ) else: # try to reload the default text self.path_change() elif kkey in C.KEYS_WEB_FORWARD : if self.view.history().canGoForward() : self.view.history().forward() else: utilities.beep() else : super().keyPressEvent(event) # Methods for a basic Find operation. Other modules e.g. noteview have a # custom Edit menu with Find/Next as menu actions. Here we do not support # any editing actions, so support Find/Next/Prior only via keystrokes. # # Use the simple find dialog in utilities to get a string to look for. # Initialize the dialog with up to 40 chars of the current selection. If # the user clicks Cancel in the Find dialog, self.find_text is None; if # the user just clears the input area and clicks OK the find_text is an # empty string. In either case do nothing. # # Following a successful find, the found text is selected in the view, so # if you do ^f again without disturbing that selection, that text is back # in the dialog to be used or replaced. So ^f+Enter is the same as ^g. FIND_NORMAL = QWebPage.FindWrapsAroundDocument FIND_PRIOR = QWebPage.FindWrapsAroundDocument | QWebPage.FindBackward def find_action(self): prep_text = self.view.selectedText()[:40] self.find_text = utilities.get_find_string( _TR('Help viewer find dialog','Text to find'), self, prep_text) if self.find_text : # is not None nor an empty string self._do_find( self.FIND_NORMAL ) # For ^g Find Next, if there is no active find-text pretend that ^f was # hit. If there was a prior search, repeat the search. def find_next_action(self): if self.find_text : # is not None nor an empty string self._do_find( self.FIND_NORMAL ) else : self.find_action() # for ^G Find Prior, same as for ^g but backward. def find_prior_action(self): if self.find_text : self._do_find( self.FIND_PRIOR ) else : self.find_action() # The actual search, factored out of the above actions. Because the # search wraps around, if it fails, it fails, and that's that. def _do_find( self, find_flags ) : if not self.view.page().findText( self.find_text, find_flags ): utilities.beep()