Disable auto-selecting items in QListWidget on click+drag - c++

I've spent better half of today trying to resolve seemingly trivial QListWidget behavior customization: when used presses mouse left button and moves mouse cursor, the content of ListWidget is scrolled and selection is moved to another item that happens to appear under mouse cursor. I am alright with scrolling, but I want to avoid selecting all consequtive items because this causes timely operation in my program. Finally I would like to keep list content scrolling on mouse press and move, but select items only by clicking directly on them.
Drag-n-drop is disabled for this list (which is default behavior) and it should be; I've tried to disable it explicitly: no changes whatsoever.
I have read all available docs on Qt related classes like QListWidget, QListWidgetItem, QListView, you name it! Tried to make sense of source code for these widgets; dug up StackOverflow and Google... but sadly no result :(
Here is all relevant code for my QListWidget: single selection, nothing fancy:
QListWidget* categoryListWidget;
...
categoryListWidget = new QListWidget();
categoryListWidget->move(offsetX, offsetY);
categoryListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
categoryListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
categoryListWidget->setFocusPolicy(Qt::NoFocus);
categoryListWidget->setStyleSheet(listQSS);
...
categoryListWidget->clear();
new QListWidgetItem(tr("1 - Sample Category 1"), categoryListWidget);
new QListWidgetItem(tr("2 - Sample Category 2"), categoryListWidget);
new QListWidgetItem(tr("3 - Sample Category 3 with a very long name"), categoryListWidget);
new QListWidgetItem(tr("4 - Sample Category 4"), categoryListWidget);
C++/Qt 5.5 if that's somehow relevant, both Win and Mac platforms share similar behavior.

For the sake of whoever stumbles upon the same question, here is my solution: subclass QListWidget and make child class ignore mouseMove events when leftButton is pressed.
Header:
class QtListWidget: public QListWidget
{ // QListWidget clone that suppresses item selection on mouse click+drag
private:
bool mousePressed;
public:
QtListWidget():QListWidget(), mousePressed(false) {}
protected:
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
};
Source:
//////////////////////////////////////////////////////////////////////////
void QtListWidget::mousePressEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mousePressEvent";
if(event->button() == Qt::LeftButton)
mousePressed = true;
QListWidget::mousePressEvent(event);
}
void QtListWidget::mouseMoveEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseMoveEvent";
if(!mousePressed) // disable click+drag
QListWidget::mouseMoveEvent(event);
}
void QtListWidget::mouseReleaseEvent(QMouseEvent *event){
// qDebug() << "QtListWidget::mouseReleaseEvent";
if(event->button() == Qt::LeftButton)
mousePressed = false;
QListWidget::mouseReleaseEvent(event);
}
//////////////////////////////////////////////////////////////////////////
Usage is trivial, for as many List Widgets as you need:
QtListWidget* categoryListWidget;
// all original code above would still work as expected
...
Want it done right? then do it yourself! :)

Your solution killed scrolling for me. I am using QListView. Here's another way:
In the constructor of the QListView's parent:
ui->listView->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->listView, SIGNAL(clicked(QModelIndex)), this, SLOT(on_listview_clicked(QModelIndex)));
In the connected slot:
on_listview_clicked(const QModelIndex &index)
{
if (index.isValid())
{
ui->listView->selectionModel->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
}
}
So, it only selects on a click.

Related

Mouse right click option using eventFilter in Qt

