Drag and drop widget outside source application widgets - qt

I have a Qt Desktop aplication which has several top-level widgets. Subwidgets of top-level widgets can be moved between top-level widgets by using drag-and-drop mechanism.
The problem i have now is to drop a sub-widget outside any of existing top-level widgets and create a new top-level widget to contain this one. Lets call this separation.
Can this be done using drag-and-drop? I could not find a way where my dropEvent goes?
Can i want to handle the drop event in my application even if the drop place is not allowed? Maybe a mouse release or something?
I cannot change everything now but also a question for the future. Is docking/undocking a better way to do this?
Regards
Mihai

I found a way to do this. When drag moves outside of the application widgets QDrag object emits a targetChanged signal with 0 parameter.
So i inherited from QDrag and then emit a custom signal in destructor if the target() is null.
The only problem is that the cursor looks like interdiction of drop and this i could not fix because QDrag can only set cursor pixmap for valid actions like Move or Copy or Link
Update:
Here is the inherited class.
class TabDrag: public QDrag
{
Q_OBJECT
public:
explicit TabDrag(QWidget *dragSource);
~TabDrag();
signals:
void tearOff(); /// emit tearOff signal if the QDrag object is destroyed and target was null
};
TabDrag::TabDrag(QWidget *dragSource):QDrag(dragSource)
{
}
TabDrag::~TabDrag()
{
// check if we need to detach this tab
if(!target())
{
emit tearOff();
}
}
The tearOff signal should be connected to whatever you want to happen. In my case i pull out the widget from the tab and change parent to a new window.
Example of usage
void MyTabBar::mouseMoveEvent(QMouseEvent* event)
{
..................
TabDrag * drag = new TabDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(*m_tabPixmap.data());
drag->setHotSpot(QPoint(m_dragStartPos.x() - tabAtRect.x(), m_dragStartPos.y() - tabAtRect.y()));
drag->exec();
connect(drag, SIGNAL(tearOff()), this, SLOT(onTearOff()));
}

Related

Triggering an action based on its custom shortcut

Suppose I have some action to happen. For that I can create a QAction object and connect its triggered() signal to the slot that executes the desired function. Also, I can have a shortcut associated with the action; by changing the shortcut I'll be able to execute the same action with that shortcut.
My problem now is that the "shortcut" I wanna set to the action, contains also a mouse button press (and mouse events cannot be assigned to action shortcuts); say I want Shift+Left mouse button. Maybe this sounds a little bit harsh but bear with me.
What do I need? Well, I have a button, and an action (say "execute a script"). I want the script to execute when Shift+Left click is clicked, and I want this "shortcut" to be customized, i.e. the user should be able to change to shortcut to, say Ctrl+Left click (from some GUI element, e.g. button text), and now Ctrl+Left click should execute the script.
How can I achieve this?
Note: I as a user would expect an action triggered by a mouse button to be position dependent. If so, the following gets a bit simpler.
Qt doesn't have an option to specify such a shortcut.
You can roll your own by reacting to mouse events:
Maybe you have an event handler mousePressEvent(),
or a generic eventFilter(QObject *obj, QEvent *evt),
or utilize QApplication::notify
Whichever, at some place you need to catch a QMouseEvent *mouseEvt.
Choose the widget (or qApp) that is as outmost as needed.
There, compare mouseEvt->button() and mouseEvt->modifiers() to your list of actions and trigger the selected action. When the user chooses another trigger method, adjust your list of actions.
Let's put this to practice:
class MainWindow : public QWidget {
Q_OBJECT
public:
QMap<QPair<Qt::MouseButton, Qt::KeyboardModifiers>, QAction*> mapMouseShortcuts;
QAction *pLaunchScript;
MainWindow() : QWidget() {
mapMouseShortcuts.insert(qMakePair(Qt::LeftButton, Qt::ControlModifier), pLaunchScript);
}
void mousePressEvent(QMouseEvent *me) {
QAction *action = mapMouseShortcuts.value(qMakePair(me->button(), me->modifiers()), Q_NULLPTR);
if(action != Q_NULLPTR) {
action->trigger();
me->accept(); // optional
}
// optional:
if(!me->isAccepted()) {
QWidget::mousePressEvent(me);
}
}
};

Issue with Qt Undo Framework example: add / remove item

I was using the QT undo framework example as reference to implement this functionality in my tool. However, it seems to have a bug with the way that it calls the destructor of the items.
I understand that QGraphicsScene will assume ownership of the items while they are in the scene. However, both undo objects: AddCommand and RemoveCommand should assume ownership of these items when they remove them from the scene.
In the Qt undo framework example, only AddCommand tries to delete the object in its destructor, but will not do it if the item is still in the scene.
AddCommand::~AddCommand()
{
if (!myDiagramItem->scene())
delete myDiagramItem;
}
In this case, if we remove the item from the scene after the corresponding AddCommand object leave the stack (when using an undo limit), the item will never be deleted again since RemoveCommand destructor doesn't do it.
I fixed it using a flag in both AddCommand and RemoveCommand classes. It informs when this objects should be responsible for the item destruction. When they remove the item from the scene, I set this flag to true, and I test this flag in the undo object destructor before calling the item's destructor:
AddCommand::AddCommand(QGraphicsScene *scene, DraftGraphicItem* item, QUndoCommand *parent):
scene(scene), item(item), QUndoCommand(parent){
setText("Add item to scene");
}
AddCommand::~AddCommand(){
if(isItemOwner)
delete item;
}
void AddCommand::undo(){
Q_ASSERT(item->scene());
scene->removeItem(item);
isItemOwner = false;
}
void AddCommand::redo(){
Q_ASSERT(!item->scene());
scene->addItem(item);
isItemOwner = true;
}
and the same with RemoveCommand, just inverting redo() and undo() methods.

