Dragging a QWidget in QT 5 - qt

I have to make something like the iOS interface, 3x3 field of icons that can be dragged to change their position, and then remember it in an XML file.
I decided to use Grid class (QWidget is parent) as a container and my own class, inherited from QWidget, as elements.
Now I'm trying to learn how to perform a drag & drop for QWidget, but it seems like you are only able to drop onto a QWidget, but it's impossible to drag it.
Is it impossible? How do I move this thing?

Dragging a widget isn't that easy. Plenty of coding you have to do yourself.
From the Qt Docs:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
drag->setPixmap(iconPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
}
This means, when your widget receives a message that it is to be dragged, e.g. like with a mousepress, it has to create a QDrag object.
In the QDrag object you can set a pixmap, which represents your dragged widget. If you hide your widget at this point, it looks like as if your mouse pointer 'took' the widget.
Additionally you need a QMimeData object. In this you can put all kinds of data, which describes your widget. In your use case something which allows you to identify your widget. Because, and here comes the difficult part: You have to do the moving yourself.
The widget, which is the grid's parent, receives the drop event and reads from the mime data, which widget wishes to me moved. From the QDropEvent it gets the point where the widget is to be moved. That's what you have to code: The actual repositioning in your grid layout. And don't forget to update the xml.

do a trick then you can able to drag QWidget also,
QPixmap *widgetPixmap = new QPixmap;
yourWidget->render(widgetPixmap); // rendering the current widget to t_iconTPixmap
Now use this widgetPixmap as the pixmap for yourDragObject->setPixmap();
example,
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
QPixmap *widgetPixmap = new QPixmap;
yourWidget->render(widgetPixmap);
drag->setMimeData(mimeData);
drag->setPixmap(widgetPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
}

The Qt wiki contains a QtQuick example on how to emulate the iOS icon interface with less then 80 lines of code.
You can find it here.

Related

Qt eventFilter passes event to target, although it should be filtered

my eventFilter doesn't work as expected. I either missunderstood its behaviour or I have a problem in my program.
In my program I have a QScrollArea with a QLabel inside to display an Image (just like in the Qt Image Viewer Example), but if the user scrolls his MouseWheel I don't want the ScrollArea to scroll, but the Image to scale. Therefore I implemented an eventFilter for my Label and my ScrollArea.
bool RBDP::eventFilter(QObject *target, QEvent *event)
{
if (target == scrollArea || target == iv) {
if (event->type() == QEvent::Wheel) {
QWheelEvent *w = static_cast<QWheelEvent *>(event);
iv->scaleImage(w->delta());
return true;
}
I know that the eventFilter actually gets triggered when you scroll. The problem is that the ScrollArea ALSO scrolls, i.e. if you use your MouseWheel, the Image scales and the ScrollArea scrolls!
As far as I know, the event shouldn't be propagated to its original target, if the eventFilter returns true.
I installed in my main class eventFilters for both, the scrollArea and iv (which is my QLabel).
scrollArea->installEventFilter(this);
iv->installEventFilter(this);
How do I prevent the ScrollArea from scrolling? I know this isn't much code, but the program is kind of complex already and I don't know, what else could be useful. So please let me know, if you need to see other code segments.
Thanks in advance

Drag and drop widget outside source application widgets

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()));
}

How can I add another tab that looks exactly like the first one (like in a browser)?

I have a browser made in Qt and a I have a tabwidget with one tab (which has a label, lineedit and a webview). I want to add others that look like the first one (have label, lineedit and webview).
How can I do this?
I don't know of any way to "clone" or duplicate an existing tab or widget, so I believe you'll need to code the tab contents yourself (i.e. not through the designer).
If all you need are a QLabel, a QLineEdit and a QWebView, that's not very complex. The idea would be to:
create a custom widget (inheriting from QWidget directly, or from QFrame)
lay out the contained widgets in the fashion you want in its constructor
add as many tabs as you want, when you want them, via the QTabWidget.addTab function.
The Tab Dialog example has everything you need - it's actually more complex than what you need because it uses different widgets for each tab. You can get away with a single widget.
If you wonder how to do the layout, and you're satisfied with what you got from the designer, you can inspect the generated (.moc) files. You'll see what layouts it uses, and you can replicate that in your own code.
Skeleton widget:
class BrowserTab : public QWidet
{
Q_OBJECT
public:
BrowserTab(QUrl const& home, QWidget *parent = 0);
void setUrl(QUrl const& url);
private:
QWebView *web;
QLabel *title;
QLineEdit *urlEdit;
};
BrowserTab::BrowserTab(QUrl const& home, QWidget *parent)
: QWidget(parent)
{
urlEdit = new QLineEdit(this);
title = new QLabel(this);
web = new QWebView(this);
QVBoxLayout *vl = new QVBoxLayout;
vl->addLayout(title);
vl->addLayout(urlEdit);
vl->addLayout(web);
setLayout(vl);
setUrl(home);
}
void BrowserTab::setUrl(QUrl const& url)
{
web->load(url);
// update label & urlEdit here
}
You'll need to do a bit more to make it a proper browser (setUrl should probably be a slot too), but this should get you started.