I have QGraphicsView, which has many QGraphicsItem. I am trying to create a right click menu on these QGraphicsItem. Right click menu has multiple options. But only 1st option works. It means, if I click on 2nd option, it does not work. If I change the sequence ( means 1st one will go to 2nd position, and 2nd one will come to 1st position ) then still 2nd one will not work.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
foreach(QGraphicsItem* pItem, _scene->items())
{
if(pItem->isUnderMouse())
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
myMenu = menu->addMenu("Copy");
myMenu ->addAction(Name);
myMenu ->addAction(Address);
if(Name == menu->exec(mouseEvent->globalPos()))
{
// logic
}
if(Address == menu->exec(mouseEvent->globalPos()))
{
// logic
}
}
}
}
}
Always works only 1st mouse right click option. Why is so ?
The usual way to do something like this is to override the QGraphicsItem::mouseReleaseEvent() or QGraphicsItem::mousePressEvent() function of your item class.
This way, you won't have to do anything (no looping, etc...), it is already handled by the event loop.
Here you can find a simple example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
my_menu.exec(event->globalPos());
}
}
From the Qt documentation:
Note that all signals are emitted as usual. If you connect a QAction to a slot and call the menu's exec(), you get the result both via the signal-slot connection and in the return value of exec().
You just need to QObject::connect() the QActions you added to the context menu to the proper slots (here goes the "logic") and the job is done.
If you prefer to check the returned value by yourself, you just have to get the returned QAction* once and for all (only one call to QMenu::exec()) and branch on it.
For example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
QAction * triggered = my_menu.exec(event->globalPos());
if(triggered == my_first_action)
{
// Do something
}
else if(triggered == my_second_action)
{
// Do some other thing
}
//...
}
}
I would personnally prefer to stick with the signal-slot connections instead that manually handling the returned value, especially since each QAction is most likely to be already connected to its corresponding slot.

slow response on right click contex menu in graphic scene Qt

