Connecting to a signal hidden by QMessageBox - qt

I want to show the user a warning QMessageBox with a link inside. This is relatively easy, I just need to make sure I set the RichText text format on the message box and the QMessageBox setup does the rest. However, I would also like to close the message box (as in some-sort-of-call-to done()) if the user clicks on the link - the semantic being that the user acknowledged the message and made a decision.
The problem: QMessageBox hides the linkActivated signal coming from its inner QLabel (which is used to store the text).
I thought I could extend the QMessageBox class and do this very ugly hack in the constructor:
QLabel *lbl = findChild<QLabel*>(QString("qt_msgbox_label"));
assert(lbl != NULL);
connect(lbl, SIGNAL(linkActivated(const QString&)), this, SLOT(handle_link_activation(const QString&)));
but although the label found with findChild is not null, and the "qt_msgbox_label" is definitely correct (c/p'ed from the source), and there is no "no such signal/slot" message, my slot never gets called when I click the link.
I'd like to avoid writing my own QDialog which would mimic the QMessageBox behavior. Does anyone have any idea on how I can catch that signal?

Try defining your own link "protocol" i.e. msgboxurl://yoururl.is.here and install url handler for it
QDesktopServices::setUrlHandler("msgboxurl", urlHandlerObj, "slotName");
urlHandlerObj may be object that created message box. In slot you can just hide your message box and take url part after // and open it with QDesktopServices::openUrl but remember that you have to prepend then http/https prefix (on some platforms url without "scheme" is not handled properly). Slot handling url must have same parameters as QDesktopServices::openUrl static method

Related

Qt shortcut for custom context menu

I have been reading though a couple of examples and post but I just cannot figure out how to add a shortcut to my custom context menu. My GUI has several elements. One of them is a treeView. For the elements in my treeView I would like to have a custom context menu.
My first approach was according to this tutorial here. The context menu itself worked but the shortcuts cannot work if you create the actin within the show function.
So my second approach was according to this tutorial. But still my shortcuts do not work and if I use the context menu all actions are called twice...
Since I did not find a tutorial or code example, which matches my case, I hope that someone here can explain to me how this is correctly done in theory. Adding a shortcut to an action for a custom context menu.
Where do I have to declare my action?
What needs to be the parent of the action?
On which widget do I need to call addAction?
Thanks for any hints.
Another way is to add your action also to the parent widget (or main window widget). As mentioned in this reply, adding the same action to multiple widgets is fine and it's the way QActions are supposed to be used.
Example with custom HtmlBrowser class deriving from QTextBrowser:
Ctrl+U shortcut works for this code:
HtmlBrowser::HtmlBrowser(QWidget * parent) : QTextBrowser(parent)
{
viewSourceAct = new QAction(tr("View/hide HTML so&urce"), this);
viewSourceAct->setShortcut(tr("Ctrl+U"));
viewSourceAct->setCheckable(true);
parent->addAction(viewSourceAct);
connect(viewSourceAct, &QAction::triggered, this, &HtmlBrowser::viewSourceToggle);
}
and Ctrl+U shortcut does not work with this code (same as above, but without parent->AddAction(...)):
HtmlBrowser::HtmlBrowser(QWidget * parent) : QTextBrowser(parent)
{
viewSourceAct = new QAction(tr("View/hide HTML so&urce"), this);
viewSourceAct->setShortcut(tr("Ctrl+U"));
viewSourceAct->setCheckable(true);
connect(viewSourceAct, &QAction::triggered, this, &HtmlBrowser::viewSourceToggle);
}
Curiously, parent in this case is another widget (tab widget), not MainWindow. Still, adding parent->addAction() helps. And, unlike your suggested answer, it works even when connecting action to simple methods, without slots. Works for me in Qt 5.15.0. Not quite sure why it works, though. Perhaps, the widget the action is added to must be permanent for shortcuts to work? Looks like a bug in Qt.
Thanks to Scheff's hint I got it working. I do not now if this is really the correct way but this works for me.
The action needs to be declared in the constructor of your GUI class (e.g. MainWindow):
actionDel = new QAction(tr("delete"), this);
actionDel->setShortcut(QKeySequence(Qt::Key_Delete));
connect(actionDel, SIGNAL(triggered()), this, SLOT(actionDel_triggered()));
The triggered signal needs to be connected to a slot. Hint: if you create the slot do not use on_ACTIONNAME_triggered, this will interfere with the designer and cause a connection error.
Next add the action to a custom menu
fileContextMenu = new QMenu(this);
fileContextMenu->addAction(actionDel);
And to the widget
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showDirContextMenu(QPoint)));
ui->treeView->addAction(actionDel);
All in the constructor of your GUI class.
To show the context menu use the following code in slot used in the above connect:
QModelIndex index=ui->treeView->indexAt(pos);
// Here you can modify the menu e.g. disabling certain actions
QAction* selectedItem = fileContextMenu->exec(ui->treeView->viewport()->mapToGlobal(pos));
If you do not have a slot for an action, the action can be also handled in the context menu slot, but this does not work with shortcuts!
if(selectedItem == actionOpen){
on_treeView_doubleClicked(index);
}

