Troubles with undo history (QUndoStack, QUndoView and other) - c++

I have two separate threads.
First thread for GUI, and second for application data.
Initially, I wanted to use QUndoStack and QUndoView.
But there was a problem - this view works directly with the stack:
https://code.woboq.org/qt5/qtbase/src/widgets/util/qundoview.cpp.html#_ZN10QUndoModel20setStackCurrentIndexERK11QModelIndex
In this case I got race condition.
To solve this problem I wrote custom myUndoView using QListView and QAbstractListModel.
Now all my slots using queued connections and I store a lightweight copy of the "real" undo stack in the custom view model.
This is same size and same order of the "real" undo stack elements.
A lightweight element contains only type of the undo command and text.
Now I have another problem. I'm not blame for this ))
I have a QLineEdit that emits signal on value changed when I click Enter key or lost focus.
This value in turn is sent to object (app model) with "real" undo stack. It works.
But this does not work when I interact with undo view too.
Repeat, I'm not blame for this. QUndoView has the same behavior.
Step by step:
QLineEdit in focus.
Changing value, still in focus.
Click the mouse in the undo view.
Oops.. currentIndexChanged() signal from undo view can be sent first,
or signal from QLineEdit can be sent first.
It always differs ..
If signal from QLineEdit was sent first - it works correctly.
The history of changes not lost.
I want to make enter/blur and other changes (not in history view) always invoked first. Probably I can use QTimer::singleShot() for delay of emit undo view signals . But not curentIndexChanged() because this signal emit with user interactions and when undo stack updated programmatically. We can not determine who make changes - user or application.
What I tried?
Intercept mouse clicks:
myUndoView::mousePressEvent(QMouseEvent *event)
{
event->ignore();
qDebug() << "catched!";
}
But sometimes it loses the clicks.
At the bottom of the list item (under the letters) is an area that pass a click to the item.
This may be a Qt bug, found in my environment: Debian, Mate, GTK+ Qt-style.
I think, I can place another transparent widget over list, and get coordinates of the click and use it:
http://doc.qt.io/qt-5/qabstractitemview.html#indexAt
to get the selected index.
Or I make all wrong?
Maybe there is an easier way?
How to make it right?