How to select multiple items without pressing Ctrl key within QGraphicsScene?

In Qt's QGraphicsScene, if I wanna one item, just click it, and click another selectable item will make the selected item to be unselected. If I want to select multiple items, I'd use Ctrl-key. But this maybe not convenient for some cases, then how to select multiple items without pressing Ctrl-key within QGraphicsScene?
You want to change the default behavior of QGraphicsScene, so you have to create your own scene class, inheriting QGraphicsScene.
In your class, you'll have to reimplement at least mousePressEvent and handle the item selection yourself.
Here is how you could do it (the inherited scene class is called GraphicsSelectionScene) :
void GraphicsSelectionScene::mousePressEvent(QGraphicsSceneMouseEvent* pMouseEvent) {
QGraphicsItem* pItemUnderMouse = itemAt(pMouseEvent->scenePos().x(), pMouseEvent->scenePos().y());
if (!pItemUnderMouse)
return;
if (pItemUnderMouse->isEnabled() &&
pItemUnderMouse->flags() & QGraphicsItem::ItemIsSelectable)
pItemUnderMouse->setSelected(!pItemUnderMouse->isSelected());
}
Implementing this way, clicking on an item with select it if it is not already, or will unselect it otherwise.
But be careful, implement mousePressEvent is certainly not enough : you'll have to handle the mouseDoubleClickEventas well if you don't want the default behavior.
Your scene will have to be displayed by a QGraphicsView. Here is an example of a view creating it's own scene (MainFrm class is inheriting QGraphicsView) :
#include "mainfrm.h"
#include "ui_mainfrm.h"
#include "graphicsselectionscene.h"
#include <QGraphicsItem>
MainFrm::MainFrm(QWidget *parent) : QGraphicsView(parent), ui(new Ui::MainFrm) {
ui->setupUi(this);
// Create a scene with our own selection behavior
QGraphicsScene* pScene = new GraphicsSelectionScene(this);
this->setScene(pScene);
// Create a few items for testing
QGraphicsItem* pRect1 = pScene->addRect(10,10,50,50, QColor(Qt::red), QBrush(Qt::blue));
QGraphicsItem* pRect2 = pScene->addRect(100,-10,50,50);
QGraphicsItem* pRect3 = pScene->addRect(-200,-30,50,50);
// Make sure the items are selectable
pRect1->setFlag(QGraphicsItem::ItemIsSelectable, true);
pRect2->setFlag(QGraphicsItem::ItemIsSelectable, true);
pRect3->setFlag(QGraphicsItem::ItemIsSelectable, true);
}
maybe it's a hack but it's works for me. In this example you can select multiple items using shift key
void MySceneView::mousePressEvent(QMouseEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier ) //any other condition
event->setModifiers(Qt::ControlModifier);
QGraphicsView::mousePressEvent(event);
}
void MySceneView::mouseReleaseEvent(QMouseEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier ) //any other condition
event->setModifiers(Qt::ControlModifier);
QGraphicsView::mouseReleaseEvent(event);
}

How to insert QPushButton into TableView?

I am implementing QAbstractTableModel and I would like to insert a QPushButton in the last column of each row. When users click on this button, a new window is shown with more information about this row.
Do you have any idea how to insert the button? I know about delegating system but all examples are only about "how to edit color with the combo box"...
You can use
QPushButton* viewButton = new QPushButton("View");
tableView->setIndexWidget(model->index(counter,2), viewButton);
The model-view architecture isn't made to insert widgets into different cells, but you can draw the push button within the cell.
The differences are:
It will only be a drawing of a pushbutton
Without extra work (perhaps quite a bit of extra work) the button won't be highlighted on mouseover
In consequence of #1 above, you can't use signals and slots
That said, here's how to do it:
Subclass QAbstractItemDelegate (or QStyledItemDelegate) and implement the paint() method. To draw the pushbutton control (or any other control for that matter) you'll need to use a style or the QStylePainter::drawControl() method:
class PushButtonDelegate : public QAbstractItemDelegate
{
// TODO: handle public, private, etc.
QAbstractItemView *view;
public PushButtonDelegate(QAbstractItemView* view)
{
this->view = view;
}
void PushButtonDelegate::paint(
QPainter* painter,
const QStyleOptionViewItem & option,
const QModelIndex & index
) const
{
// assuming this delegate is only registered for the correct column/row
QStylePainter stylePainter(view);
// OR: stylePainter(painter->device)
stylePainter->drawControl(QStyle::CE_PushButton, option);
// OR: view->style()->drawControl(QStyle::CE_PushButton, option, painter, view);
// OR: QApplication::style()->drawControl(/* params as above */);
}
}
Since the delegate keeps you within the model-view realm, use the views signals about selection and edits to popup your information window.
You can use setCellWidget(row,column,QWidget*) to set a widget in a specific cell.

Resources