Qt : Tooltip on wrong input

I have a QLineEdit, on which I have set a QRegExpValidator, that allows the user to input only one whitespace between words.
Now I want that whenever the user tries to enter more than one whitespaces, the tooltip of the QLineEdit should show up, but I'm not getting any method to implement it.
Thanx :)
It seems there is no direct method to perform what you want. One way to do above is to handle QLineEdit's textChanged() signal. Then you can check that string against your regular expression using QRegExp::exactMatch() function and if it don't match then show tooltip.
Connect the signal..
...
connect(ui->lineEdit,SIGNAL(textChanged(QString)),this,SLOT(onTextChanged(QString)));
...
Here your slot goes..
void MainWindow::onTextChanged(QString text)
{
QRegExp regExp;
regExp.setPattern("[^0-9]*"); // For example I have taken simpler regex..
if(regExp.exactMatch(text))
{
m_correctText = text; // Correct text so far..
QToolTip::hideText();
}
else
{
QPoint point = QPoint(geometry().left() + ui->lineEdit->geometry().left(),
geometry().top() + ui->lineEdit->geometry().bottom());
ui->lineEdit->setText(m_correctText); // Reset previous text..
QToolTip::showText(point,"Cannot enter number..");
}
}
I don't remember explicit API for showing a tooltip. I'm afraid you'll have to go with popping up a custom tool window (i.e. a parent-less QWidget) to achieve the desired result.
If you want to style your own popup window like a standard tooltip, QStyle should have something for that. If in doubt, read into the Qt source code where it renders the tooltip. That'll tell you which style elements to use.

cannot connect to parents slot

I thought I was beginning to understand this but it seems not.
I am trying to connect together a stacked widget so that the children can send a message to the parent to tell it to change the view. The QStackedWidget is a child of the main window and its pages are promoted fomrs that I created myself. I think that's the right way to do it?
So within one of the children forms I tried the following:
// Set up a mapping between the buttons and the pages
QSignalMapper *mapper = new QSignalMapper(this);
mapper->setMapping(ui->automatedButton, 1); // Value of the index
connect(ui->automatedButton, SIGNAL(clicked()), mapper, SLOT(map()));
connect(mapper, SIGNAL(mapped(int)), ((QStackedWidget*)parentWidget()), SLOT(setCurrentIndex(int)));
But that doesn't seem to work. I click the button and nothing changes. However if i do this:
void MySelectionForm::on_automatedButton_clicked()
{
((QStackedWidget*)parentWidget())->setCurrentIndex(1);
}
Which I thought was the same as what I was doing before?
I do see on message when running which I guess is associated:
QObject::connect: Cannot connect QSignalMapper::mapped(int) to (null)::setCurrentIndex(int)
Have you checked that parentItem() is not returning NULL when you call QObject::connect()? It sounds like maybe the parent item is being set after the constructor, which would mean that parentItem() would indeed return NULL. I suspect that might be how Qt Designer rolls. If so, you would need to connect the signal/slot outside of the constructor, either in another method (one that is not called inside the constructor), or outside of the child altogether.
According to the message,
((QStackedWidget*)parentWidget()), SLOT(setCurrentIndex(int)));
leads to
(null)::setCurrentIndex(int)
which means parentWidget() is NULL.
This basically means that the forms are not a child of the QStackedWidget.
If you create your forms manually, you have to do something like this
MyForm *form = new MyForm( ui->stackedWidget );