I would try blocking the list model signals while the line edit is focused.
Let's have an event filter like this:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(QObject * model) : _model(model){}
bool eventFilter(QObject *watched, QEvent *event);
private:
QObject * _model;
};
which keeps a private reference to the list model as a pointer to QObject, passed in constructor argument.
The filter implementation:
bool EventFilter::eventFilter(QObject *watched, QEvent *event)
{
if(event->type() == QEvent::FocusIn)
{
_model->blockSignals(true);
}
return false;
}
Keep a reference to an instance of the filter in the window class (Form, in my example), along with the list model instance reference:
private:
EventFilter * filter;
QAbstractListModel * model;
The filter has to be instantiated and installed in line edit, in Form constructor (don't forget to delete it in the destructor):
filter = new EventFilter(model); //the model is passed to the filter in construction
ui->lineEdit->installEventFilter(filter);
At this point, model events will be blocked when the line edit gets focus. To unlock them, use the line edit editingFinished slot:
void Form::on_lineEdit_editingFinished()
{
model->blockSignals(false);
}

Related

How to add hyperlinks in Qt without QLabel?

I have some labels and layouts nested inside a QWidget to build a part of a sidebar. Each QWidget is its own section and one component currently looks like this:
To my understanding, you can only set hyperlinks with QLabel, but I'm trying to get the whole area between the white lines clickable. This is including the icon and the whitespace. Is there any way to achieve this?
This got marked as a duplicate to the opposite of what I was asking, so I'd like to reiterate that I'm trying to implement a hyperlink without QLabel.
You can easily have a widget open a link on click:
class Link : public QWidget {
Q_OBJECT
public:
Link(QUrl url, QWidget p = nullptr) : QWidget(p), _url(url) {}
QUrl _url;
void mouseReleaseEvent(QMouseEvent *) { QDesktopServices::openUrl(_url); }
}
You can avoid any extra signals and connections, and have each link widget store its own link internally, the url can be set on construction and changed at any time. Not using signals and slots makes it easier to change the link too, without having to disconnect previous connections.
IMO going for a signals and slots solution is only justified when you want different arbitrary behavior. In this case you always want the same - to open a particular link, so you might as well hardcode that and go for an easier and more computationally efficient solution.
I would just manually catch the SIGNAL for clicked() and use desktop services to open the url in code.
bool QDesktopServices::openUrl ( const QUrl & url ) [static]
Opens the given url in the appropriate Web browser for the user's desktop environment, and returns true if successful; otherwise returns false.
http://doc.qt.io/qt-4.8/signalsandslots.html
Using this type of syntax, or in the designer, you can also connect a signal to a slot.
connect(widgetThatRepresentsURL, SIGNAL(clicked()),
handlerThatWillOpenTheURL, SLOT(clicked_on_url()));
For widgets that don't have a signal set up for clicked (or whatever event you are interested in), you can subclass the widget in question and reimplement...
void QWidget::mousePressEvent ( QMouseEvent * event ) [virtual protected]
Specifically for creating a signal, there is emit. I've used this in the past like the following
void Cell::focusInEvent(QFocusEvent *e)
{
emit focus(this, true);
QLineEdit::focusInEvent(e);
}
with the following in the header
signals:
void focus(Cell *, bool);

Qt C++ : How to get event target object?

There is a container Widget instance A and there are contained Widget instances C1, C2, C3, ... etc within A.
There is a slot that handles Widget A's action_triggreed() signal.
Is there a way to determine which of the target Widgets C1, C2, C3, ... was clicked?
The reason for this necessity is that there are numerous contained widgets, and it doesn't make sense to use connect() method on each, which would require 50+ lines of extra connect statements, one for each!
For example: Consider a QToolBox with lots of Buttons. How would you determine which Button is pressed by using a QToolBox action_triggered or similar signals, without using signals and slots for individual Buttons separately?
You can use the QObject::sender() function.
void A::triggeredSlot() {
QObject* obj = sender();
QButton* but = qobject_cast<QButton*>(obj);
}
Just use the correct types etc.
There are a few general approaches.
The first one is straightforward - you should connect each widget's signal to the slot and check what sender() is. Yes, lines of code for connecting.
The second one is to use a kind of mapper. Every widget is connected to the mapper and only mapper is connected to the target slot. Lines of code again, at least in the part where you connect widgets to the mapper. There is a generic QSignalMapper or you can use more specific one suited for your widgets. For example, if they are buttons then you can use QButtonGroup class. Every button is registered in the group and only one signal/slot connection is required.
QButtonGroup group;
group->addButton(buttonC1,C1_ID);
...
group->addButton(buttonC1,Cn_ID);
connect(&group,SIGNAL(buttonClicked(QAbstractButton*),this,SLOT(buttonClicked(QAbstractButton*));
The third approach is to detect mouse event only on the mother's widget A and then iterate over all its children and find which one is under mouse. Less code, you can easily add new widgets, but the cost is iterating over all widgets in runtime. Below is an example. Note, that you can add specific QObject names or properties to the widgets C1... so that you could filter them if you are interested only in a part of children widgets of the given type.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QList<QToolButton*> buttons=findChildren<QToolButton*>(); // you can also use specific object names on the widgets under your interest
foreach (QToolButton* button, buttons)
{
if (button->underMouse()) // you could try isDown() for button, but I'm not sure if that will work here
{
emit buttonClicked(button);
break;
}
}
}
}
Well, you could try another approach also. Detect the mouse event, get cursor position and find the child widget on that position. Not costly in runtime.
void mousePressEvent(QMouseEvent* event)
{
if (event->button()==Qt::LeftButton)
{
QPoint pt=mapFromGlobal(QCursor::pos());
QWidget* child=childAt(pt);
if (child)
{
emit childClicked(child);
}
}
}

Deleting a QGraphicsItem/QGraphicsObject from QGraphicsScene?

I have created Qt GUI application. It consists of QGraphicsScene, and items (QGraphicsItems) are added to them by pressing or triggering pushbuttons. Each item added to the scene are members of different classes derived from QGraphicsItem. Now, my challenge is to delete an added item off the scene through one of the following mechanisms:
1) Right click an added item, create a context menu, and then use
scene->removeItem(addedItem);
2) Double click the item which deletes the item
3) Select an item using the flag ItemIsSelectable, and then delete the item by pressing the delete key on the keyboard
But having said that, as a newbie to Qt, I'm unable to do number 1 since the context menu doesn't show up when right clicked. In the case of number 2, I used signals and slots, a single emitted whenever an item is double clicked, and a slot in the mainWindow absorbs the signal and removes the item. But this way, the programs fails to compile because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file of the item's class.
So my final option is to select an item on the screen and propane the keyboard signal to delete the item by pressing delete. How can be this done? Please give me advice if any of the above methods can be easily done in case I might be doing it completely wrong.
P.S. : I know there a lot of queries regarding deleting QGraphicsItem off QGraphicsScene, but none of them document a solid answer.
... I'm unable to do number 1 since the context menu doesn't show up when right clicked.
There are two possible methods to accomplish this:
Create a QWidget based menu, attached to the QGraphicsView.
Create your own menu item, derived from a QGraphicsItem.
Whilst the 2nd method will take more time, it's probably a better system in my opinion, as it will feel more integrated with the item you're deleting in the scene. The first method is also possible and if it's not working, then you could post an example question on SO.
2, I used signals and slots, ... because of the error "duplicate symbol found" when I add a Q_OBJECT macro to the header file
It sounds like you're trying to add the signal / slot functionality to a class derived from QGraphicsItem. You don't need to do this. Qt provides the QGraphicsObject class, which you can derive from, instead of QGraphicsItem, if you want signals and slots on items in a QGraphicsScene.
propane the keyboard signal to delete the item by pressing delete.
I assume you mean to 'propagate' keyboard signals. By overriding the QGraphicsScene and its keyPressEvent or keyReleaseEvent, you can get a list of selected items and delete them from the scene. Here's a skeleton example: -
class MyScene : public QGraphicsScene
{
protected:
void keyReleaseEvent(QKeyEvent * keyEvent);
};
void MyScene::keyReleaseEvent(QKeyEvent * keyEvent)
{
if(keyEvent->key() == Qt::Key_Backspace)
{
QList<QGraphicsItem*> selectedItems = selectedItems(); // get list of selected items
foreach(QGraphicsItem* item, selectedItems)
{
removeItem(item);
delete item;
}
}
}
You're seeking a lot of answers, Not so much how to handle QGraphicsItem or QGraphicsScene.
1) Right click an added item, create a context menu, and then use scene->removeItem(addedItem); here.
2) Double click the item, which deletes the item - you'll need to handle double clicks, and hit-testing the QGraphicsItems, you'll have to implement mouseDoubleClickEvent(QMouseEvent *e) and pass e's pos() to this to determine if a QGraphicsItem was clicked or not.
3) Select an item using the flag ItemIsSelectable and then delete the item by pressing the delete key on the keyboard - I'm not sure about the ItemIsSelectable flag. However, you'll need #2. And to learn how to handle keyboard input, by overriding this:
void QWidget::keyPressEvent( QKeyEvent *k ){
switch ( tolower(k->ascii()) ) {
case '\x08': \\backspace
break;
case '\x7F': \\delete
break;
}
}
There's also the Qt::key enumeration, which has Key_Backspace, and Key_Delete. It can be tested against the QKeyEvent::Key()'s return if you don't like dealing with ASCII character codes.