How to access a QAction using the QtTest lib?

I have a pop-up menu in a QTableWidget (resultTable). In the constructor of my class I set the context menu policy:
resultTable->setContextMenuPolicy(Qt::CustomContextMenu);
connect(resultTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(popUpMenuResultTable(QPoint)));
The popUpMenuResultTable function:
void MyClass::popUpMenuResultTable(QPoint pos)
{
QMenu menu;
QAction* actionExport = menu.addAction(QIcon(":/new/prefix1/FileIcon.png"), tr("Export"));
connect(actionExport, SIGNAL(triggered()), this, SLOT(exportResultsTable()));
menu.popup(pos);
menu.exec(QCursor::pos());
}
Now, I need to implement a function to test my GUI using the QtTest lib.
How can I produce the same result as a user by right clicking on my resultTable? Basically, I need to get access to the actionExport (QAction) and trigger it.
For example:
I already tried:
QTest::mouseClick(resultTable, Qt::RightButton, Qt::NoModifier, pos, delay);
but it does not show the QMenu.
I'm using Qt 5.3.2.
Maybe not entirely what you are after but an alternative approach that is easier to test.
Instead of creating the menu manually you register the actions with the widgets and use Qt::ActionContextMenu:
// e.g. in the widget's constructor
resultTable->setContextMenuPolicy(Qt::ActionsContextMenu);
QAction* actionExport = menu.addAction(QIcon(":/new/prefix1/FileIcon.png"), tr("Export"));
connect(actionExport, SIGNAL(triggered()), this, SLOT(exportResultsTable()));
resultTable->addAction(actionExport);
Then you either add an accessor to your widget that returns resultTable->actions() or just make actionExport a member of your class.
Once your test code has access to the action it can simply call its trigger trigger() method.

Show a QWidget after pressing a button

I want to change a QWidget in a QMainWindow dynamically. Therefore, the old widget (if it exists) will be deleted, a new one will be constructed and added to the main window.
The widget (_visualization) is a QMainWindow itself that contains a menu bar and a widget that shows an OSG scene graph.
If I donĀ“t call show() on the new widget, I will only see the menu bar, but not the scene graph.
My goal is to call show(), when the user clicks on a button. The button is connected with the outer QMainWindow (MyQMainWindow).
Unfortunately, when I call show() on _visualization in the connected method, the scene graph will not be shown. In contrast to that, the scene graph will be shown, if I call it in the constructor method (loadVisualization(...)).
MyQMainWindow::MyQMainWindow(QWidget *parent ) :
QMainWindow(parent) {
...
loadVisualization(...);
connect(_ui->nextButton, SIGNAL(clicked()), this, SLOT(showNext()));
...
}
void MyQMainWindow::loadVisualization(QString filePath) {
if (_visualization) {
_heightWidgetLayout->removeWidget(_visualization);
delete _visualization;
}
_visualization= new HeightVisualization(0, filePath);
_visualization->setParent(_mainWindowWidget);
_heightWidgetLayout->addWidget(_visualization);
//_visualization->show(); // will work here
}
void MyQMainWindow::showNext() {
_visualization->show(); // does not work here!
}
I know that QT will call setVisible(...) on the widget. This method first tests some states in QT (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)). If I call show() in showNext(), these tests lead to a return without any change.
Is it a problem with connectors and slots? Is there a possibility to show the widget, when the user clicked on a button?
What I have learned is that it is easy to use stackedWidget.
You can then programmatically change it with this
currentPageIndex = (currentPageIndex + 1) % 3;
ui->stackedWidget->setCurrentIndex(0);
The 3 can be the total pages you have in your stack widget. You can either use currentPageIndex, or you can just create some constants with the page ids like I have done with the 0.

make Custom QWidget selectable

I'm working on a boardgame and I'm trying to make QWidgets (Rectangles) selectable.
So I have a BoardView (inherited from QWidget) which contains BuildingViews, PlantationViews (both inherited from QWidget). It all shows up on the window, but it is not clickable. How can I make this clickable?
You could try to make a QMouseEvent implementation where the widget ID is forwarded,
something like this:
In the implementation of your widget (e.g. YourWidget.cpp):
YourWidget::MouseReleaseEvent(QMouseEvent *event)
{
emit clickedWithMouse(this); // this is a signal, declared in YourWidget.h
}
In the "main" game file (e.g. Game.cpp):
Game::onButtonClicked(YourWidget* widget) // this is a public slot, you must connect all YourWidgets's clickedWithMouse signals to this slot (in Game object code, e.g. when initialising the board)
{
lastWidget = widget; //save the widget "ID" (lastWidget is a member of class Game)
someFunction(widget); //do something with the widget (if you wish)
}

Resources