I have set a large menu in event filter on right click with 45-50 actions
inside and I find that when I right click the response to show the menu is slow
I did try the same code with 5 actions in the menu and the response was fine.
Is there something wrong with this way of coding on a contex menu ?
eventFilter
bool Editor::eventFilter(QObject *o, QEvent *e)
{
Q_UNUSED (o);
QGraphicsSceneMouseEvent *me = (QGraphicsSceneMouseEvent*) e;
switch ((int) e->type()){
case QEvent::GraphicsSceneMousePress:{
switch ((int) me->button()){
case Qt::RightButton:{
QGraphicsItem *item = itemAt(me->scenePos());
showContextMenu(item->scenePos().toPoint());
return true;
}
//more cases here//
}
break;
}
}
return QObject::eventFilter(o, e);
}
showContextMenu
void Editor::showContextMenu(const QPoint &pos)
{
QGraphicsItem *item =itemAt(pos);
// Create main effe menu
effeMenu= new QMenu("Menu");
QString menuStyle(
"QMenu {"
"border:10px };"
//more code here
);
effeMenu->setStyleSheet(menuStyle);
AmpMenu=effeMenu->addMenu(QIcon(":/effectImg/img/effePng/amp.png"),"Amp");
Amp1 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 1");
Amp2 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 2");
CabMenu=effeMenu->addMenu(QIcon(":/effectImg/img/effePng/cab.png"),"Cab");
Cab1 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 1");
Cab2 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 2");
.
.
.
.
//45 actions more
connect(effeMenu, &QMenu::triggered,this,[this,&item](QAction * k){
menuSelection(k,item);
});
Instead of creating a new QMenu each time you call showContextMenu you could make it a member of the class and build it once. On the other hand it is not necessary to use a signal, you could simply use the exec() method of QMenu:
*.h
class Editor: ...{
...
private:
QMenu effeMenu;
}
*.cpp
Editor::Editor(...){
effeMenu.setTitle("Menu");
QString menuStyle(
"QMenu {"
"border:10px };"
//more code here
);
effeMenu.setStyleSheet(menuStyle);
AmpMenu=effeMenu.addMenu(QIcon(":/effectImg/img/effePng/amp.png"),"Amp");
Amp1 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 1");
Amp2 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 2");
CabMenu=effeMenu.addMenu(QIcon(":/effectImg/img/effePng/cab.png"),"Cab");
Cab1 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 1");
Cab2 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 2");
...
}
void Editor::showContextMenu(const QPoint &pos){
QGraphicsItem *item =itemAt(pos);
QAction *action = menu.exec(pos);
menuSelection(action, item);
}
There are two things you can do to improve speed:
1 - itemAt(pos) is costly, and you are doing it twice, one in the event, and one in the showContextMenu. From what I could understand from your code you don't need the item in the event, just in the showMenu.
2 - The menu creation that you are doing is expensive: all the actions have pixmaps. this allocs memory for the QPixmap, loads, execute, dumps. Because you told us that you use around 40 actions (and really, that's too much for a menu), this can get costly.
My advice:
Create a class for your menu, create one instance of it, add a setter for the current QGraphicsObject that your menu will work on, and always use that one instance.

How to make the text of a QComboBox bold, but not the list items?

We have a longstanding convention in our UI that items are shown in bold when they have been changed but the change is not yet committed. Strangely, until now we haven't used any combo boxes, but I have a use for one now and need to implement this behaviour. So I need to programmatically bold (and later un-bold) the text displayed by a closed combo box. However, I don't want to bold the entire list of items in the pop-up. I could accept bolding the selected item in the list if that's easier.
I've seen lots of answers doing almost this, but usually trying to modify the list items rather than the button. I've tried variations on most of them; unfortunately I didn't keep records of what I tried. For what it's worth, my code currently looks like:
myCombo->setStyleSheet(
"QComboBox {font-weight: bold;} "
"QComboBox QAbstractItemView::item {font-weight: normal;}"
);
This turns the button bold, but also the list items. The same behaviour is seen when I apply the normal weight just to the QAbstractItemView without the ::item, and when I tried a different technique based on :open and :closed on the QComboBox.
I will say I'm fairly new to Qt. I am using Qt5 on Fedora 26, but will be deploying to CentOS 7.
It seems that setting the font-style in QComboBox overrides the view's (and it shouldn't, IMHO).
But, when I tried to explicitly set a view to the combo box, this way:
view = new QListView();
myCombo->setView(view);
the stylesheet posted by the OP suddenly worked.
By the way, the new view is different from the original (e.g. has a white background, etc) and I guess the OP isn't happy with that. One could go on styling it, of course, but one would rather prefer a ready to use view, with a consistent style.
Inspecting the default QComboBox view:
QComboBox * combo = new QComboBox();
qDebug() << combo->view();
yelds this:
QComboBoxListView(0x2091880)
So, there is a specific QComboBoxListView class, which is nowhere to be found in documentation and is defined in qcombobox_p.h, not a file one could include, really, but at least we can understand where the issue come from, in the viewOptions overridden method:
QStyleOptionViewItem option = QListView::viewOptions();
option.showDecorationSelected = true;
if (combo)
option.font = combo->font(); // <--- here
return option;
That combo is a private pointer to QComboBox, initialized in construction:
QComboBoxListView(QComboBox *cmb = 0) : combo(cmb) {}
which will always override the view options font with its own.
Let's have a copy of the QComboBoxListView class, edited and renamed:
comboitemview.h
#ifndef COMBOITEMVIEW_H
#define COMBOITEMVIEW_H
#include <QListView>
#include <QComboBox>
class ComboItemView : public QListView
{
Q_OBJECT
QComboBox * _box;
public:
ComboItemView(QComboBox *box);
protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
QStyleOptionViewItem viewOptions() const;
};
#endif // COMBOITEMVIEW_H
comboitemview.cpp
#include "comboitemview.h"
#include <QPaintEvent>
#include <QPainter>
ComboItemView::ComboItemView(QComboBox * box = 0) : _box(box){}
void ComboItemView::paintEvent(QPaintEvent *event)
{
if (_box)
{
QStyleOptionComboBox opt;
opt.initFrom(_box);
opt.editable = _box->isEditable();
if (_box->style()->styleHint(QStyle::SH_ComboBox_Popup, &opt, _box))
{
QStyleOptionMenuItem menuOpt;
menuOpt.initFrom(this);
menuOpt.palette = palette();
menuOpt.state = QStyle::State_None;
menuOpt.checkType = QStyleOptionMenuItem::NotCheckable;
menuOpt.menuRect = event->rect();
menuOpt.maxIconWidth = 0;
menuOpt.tabWidth = 0;
QPainter p(viewport());
_box->style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this);
}
}
QListView::paintEvent(event);
}
void ComboItemView::resizeEvent(QResizeEvent *event)
{
resizeContents(viewport()->width(), contentsSize().height());
QListView::resizeEvent(event);
}
QStyleOptionViewItem ComboItemView::viewOptions() const
{
QStyleOptionViewItem option = QListView::viewOptions();
option.showDecorationSelected = true;
return option;
}
And finally use it to style the view font:
myCombo->setView(new ComboItemView(myCombo));
myCombo->setStyleSheet(
"QComboBox {font-weight: bold;} "
"QComboBox QAbstractItemView {font-weight: normal;}"
);