Changing QGraphicsItems position within the scene event handler?

I am using
void QGraphicsItem::installSceneEventFilter(QGraphicsItem * filterItem);
to set an event filter on a QgraphicsItem (see itemChanged() in QGraphicsItem for a many different items)
Now, for some of these items, I'd like to restrict the movement, i.e. change the x and y position of the item so that the user would be restricted in some area in the object move.
I first tried to modify the event with:
(static cast <QGraphicsSceneMouseEvent*>(event))->setPos(QPoint(150, watched->y()));
The whole handler beiing:
bool generic_graphic_item::sceneEventFilter(QGraphicsItem* watched, QEvent* event)
{
if(event->type() == QEvent::QEvent::GraphicsSceneMouseMove)
{
(static_cast<QGraphicsSceneMouseEvent*>(event))->setPos(QPointF(150, watched->y()));
//emit my_item_changed(watched); // signal that the item was moved
emit(item_pos_changed(watched, watched->x(), watched->y()));
}
return false; // pass the event to the original target item
}
But it did not work. I was not really sure either about the specific event class hiding behind a QEvent::GraphicsSceneMouseEvent.
I then tried to call watched->setX() and watched->setY() within the event handler, but that was not very popular... which I can understand...
Is it possible to restrict the movement within the scene event handler?
I have read that QGraphicsItem::itemChange() can be used to do that, but then I am back into the problem described in 'itemChanged() in QGraphicsItem for a many different items', i.e. how can I have this common to many items without subclassing each of them...
many thanks,
The code you post in this question is responding to the event of the moving of the mouse. For what you're describing that you want to do, I suggest you check for the event of a widget being moved with QEvent::GraphicsSceneMove: -
if(event->type() == QEvent::GraphicsSceneMove)
{
// set the position of the item.
}

