QTreeWidget: separating cursor from selection - qt

I'm trying to use QTreeWidget for a file browser. I want to have separate concepts of selection (items to which operations are applied) and cursor (that can be used for extending selection). Like in Total Commander: red is selection, moving cursor doesn't affect selection unless Shift is pressed.
Is there any neat way to implement this with QTreeWidget? Or not neat but at least working and not requiring to manually handle half the events?

If you set the widget's selection mode to QAbstractItemView::ExtendedSelection then an user will be able to navigate through items without changing selection using arrow keys with pressed Ctrl. If you want to change this behavior, you should reimplement keyPressEvent as follows:
void MyWidget::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_Down ||
event->key() == Qt::Key_Up) {
event->setModifiers(Qt::ControlModifier);
}
QTreeWidget::keyPressEvent(event);
}
Now when user press Up or Down key, the current item is changed and the selection remains unchanged. I've tested this solution.
Note that there are also pageup, pagedown, home, end (and may be other) keys that change the selection by default. You may need to process these events too.

Related

Uncheck a QPushButton with autoExclusive

Ubuntu 22.04, Qt 6.4.0.
I have some QPushButtons that belong to the same parent.
I set all of them with checkable and autoExclusive properties to true.
The doc about the latter states:
If auto-exclusivity is enabled, checkable buttons that belong to the
same parent widget behave as if they were part of the same exclusive
button group. In an exclusive button group, only one button can be
checked at any time;
Emphasis is mine.
From what I understand, they say that only one button "can" be checked. This means that more than one button can never be checked. But it allows to no buttons checked. Otherwise I was expecting something like: "only one button must be checked at any time".
Anyway, my problem is I cannot uncheck a button after it was checked.
It remains checked no matter I click on it.
I also tried programmatically:
ui->myCheckableButton->setChecked(false);
no way.
Disabling the autoExclusive property I can disable them, but of course is not the intended behavior.
What am I missing here?
How to uncheck a button when autoExclusive property is active?
Sadly, you are experiencing the intended behavior of Qt.
The source code for QAbstractButton::setChecked contains lines that makes it clear a button belonging to an exclusive group cannot be unchecked (unless it is the only button in that group).
if (!checked && d->queryCheckedButton() == this) {
// the checked button of an exclusive or autoexclusive group cannot be unchecked
#if QT_CONFIG(buttongroup)
if (d->group ? d->group->d_func()->exclusive : d->autoExclusive)
return;
if (d->group)
d->group->d_func()->detectCheckedButton();
#else
if (d->autoExclusive)
return;
#endif
}
d->queryCheckedButton() returns nullptr if only 1 button belongs to the group. The rest is straight forward:
If Qt was compiled to use button groups and the button group is exclusive, do not uncheck the button (return; instead).
Otherwise, if the button itself is autoexclusive, do not uncheck it.
I checked the exclusive property of QButtonGroup, the description makes the behavior clear: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..
It is unfortunate the description for QAbstractButton does not contain that sentence too, although I will say it is not incorrect since 0 buttons may initially be checked (then, when 1 is checked, it will never allow to uncheck everything, which is the missing part).
Your modified sentence using must would have a meaning in the lines of:When using the QAbstractButton class, you must make sure not to allow more than 1 button to be checked.There are several instances of sentences built this way and AFAICT, all of them describe things you must do for your implementation to work.
The good news in all of that is that your solution of disabling the autoExclusive property seems no have no impact whatsoever on the signals emitted by the button.You may for instance create a subclass of QPushButton (if that is what you are using), remove the autoExclusive property, first thing in the mousePressEvent and re-enable it last thing in the mouseReleaseEvent.
Alternatively, you can make your button non autoexclusive and connect signals when it is toggled (to true) to unckeck the other buttons.This is probably how I would do it.

Multiple Selection table, without selection of mouse dragging

I would like to have QAbstractItemView that will allow multi-selection of items only with ctrl button toggle. I can't use QAbstractItemView::ExtandedSelection because it also allow multiple items selection by dragging the mouse over them.
I assume you are using a QTableView
You can override the QTableView and then use mouseMoveEvent cleverly to ensure that user can't make multiple selections by dragging!
If user enters into mouseMoveEvent with the left mouse button pressed, you can choose to eat the event without passing it to the QTableView that will remove the possibility of multiple selection.
e.g.
void
TableView::mouseMoveEvent( QMouseEvent * inEvent )
{
// Deliberately commented to not to pass this event to parent to avoid multiple selection
// QTableView( inEvent );
inEvent->accept();
}
This might work for you, you may also have to be careful in the mouseMoveEvent, when you do above to mousePressEvent you have to do the same for mouseReleaseEvent as well.
Though this is just a theory , but is should work!

