Qt custom context menu - qt

Am creating a Qt Application which consists of a tree view and a webview. when a item from a tree view is clicked it should load the corresponding url. It Works Fine. when am right clicking on the item a custom context menu will appear n it will open it in a new webview. This is also working. But my problem is when am Right clicking on the treeview item my context menu comes and if am clicking it outside the pop up menu the url of that item gets loaded. how to solve this.. Help me friends..
Here's my coding:
QStandardItem *rootItem = new QStandardItem("Google");
QStandardItem *stackItem = new QStandardItem("Stack Overflow");
QStandardItem *yahooItem = new QStandardItem("Yahoo");
rootItem->appendRow(stackItem);
standardModel->appendRow(rootItem);
standardModel->appendRow(yahooItem);
***// private slot for loading the url if a treeview item is clicked:***
void MainWindow::treeViewClicked(const QModelIndex &index)
{
str = index.data().toString();
if(!(str.isEmpty()) && str=="Google")
{
url = "http://www.google.com";
}
else if (!(str.isEmpty()) && str == "stack Overflow")
{
url = "http://www.stackoverflow.com";
}
else if (!(str.isEmpty()) && str == "Yahoo")
{
url = "http://www.yahoo.com";
}
WebView *wv = dynamic_cast<WebView *>(ui->tabWidget->currentWidget());
wv->load(QUrl(url));
ui->tabWidget->setTabText(ui->tabWidget->currentIndex(),str);
treeView->setModel(standardModel);
**//Creating custom context menu for QtreeView:**
void MainWindow::showContextMenu(const QPoint& point)
{
QList<QAction *> actions;
if(treeView->indexAt(point).isValid())
{
actions.append(m_treeViewAction);
}
else if(actions.count() > 0)
{
QMenu::exec(actions, MainWindow::treeView->mapToGlobal(point));
QModelIndex index = treeView->indexAt(point);
QStandardItem *item = standardModel->itemFromIndex(index);
treeView->setCurrentIndex(index);
treeViewClicked(index);
}
}

For what I know, the situation you describe is standard with context menus in views: When you right click, the item is also selected.
If you want another behaviour, you must implement the mousePressEvent and implement the behaviour you want to achieve.
Here is a hint:
void MyTreeView::mousePressEvent ( QMouseEvent * event )
{
if (event->button() == Qt::LeftButton) {
// set the current item based on event->pos() / deselect if no item
}
else if (event->button() == Qt::RightButton) {
// show context menu for the item / different context menu if no item
}
}
Yes, you must derive the QTreeView class and make one of your own.
I've done this long time ago, and I remember this as the starting point. I don't remember now if I had to reimplement all four basic mouse events: press, release, move and doubleclick, as they are internally related.

Related

Adding a right-click menu for specific items in QTreeView

I'm writing a Qt desktop application in c++ with Qt Creator.
I declared in my main window a treeView, and a compatible model.
Now, I would like to have a right-click menu for the tree item. Not for all of the items, but for a part of them, for example: for the tree elements with an even index.
I tried adding a simple context menu with the following code:
in the .h file:
QStandardItemModel* model;
QMenu* contextMenu;
QAction* uninstallAction;
private slots:
void uninstallAppletClickedSlot();
and in the .cpp file:
in the constructor:
ui->treeView->setModel(model);
contextMenu = new QMenu(ui->treeView);
ui->treeView->setContextMenuPolicy(Qt::ActionsContextMenu);
uninstallAction = new QAction("Uninstall TA",contextMenu);
ui->treeView->addAction(uninstallAction);
connect(uninstallAction, SIGNAL(triggered()), this, SLOT(uninstallAppletClickedSlot()));
and a slot:
void MainWindow::uninstallAppletClickedSlot()
{
}
this code gives me a context menu with the wanted action, but do you have any idea how can I add this action only for the QStandardItems with the even indexes??
BTW, I'm adding items to the treeView by the following way:
void MainWindow::AddItem(QString name)
{
QStandardItem *parentItem = model->invisibleRootItem();
QStandardItem *app = new QStandardItem(name);
parentItem->appendRow(app);
}
I googled a lot, but found nothing :(
thanks in advance!
I would do this in the following way:
Configure the context menu
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
Implement the context menu handling
void MainWindow::onCustomContextMenu(const QPoint &point)
{
QModelIndex index = ui->treeView->indexAt(point);
if (index.isValid() && index.row() % 2 == 0) {
contextMenu->exec(ui->treeView->viewport()->mapToGlobal(point));
}
}

How to ensure, the entire area in QScrollArea was displayed?

I'm displaying some information to the user in QScrollArea.
The user should have seen all contents, before she can proceed (at least the content should have been scrolled to the end)
How could I detect this in an easily?
Is the reimplementing of virtual void scrollContentsBy (int dx,int dy) the only way?
EDIT
I was able to solve it, but had to use some workarounds:
Scroll-action value sent by the signal actionTriggered(int) had never the value QAbstractSlider::SliderToMaximum (Qt4.8, Windows 7). So I've checked manually, if the slider value is close to maximum.
Even if scroll-bar widget was dragged by mouse till the bottom, the value of the scroll-bar is never the maximum. Only if the scroll-bar widget is moved by any other event such as arrow-down or mouse wheel, the value may become maximum. I've work-arounded it with recheckPosition()
I hope, there are better solutions.
void NegativeConfirmation::recheckPosition()
{
processScrollAction(1);
}
void NegativeConfirmation::processScrollAction( int evt)
{
if ( evt == QAbstractSlider::SliderToMaximum) // Have not managed to receive this action
{
ui->bConfirm->setEnabled(true);
}
//Another approach
QWidget * sw = ui->scrollArea->widget();
if ( sw ) //any content at all ?
{
QScrollBar * sb = ui->scrollArea->verticalScrollBar();
if ( sb )
{
int sbv = sb->value();
int sbm = sb->maximum()-10;
if ( sbm>0 && sbv >= sbm )
{
ui->bConfirm->setEnabled(true);
}
else
{
QTimer::singleShot(1000, this, SLOT(recheckPosition()));
}
}
}
}
QScrollArea inherits QAbstractSlider which provides this signal: -
void QAbstractSlider::actionTriggered(int action)
Where action can be QAbstractSlider::SliderToMaximum.
I expect you can connect to the this signal and test when the action is QAbstractSlider::SliderToMaximum, representing that the user has scrolled to the bottom.

How to make a Qt dialog read-only?

How to make a QT dialog read-only? Any general way to implement it easily? For example
(1) set all its containing widgets disable. (how to implement it?)
(2) Intercept edit events like key pressed, mouse pressed but how not to intercept the one to close the dialog?
I think this feature should be very helpful.
Disabling the widgets can be done similar to the following:
void myDialog::disableWidgets()
{
QList<QWidget *> widgets = this->findChildren<QWidget *>();
foreach(QWidget* widget, widgets)
{
widget->setEnabled(false);
}
}
To intercept events, QDialog includes the function installEventFilter(QObject*).
This allows you to use a separate object to receive all events passed to the dialog. You can then choose to handle the event in the object, or pass it on to the dialog itself by calling the base class QObject::eventFilter
class MyEventHandler : public QObject
{
Q_OBJECT
protected:
bool MyEventHandler::eventFilter(QObject *obj, QEvent *event)
{
// handle key press events
if (event->type() == QEvent::KeyPress)
{
// Do something
// ...
return true; // event handled by the class
}
else
{ // ignore this event and pass it to the dialog as usual
return QObject::eventFilter(obj, event);
}
}
return false;
};
QDialog* dlg = new QDialog;
MyEventHandler evtHandler = new MyEventHandler;
dlg->installEventFilter(evtHandler);
Read-only is a strange term to apply to a dialog. Disabling all widgets as above does the trick. If you only wanted to make the input part of a QInputDialog read-only (while leaving scrollbars, buttons, etc. enabled), you could adapt that code as below:
QInputDialog dialog(this);
dialog.setOptions(QInputDialog::UsePlainTextEditForTextInput);
dialog.setWindowTitle("Title");
dialog.setLabelText("Label");
dialog.setTextValue("1\n2\n3\n");
QList<QWidget *> widgets = dialog.findChildren<QWidget *>();
foreach(QWidget* widget, widgets) {
if (strcmp(widget->metaObject()->className(),"QPlainTextEdit")==0) {
QPlainTextEdit *t = static_cast<QPlainTextEdit*>(widget);
t->setReadOnly(true);
}
}
dialog.exec();

How to let QComboBox have context menu?

I have a Qt combo box. When it pops, items are listed down. When right clicking an item, I hope a context menu to pop up. Any way to implement it? I find a function onContextMenuEvent under QComboBox. Does it help? Thanks.
You can obtain the list widget using QComboBox::view. You can add a context menu to the list as usual. But also you should install event filter on the view's viewport and block right click events because such events cause popup list to close.
In the initialization:
QAbstractItemView* view = ui->comboBox->view();
view->viewport()->installEventFilter(this);
view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(view, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(list_context_menu(QPoint)));
Event filter:
bool MainWindow::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::MouseButtonRelease) {
if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton) {
return true;
}
}
return false;
}
In the slot:
void MainWindow::list_context_menu(QPoint pos) {
QAbstractItemView* view = ui->comboBox->view();
QModelIndex index = view->indexAt(pos);
if (!index.isValid()) { return; }
QMenu menu;
QString item = ui->comboBox->model()->data(index, Qt::DisplayRole).toString();
menu.addAction(QString("test menu for item: %1").arg(item));
menu.exec(view->mapToGlobal(pos));
}
In this example items are identified by their displayed texts. But you also can attach additional data to items using QComboBox::setItemData. You can retrieve this data using ui->comboBox->model()->data(...) with the role that was used in setItemData.