Catch QTableWidgetItem check state change

I have one QTableWidget with some QTableWidgetsItems on it. Some items use checkboxes. I've added the checkboxes using the follow code:
QTableWidgetsItem->setCheckState(Qt::Checked);
I would like now to call some function when this checkbox state change. Using a signal for example.
What may be the easiest way to accomplish this?
The easiest way to do this is to capture signal(s) of QTableWidget with slot(s) in the class that contains QTableWidget. While it would seem that QTableWidget::itemActivated might be our best bet, it is uncertain whether or not this is emitted when the Qt::CheckState is equal to Qt::Checked. In addition, even if this was true, the signal would not provide you the capabilities of handling item unchecking which your application may need to do.
So, here is my proposed solution. Capture the QTableWidget::itemPressed and QTableWidget::itemClicked signals with slots defined in the class that contains the QTableWidget. As itemPressed should be called BEFORE the mouse button is released, and itemClicked should be called AFTER the mouse button is released, the Qt::CheckState for that QTableWidgetItem should only be set in between these two signal emissions. Thus, you can determine exactly when a QTableWidgetItem's checkState has changed with low memory overhead.
Here is an example of what these slots could look like:
void tableItemPressed(QTableWidgetItem * item)
{
// member variable used to keep track of the check state for a
// table widget item currently being pressed
m_pressedItemState = item->checkState();
}
void tableItemClicked(QTableWidgetItem * item)
{
// if check box has been clicked
if (m_pressedItemState != item->checkState())
{
// perform check logic here
}
}
And the signals/ slots would be connected as follows:
connect(m_tableWidget,SIGNAL(itemPressed(QTableWidgetItem *)),this,SLOT(tableItemPressed(QTableWidgetItem *)));
connect(m_tableWidget,SIGNAL(itemClicked(QTableWidgetItem *)),this,SLOT(tableItemClicked(QTableWidgetItem *)));
Where m_tableWidget is the QTableWidget * you associate with your table widget.