QWebView/QWebPage need help with context menu
-
(Edit: I changed the title because it is clear that the context menu is the right approach but I can't see how to make it work.)
Hello. I have a simple browser based on QWebView/QWebPage. I would like my user to be able to ask for special treatment by right-clicking (control-clicking) a link. Or maybe shift-clicking, anyway a special click to request special linkage.
When the link is clicked normally, the WebPage should handle the link in the usual way. I want to offer special handling only when the link is differently-clicked (specifically, I would open the link in the system default browser instead of mine).
I can see two approaches: -one, set the link delegation policy to delegate all links, this gives me a signal linkClicked(URL). However I do not see, in the handler for this signal, any way to distinguish the type of click that caused the signal.-
Or, I could override createStandardContextMenu() and provide a menu with my action in it. This would be called on any right/control-click, but then: how do I know what link the mouse is on, or indeed, was it clicked on a link at all?
Any suggestions or different ideas most welcome.
-
A little more experimenting and I find the context menu is probably the right choice. QWebView knows whether the user has right-clicked a URL or just text! It uses different default context menus.
If I right-click on selected text (not a url) the context menu has only one choice, Copy. If I right-click on unselected text, the word under the cursor is selected(!) and then the menu is Copy.
But when I right-click on a link, I get a context menu with Open Link, Open in New Window, Save Link, Copy Link. That's the menu I need to customize...
But HOW does it know? If I want to provide a custom context menu only when the context menu is invoked over a link, how do I find out? The only input to contextMenuEvent is the reason (mouse or keyboard) and the position in the viewport.
From that, how do I figure out whether the event involves a link, and if so, the contents of the link?
-
The hint of an answer, from stackoverflow: get an action that assumes a link, and if it is enabled, there's a link. For example (this is python, inside a QWebView object)
@
linkact = self.page().action(QWebPage.OpenLink)
if linkact.isEnabled() :
# it would appear there's a link involved...
@
Next question: how does one access the URL of that link??? -
OK, I am going to answer my own question. This can be marked [Solved].
The following code is in Python and PyQt4; PySide should be the same; translation to C++ should be straightforward.
To create a custom context menu in a QWebView all you need to do is re-implement contextMenuEvent. Your method will get control whenever a context menu is requested (typically by a right-click or (mac) control-click).
You receive a contextMenuEvent object which contains the reason for the call (mouse, keyboard or other) and the global and relative point positions of the event.
@
def contextMenuEvent (self, cx_event) :
why = cx_event.reason()
rel_pos = cx_event.pos()
@In my case I did not care the reason, but I did want to know whether the user had clicked on a link or not. In order to find out you must test the context of the event. You call your QWebPage to get the web frame of the click, and you ask it for the hit context based on the relative position:
@
main_frame = self.page().mainFrame()
hit_test = main_frame.hitTestContent(rel_pos)
@What is returned is a QWebHitTestResult object and this can be queried for various things. I wanted to know if it represented a link, and if it did not, I wanted to just pass control to the original context menu handler and exit:
@
hit_url = hit_test.linkUrl()
if hit_url.isEmpty() :
super(myWebPage, self).contextMenuEvent(cx_event)
return
@When the event involved a link I need to supply the custom context menu. To do this, one creates a QMenu object and populates it with QActions, connecting the 'triggered()' signal of each action to an appropriate handler method. One can call self.pageAction(action-name) to get prepared QActions for many different web-related functions listed in the QWebPage::WebAction enum. This makes it easy to populate the QMenu with standard actions. However I did not do this. For demonstration purposes, here is the rest of my code:
@
# save the string form of the clicked URL as python string
self.contextUrl = unicode(hit_url.toString())
# Create the custom two-action menu:
ctx_menu = QMenu()
# Action one is, copy link to clipboard
ctx_copy = QAction(QString(u'Copy link to clipboard'),self)
self.connect(ctx_copy, SIGNAL("triggered()"), self.copyLinkToClipboard)
ctx_menu.addAction(ctx_copy)
# Action two is, open link in default browser
ctx_open = QAction(QString(u'Open in default browser'),self)
self.connect(ctx_open, SIGNAL("triggered()"), self.openInDefaultBrowser)
ctx_menu.addAction(ctx_open)
# Finally show the menu.
ctx_menu.exec_(cx_event.globalPos())
@
That completes the contextMenuEvent method. The two slots connected from the menu actions are:
@
def copyLinkToClipboard(self) :
QApplication.clipboard().setText(self.contextUrl)
def openInDefaultBrowser(self) :
webbrowser.open_new(self.contextUrl)
@
I coded the copy method this way rather than using pageAction(QWebPage::CopyLinkToClipboard) because the latter action seemed to use an internal clipboard: I could paste the copied link within my app, but not in another app. Coded as above, it goes to the system clipboard. The webbrowser.open_new call is a Python library module that finds the system browser in a platform-independent way.