How to prevent an MFC dialog from handling the enter and escape keys and not passing it on

we have a C++ application that hosts a flex application in a MFC dialog. Everything works fine, all button pushes etc are passed directly on to flex with no problem at all, except the enter and escape keys, which immediately closes the dialog.
We can trap the enter key, by implementing PreTranslateMessage() or OnOK()
and impede the window closing behavior, but we still have the problem of passing these enter key pushes on to the hosted flex app further.
There is no "default" button on the form or anything like that, perhaps MFC is linking the enter key to the close button in the title bar behind the scenes or something.
Does anyone have any ideas how we can stop MFC from treating the enter key from being a special case.
Thanks a lot for any hints.
Edit: Here is PreTranslateMessage() that mmonem requested.
BOOL CFlexDialog::PreTranslateMessage(MSG* pMsg)
{
if ((pMsg->message == WM_KEYDOWN))
{
if (pMsg->wParam == VK_RETURN)
{
m_ctrlFlex->OnReturnKeyPressed();
return TRUE;
}
}
return __super::PreTranslateMessage(pMsg);
}
But it is not a suitable solution, calling a method in the flex app like that, as it makes life too difficult for the flex developer, it means he must write a special version implementing the return key behavior for every control.
We just want MFC to treat the return and escape keys like every other key.
Remove OnOK() and OnCancel(); PreTransateMessage is enough after considering VK_ESCAPE.
Why don't you use:
m_ctrlFlex->SendMessage(WM_KEYDOWN, VK_RETURN, 0)
instead of
m_ctrlFlex->OnReturnKeyPressed();
in your implementation of PreTranslateMessage ?
MFC command buttons can respond to events even if they do not have the focus.
Have you tried trapping the OnClicked event and OnOk to return nothing?
Example: trap OnClick...
void CMyDialog::OnClickedMyOK()
{
CDialog::OnOK();
}
Then do a no-op in the OnOk()
void CMyDialog::OnOK()
{
}
This should stop the enter key from being processed.
Another approach is "Windows Subclassing." That is, sending messages from one Window Procedure, that is a WindProc() to another WndProc(). This may provide a way without direct intervention. (This is not C++ subclassing.)
Here's a way with MFC Subclassing Edit: Provided a better link.
Search for "Windows / MFC Subclassing" if more info needed.
The flex control/window has a WndProc and your window certainly has a WndProc, Windows Subclassing should work.
New Edit: Here is perhaps a better link for subclassing an ActiveX control.
ActiveX Controls: Subclassing a Windows Control
Subclassing Windows Forms Controls - More .Net-centric.
Notice: In MFC you should see these functions.
CWnd::SubclassDlgItem
CWnd::SubclassWindow
CDialog inherits from CWnd so you will see those two functions as "dialog" functions as well. They are key to making this work. If the flash window is a true window, use SubclassWindow. If it's a control use SubclassDlgItem.
And finally, if nothing else works. A product/library that will hopefully make it easy. EasyHook looks like a product here. EasyHook at CodeProject, you can get all the source code.
If you are having issues handling tabs & enter keys I would recommend you look into using a window instead of a dialog. The dialog adds modal ( if you are using modal ), tabbing & default button handling. If you don't need/want those features, or if they are getting in your way, then don't use a dialog.
If I understand what you are doing, then you want flex to handle tabbing, enter key, and all sorts of other messages. Get the dialog code out of the way. If you still want modal style, then you may have to handle the enable/disabling of parent windows - thats all that windows does when you open a modal dialog.