QFileDialog: How to select multiple files with touch-screen input?

In our application that uses Qt 4 and supports touch input, we use the QFileDialog with the options QFileDialog::DontUseNativeDialog and QFileDialog::ExistingFiles.
The first is needed because we set our own stylesheet and that does not work with the native dialog. The second is for needed for selecting multiple files, which is what we want to do.
The problem ist that one can not select multiple files with touch input in the QFileDialog, because we have no "shift" or "ctrl"-key available. In Windows the problem is solved by adding checkboxes to the items. QFileDialog has no checkboxes.
I tried to manipulate the QFileDialog to make it displays check boxes for the items, but I failed.
I tried to exchanged the QFileSystemModel that is used by the underlying QTreeView and QListView, but this breaks the signal-slot connections between the model and the dialog. I could not find a way to restore them because they are burried deep in the private intestants of the dialog.
At this moment the only solution I can imagine is writing a whole new dialog, but I would like to avoid the effort.
So is there a way to add checkboxes to the QFileDialog model views ?
Do you have another idea how selecting multiple files could be made possible?
Is the problem fixed in Qt 5? We want to update anyway.
Thank you for your Help.
As I failed to add checkboxes to the item views, I implemented a "hacky" work-around. It adds an extra checkable button to the dialog that acts as a "ctrl"-key. When the button is checked, multiple files can be selected. The solution is a little bit ugly, because it relies on knowing the internals of the dialog, but it does the job.
So here is the code for the header ...
// file touchfiledialog.h
/*
Event filter that is used to add a shift modifier to left mouse button events.
This is used as a helper class for the TouchFileDialog
*/
class EventFilterCtrlModifier : public QObject
{
Q_OBJECT;
bool addCtrlModifier;
public:
EventFilterCtrlModifier( QObject* parent);
void setAddCtrlModifier(bool b);
protected:
virtual bool eventFilter( QObject* watched, QEvent* e);
};
/*
TouchDialog adds the possibility to select multiple files with touch input to the QFileDialog.
This is done by adding an extra button which can be used as control key replacement.
*/
class QTOOLS_API TouchFileDialog : public QFileDialog
{
Q_OBJECT
EventFilterCtrlModifier* listViewEventFilter;
EventFilterCtrlModifier* treeViewEventFilter;
bool initialized;
public:
TouchFileDialog( QWidget* parent);
protected:
virtual void showEvent( QShowEvent* e);
private slots:
void activateCtrlModifier(bool b);
private:
void initObjectsForMultipleFileSelection();
};
with the implementation ...
// file touchfiledialog.cpp
#include "touchfiledialog.h"
EventFilterCtrlModifier::EventFilterCtrlModifier(QObject* parent)
: QObject(parent)
, addCtrlModifier(false)
{
}
void EventFilterCtrlModifier::setAddCtrlModifier(bool b)
{
addCtrlModifier = b;
}
bool EventFilterCtrlModifier::eventFilter(QObject* watched, QEvent* e)
{
QEvent::Type type = e->type();
if( type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease)
{
if( addCtrlModifier)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(e);
// Create and post a new event with ctrl modifier if the event does not already have one.
if( !mouseEvent->modifiers().testFlag(Qt::ControlModifier))
{
QMouseEvent* newEventWithModifier = new QMouseEvent(
type,
mouseEvent->pos(),
mouseEvent->globalPos(),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers() | Qt::ControlModifier
);
QCoreApplication::postEvent(watched, newEventWithModifier);
return true; // absorb the original event
}
}
}
return false;
}
//#######################################################################################
TouchFileDialog::TouchFileDialog(QWidget* parent)
: QFileDialog(parent)
, listViewEventFilter(NULL)
, treeViewEventFilter(NULL)
, initialized(false)
{
}
void TouchFileDialog::showEvent(QShowEvent* e)
{
// install objects that are needed for multiple file selection if needed
if( !initialized)
{
if( fileMode() == QFileDialog::ExistingFiles)
{
initObjectsForMultipleFileSelection();
}
initialized = true;
}
QFileDialog::showEvent(e);
}
void TouchFileDialog::initObjectsForMultipleFileSelection()
{
// install event filter to item views that are used to add ctrl modifiers to mouse events
listViewEventFilter = new EventFilterCtrlModifier(this);
QListView* listView = findChild<QListView*>();
listView->viewport()->installEventFilter(listViewEventFilter);
treeViewEventFilter = new EventFilterCtrlModifier(this);
QTreeView* treeView = findChild<QTreeView*>();
treeView->viewport()->installEventFilter(treeViewEventFilter);
QGridLayout* dialogLayout = static_cast<QGridLayout*>(layout()); // Ugly because it makes assumptions about the internals of the QFileDialog
QPushButton* pushButtonSelectMultiple = new QPushButton(this);
pushButtonSelectMultiple->setText(tr("Select multiple"));
pushButtonSelectMultiple->setCheckable(true);
connect( pushButtonSelectMultiple, SIGNAL(toggled(bool)), this, SLOT(activateCtrlModifier(bool)));
dialogLayout->addWidget(pushButtonSelectMultiple, 2, 0);
}
void ZFFileDialog::activateCtrlModifier(bool b)
{
listViewEventFilter->setAddCtrlModifier(b);
treeViewEventFilter->setAddCtrlModifier(b);
}
The TouchFileDialog installs an event filter to the item views that will add a ControlModifier to the mouse events of the views when the corresponging button in the dialog is checked.
Feel free to post other solutions, because this is somewhat improvised.