QGraphicsScene: How to prevent keyboard events from reaching the main window while dragging items, for example?

I'm implementing a basic shape drawing tool using a custom subclass of Qt's QGraphicsScene and several QGraphicsItem. Now there are several situations where I don't want any "global" actions to be executed:
For example, while dragging items around, the user should not be allowed to create a new file or to undo the last action (by pressing Ctrl-Z for example) since this would lead to several problems that would have to be handled separately (if the user is currently drawing an edge between two nodes, what should happen if he presses Ctrl-Z with the last recorded action being the creation of the first node?)
I noticed that several commercial applications like Microsoft Word and Adobe Photoshop just seem to ignore any usual keyboard shortcuts while being in such an "intermediate" state. Furthermore, when dragging items out of the viewport, these tools display a "forbidden" cursor and do not allow any mouse press events to reach the outer window (like a right click on the toolbar, for example).
How should I implement this in my case, when using QGraphicsScene? I already tried to add the following override:
void MyGraphicsScene::keyPressEvent(QKeyEvent* keyEvent)
{
keyEvent->accept();
}
But any pressed keys were still delivered to the main window. In addition to that, I'm not sure if filtering just keyboard events is safe enough, since there might be other input events that could trigger forbidden actions.
Is there any generic approach to this problem that I could use in my software?

How to enable both internal reordering and external dropping in a Qt widget?

I have created a widget which inherits QListWidget.
My goal is for it to accept files dropped into it from an external file manager, and for the user to be able to reorder the elements in the widget. I can achieve both, but not at the same time.
If I just set
myWidget->setDragDropMode(QListView::InternalMove);
myWidget->setDragEnabled(true);
I can reorder the items within the widget, but I can't drop external items into it.
If I reimplement the dragMoveEvent, dragEnterEvent and dropEvent events, all of them just having acceptProposedAction(); and some debug messages inside them, I can drop external files into my widget, but I can no longer rearrange the items.
Is there a way to have the above two at the same time, or do I have to manage the items myself in the reimplemented functions? If so, how can I know if a dropped item is internal or external, and how can I know from which position it was taken and into which position in the list it was dropped into?
If I parse the mimeData which I got from the event, I can see whether it as a file or a text, and I get "qabstractitemmodeldatalist" if it was an internal item, but it still doesn't give me its position.
I can check event->pos() to know in pixels where the drop has been made, and event->source() to learn about what was dropped there, but is this really best practice, to start calculating pixel values and adding objects "manually"?
The solution was very simple: I just had to call the functions of the parent class at the end of each function I've overridden.
void myWidget::dropEvent(QDropEvent *event)
{
do_stuff_with_received_data(event);
QListWidget::dropEvent(event);
}

QAbstractItemDelegate painting while dragging problem

I'm overloading the paint() function in QAbstractItemDelegate (my own Item delegate class).
When dragging, it paints the contents of the entire cell, which I don't want. I'm assuming that the paint() function is called with something specific while dragging, but I don't seem to find it.
The closest I've been able to find is a QState variable in the owning view class (access function QTableView::state() is protected.) By creating a function on my QTableView-derived class called 'isDragging()' which calls that function and returns whether dragging or no, I can determine within my delegate class whether I'm dragging or not, and can modify the paint() function.
This almost works.
The problem is that it shows the modified paint image in the original cell, which I don't want - I want to leave the image in the original cell untouched.
Have to scour the examples, I guess, and see if there's something that does this...
I have crawled through the Qt source and I can see where it sets the drag pixmap by calling the QItemDelegate::paint() function, but the only thing it changes is it forces QStyle::State_Selected in the item option style. That's not enough, since the item is selected, already.
Any way to know how to draw a cell's contents explicitly when dragging?
Ok, the ultimate answer on this was to, yes, set the flag on 'startDrag', but rather than leaving it around and unsetting it on mouse release button event, simply call the base method and then unset.
The reason is that the image for the cursor is only requested (and painted) once - not continuously during the drag, as I had first thought. Leaving the flag set meant the cursor image would get drawn at inappropriate times.
So, the implementation looks like:
MyClass::dragStart(Qt::DropActions supportedActions)
{
__dragStart = true;
TableView::dragStart(supportedActions);
// request for drag cursor image happens here
__dragStart = false;
}
Why don't you do that yourself? Set a flag when dragging starts and remember the active ModelIndex, do some special painting when the flag is set, and clear the flag when dragging is finished. You can do this by overriding QAbstractItemView::startDrag.

Resources