Alternative to QButtonGroup that allows no selection?

I'm writing a qt-based c++ application. I have a number of buttons that I want to be mutually exclusive - only one can be toggled at a time. I generally use a QButtonGroup for this - it provides a nice logical way to manage sets of buttons. When one gets pressed, the previously-pressed one gets unpressed, which is exactly the behavior I want.
This time, however, I'd like to allow for the group to be entirely unchecked. Unfortunately this seems to be disallowed by QButtonGroup:
exclusive : bool
This property holds whether the button group is exclusive.
If this property is true then only one button in the group can be
checked at any given time. The user can click on any button to check
it, and that button will replace the existing one as the checked
button in the group.
In an exclusive group, the user cannot uncheck the currently checked
button by clicking on it; instead, another button in the group must be
clicked to set the new checked button for that group.
There are a number of ways to work around this, of course. I'm wondering if there's a pre-made alternative to QButtonGroup that allows this behavior, so that 1) I'm not reinventing the wheel and 2) I can stay within idiomatic qt to make project management easier in the future.
Any suggestions?
In Qt5, I use a similar solution as Laurent Michel's, but using the release event instead of the press event:
// Allow to uncheck button in exclusive group
void CustomButton::mouseReleaseEvent(QMouseEvent* a_Event) {
if(group()->checkedId()==group()->id(this)) {
if(isDown()) group()->setExclusive(false);
}
QToolButton::mouseReleaseEvent(a_Event);
group()->setExclusive(true);
}
For the sake of completeness, I would like to publish here one possible solution to the problem, as I just solved it in my case. Just beware that the following code is valid for Qt3. It may as well work for Qt4 and Qt5, because it doesn't use a lot of stuff.
So, I assume that I have a widget CustomWidget somewhere that contains buttons (of type CustomButton) and that one and only one button can be switched on. If one clicks another button in the widget, then the currently switched on button is switched off and the newly clicked button is switched on.
The CustomButtons contained in the CustomWidget are all contained in a QButtonGroup in the following way:
QButtonGroup* m_ButtonGroup = new QButtonGroup(this);
m_ButtonGroup->hide();
m_ButtonGroup->insert(Btn1);
m_ButtonGroup->insert(Btn2);
m_ButtonGroup->insert(Btn3);
m_ButtonGroup->setExclusive(true);
Here, Btn1, Btn2, and Btn3 are of type CustomButton
class CustomButton : public QToolButton
{
Q_OBJECT
public:
CustomButton (QWidget* apo_parent = 0, const char* as_name = 0);
virtual ~CustomButton ();
protected:
virtual void mousePressEvent(QMouseEvent* a_Event);
};
The method you want to implement specially is mousePressEvent. If its body is implemented in the following way:
void CustomButton ::mousePressEvent(QMouseEvent* a_Event)
{
if(group() && isToggleButton())
{
CustomButton* selectedButton(dynamic_cast<CustomButton*>(group()->selected()));
if(selectedButton)
{
if(selectedButton->name() == name())
{
group()->setExclusive(false);
toggle();
group()->setExclusive(true);
return;
}
}
}
QToolButton::mousePressEvent(a_Event);
}
then the widget behaves as you want.
Another similar solution as the previous answers, but using nextCheckState() which appears to be the more natural extension point to me:
void MyButton::setSemiExclusive(bool value)
{
mSemiExclusive = value;
}
void MyButton::nextCheckState()
{
if (mSemiExclusive)
{
if (auto g = group())
{
auto old = g->exclusive();
if (g->checkedButton() != this)
g->setExclusive(true);
QAbstractButton::nextCheckState();
g->setExclusive(old);
return;
}
}
QAbstractButton::nextCheckState();
}
This depends on the associated QButtonGroup having set exclusive to false.
If you don't want to extend the button class, you can also accomplish this by using signals (Qt5, Python):
from PySide import QtGui
class View(QtGui.QWidget):
def __init__(self):
self.buttonGroup = QtGui.QButtonGroup(self)
for button in buttons:
self.buttonGroup.addButton(button)
button.pressed.connect(buttonPressed)
button.released.connect(buttonReleased)
def buttonPressed(self):
button = self.sender()
checkedButton = self.buttonGroup.checkedButton()
if checkedButton != None and checkedButton.objectName() == button.objectName():
self.buttonGroup.setExclusive(False)
def buttonReleased(self):
button = self.sender()
if self.buttonGroup.exclusive() == False:
button.setChecked(False)
self.buttonGroup.setExclusive(True)
def manualDeselection:
self.buttonGroup.setExclusive(False)
self.buttonGroup.checkedButton().setChecked(False)
self.buttonGroup.setExclusive(True)
My solution is to derive the QButtonGroup, set it to non exclusive internaly and manage the states by yourself
class myQButtonGroup : public QButtonGroup
{
Q_OBJECT
public:
explicit myQButtonGroup(QObject *parent = Q_NULLPTR) : QButtonGroup(parent) {
_bExclusive = true;
QButtonGroup::setExclusive(false);
connect(this, SIGNAL(buttonClicked(QAbstractButton *)), SLOT(buttonClicked(QAbstractButton *)));
}
void setExclusive(bool bExclusive) { _bExclusive = bExclusive; }
bool exclusive() const { return _bExclusive; }
protected slots:
void buttonClicked(QAbstractButton *button) {
if (_bExclusive) {
// just uncheck all other buttons regardless of the state of the clicked button
QList<QAbstractButton *> buttonlist = buttons();
for (auto iBtn = buttonlist.begin(); iBtn != buttonlist.end(); ++iBtn) {
QAbstractButton *pBtn = *iBtn;
if (pBtn && pBtn != button && pBtn->isCheckable()) pBtn->setChecked(false);
}
}
}
protected:
bool _bExclusive;
};

Resources