mousePressEvent called only from certain areas in scene

I have an application which draws lines based on different data from cars. I want my application to be able to select the lines drawn, and then make the corresponding item selected in a list on the left as well. The problem is that the mousePressEvent is only called when I press the mousebutton in the leftmost quarter of the scene. When it is called the curveSelected() function works as well, but I can't figure out why I can't invoke the mousePressEvent from the other areas on the scene.
First of all I have a mousePressEvent.
void DrawingScene:::mousePressEvent ( QGraphicsSceneMouseEvent * event ){
event->ignore();
bool leftbutton = (event->button() == Qt::LeftButton);
if(leftbutton)
{
qDebug() << "leftbutton";
emit leftButtonPress(event->scenePos());
}
QGraphicsScene::mousePressEvent(event);
}
Later connected:
connect(d_scene, SIGNAL(leftButtonPress(QPointF)), this, SLOT(curveSelected(QPointF)));
leftButtonPress is the signal emitted. Then I have the function which selects the item in the list. This method seems to work just fine. The problem exists without this function as well.
void CurveDrawer::curveSelected(QPointF pos){
QMapIterator<QPair<unitID, QString>, carData*> it(dataMap);
while(it.hasNext()){
it.next();
QPainterPath curPath = it.value()->pathItem->path();
if(curPath.contains(pos)){
for (int i = 0; i < list->count(); ++i) {
QListWidgetItem* curItem = list->item(i);
if(curItem == it.value()->listItem){
qDebug() << "curveSelected";
curItem->setSelected(true);
}
}
}
}
}
Anyone experienced something similar, or may see some obvious mistakes in my code?
EDIT:
How can i achieve that the mousePressEvent is called every time I click inside the scene? This is basically what I want it to do. Now it is only called when I click in certain area.
I tried to implement it with void DrawGraphicsView;;mousePressEvent(QMouseEvent *event) now, and the same problem existed there. The event just got invoked from certain areas in the scene.
The strange thing for me is that when a certain place in the scene is in the left of the viewport it is not possible to invoke the mousepressEvent, but when I scroll the same place to the right in the viewport, then it is suddenly possible to invoke the mousepressEvent. Does this make the problem clearer?