Qt-Right click mouseReleaseEvents aren't caught by eventfilter,other events though are caught

My application consists of a WebView widget. A mouse click on the widget is not handled by the mousePressEvent() of my application, but by the WebView widget. So, I installed an event filter to receive the events. Now, I get notified of all events, except the mouseReleaseEvent for the right click (Everything works fine for left clicks and mousePressEvent for the right click is also getting registered). I guess it has got something to do with context events getting generated by right clicks (a pop-up menu gets generated). But since I am using a filter, the event should first be sent to me. The following is the code for the event filter
in Jambi, but I hope I can modify an answer given in Qt for Jambi.
public boolean eventFilter(QObject o,QEvent event)
{
if (event.type()==QEvent.Type.MouseButtonPress) // One can call the mousePressEvent() functions from here,which can do this work but speed
{
if (((QMouseEvent)event).button()==Qt.MouseButton.LeftButton)
{
mousebuttontype=1;
clickedorpressed=1;
}
else
if (((QMouseEvent)event).button()==Qt.MouseButton.RightButton)
{
mousebuttontype=2;
System.out.println("right");
}
t1=QTime.currentTime();
t1.start();
}
else
if (event.type()==QEvent.Type.MouseButtonRelease)
{
if (t1.elapsed()>900)
{
switch(mousebuttontype)
{
case 1: browser.back();
break;
case 2: browser.forward();
break;
}
}System.out.println("choda");
}
return false;
}
MY BASIC AIM IS GETTING THE TIME INTERVAL FOR WHICH THE RIGHT CLICK WAS PRESSED. ANY WORKAROUND ?
Some digging around certainly seems to suggest that there may be no right-mouse release event being generated if that is the trigger for a context menu on your particular system. These are conveyed as a QContextMenuEvent.
This Qt Labs post hints about this too.
A work around may be tricky if it is system dependent. Have you tried overriding event() in the QApplication class to see if the event comes through there? Some events don't necessarily get to the children but everything goes through QApplication::event().
Another option for you is, subclass Qwebview and override mouseReleaseEvent event
I've found a fix for this that I dont think will be system dependent.
My particular case was using mouseReleaseEvent in order to catch the events, and use them myself. I didn't get these events.
On all child widgets of the widget that I want to handle the event, I added to the class definition:
protected:
void mouseReleaseEvent(QMouseEvent *event) {event->ignore();}
This overrides the default implementation for context menu's, and sends the mouseReleaseEvent back up the parent chain like it would for other mousebuttons.
http://doc.qt.io/qt-5/qevent.html#ignore
This shows that it will probably propagate to the parent widget. As the link indicates, this fixed it for me at Qt 5.9, but I think it should work for virtually all version.
(I am aware this question is literally 7 years old, but it doesn't contain the fix that I think would be the best fix, and shows up as result 2 on google(qt not getting mouse release event on rightclick). So I think it deserves an up to date answer.)
Without going in to broader implementation explanations, the core problem I needed to solve related to this issue was that I needed to know if a context menu swallowed the right-click so I could ensure some custom state was handled properly. The simplest workaround I was able to find was to implement contextMenuEvent, which gets called after mousePressEvent, to detect if the event was accepted (a context menu was opened somewhere in my QGraphicsScene/Items). Here is a minimal example in Python to demonstrate how the event state might be used:
def contextMenuEvent(self, event):
event.setAccepted(False)
super(MyGraphicsView, self).contextMenuEvent(event)
self.__context_menu_used = event.isAccepted()
Note you can also skip running the the base class contextMenuEvent entirely to block the scene/items from opening a context menu. This is handy if you want to do 3D-DCC-like alt-RMB-zooming without accidentally opening a context menu, e.g.:
def contextMenuEvent(self, event):
# Block context menus if alt is held down
if event.modifiers() & QtCore.Qt.AltModifier:
return
event.setAccepted(False)
super(MyGraphicsView, self).contextMenuEvent(event)
self.__context_menu_used = event.isAccepted()

Resources