Drag and drop between two QTreeWidgets - qt

My problem is that I have two QTreeWidgets and I would like to do drag and drop from one to an other (and vice-versa). I am able of drag and dropping QTreeWidgetItems, but, when I drop a QTreeWidgetItem that has children, I lose them and only the parent is dropped.
I don't really see how to do it. The only way I have found is reimplementing dropEvent and destroying all teh dropped objects and reconsructing them .. but I don't like that solution because it's slow and the objets are not the same, so it complicates very much the implementation of a Undo/redo feature...
Well, this is what I have:
#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QWidget>
#include <QString>
#include <QTextStream>
#include <QIODevice>
#include <QMainWindow>
#include <QVBoxLayout>
#include "KTreeWidget.h"
#include "KAbstractItem.h"
#include "KItem.h"
#include "KItemGroup.h"
int main(int argc, char* argv[]){
QApplication app(argc, argv);
QMainWindow *window = new QMainWindow();
QWidget* central = new QWidget(window);
QVBoxLayout *layout = new QVBoxLayout();
KTreeWidget* kv = new KTreeWidget(window);
KTreeWidget* trash = new KTreeWidget(window);
layout->addWidget(kv);
layout->addWidget(trash);
central->setLayout(layout);
KItem* kiarr[5];
for (int i = 0 ; i < 5 ; i++){
kiarr[i] = new KItem(kv);
kiarr[i]->setText(0,QString("Item %1").arg(i));
}
KItemGroup* kgarr[5];
for (int i = 0 ; i < 5 ; i++){
kgarr[i] = new KItemGroup(trash);
kgarr[i]->setText(0,QString("Group %1").arg(i));
}
window->setCentralWidget(central);
window->show();
return app.exec();
}
My QTreeWidget:
KtreeWidget.h
#ifndef _KTW_H_
#define _KTW_H_
#include <QTreeWidget>
class KTreeWidget: public QTreeWidget{
Q_OBJECT
private:
QTreeWidgetItem* _header;
public:
KTreeWidget(QWidget* w = NULL);
};
#endif
and KTreeWidget.cc:
#include "KTreeWidget.h"
KTreeWidget::KTreeWidget(QWidget* w):QTreeWidget(w){
setColumnCount(3);
_header = new QTreeWidgetItem(NULL);
_header->setText(0,"Title");
_header->setText(1,"Edit");
_header->setText(2,"Open");
this->setDefaultDropAction(Qt::MoveAction);
setHeaderItem(_header);
setDragEnabled(true);
setAcceptDrops(true);
}
And the items (3 classes, in order to distinguish groups and leafs):
KAbstractItem.h
#ifndef _KABSI_H_
#define _KABSI_H_
#include <QObject>
#include <QTreeWidgetItem>
#include <QTreeWidget>
class KAbstractItem : public QObject, public QTreeWidgetItem{
Q_OBJECT
public:
KAbstractItem(QTreeWidget* p = NULL);
};
#endif
and KAbstractItem.cc
#include "KAbstractItem.h"
KAbstractItem::KAbstractItem(QTreeWidget* p):QTreeWidgetItem(p){}
KItem.h
#ifndef _KI_H_
#define _KI_H_
#include "KAbstractItem.h"
class KItem : public KAbstractItem{
Q_OBJECT
public:
KItem(QTreeWidget* p);
};
#endif
and KItem.cc
#include "KItem.h"
KItem::KItem(QTreeWidget* p):KAbstractItem(p){
setFlags(Qt::ItemIsSelectable
| Qt::ItemIsEditable
| Qt::ItemIsDragEnabled
| Qt::ItemIsUserCheckable
| Qt::ItemIsEnabled);
}
and KItemGroup.h
#ifndef _KIG_H_
#define _KIG_H_
#include "KAbstractItem.h"
class KItemGroup : public KAbstractItem{
Q_OBJECT
public:
KItemGroup(QTreeWidget* p);
};
#endif
and KItemGroup.h
#include "KItemGroup.h"
KItemGroup::KItemGroup(QTreeWidget* p):KAbstractItem(p){
setFlags(Qt::ItemIsSelectable
| Qt::ItemIsEditable
| Qt::ItemIsDragEnabled
| Qt::ItemIsDropEnabled
| Qt::ItemIsUserCheckable
| Qt::ItemIsEnabled);
}
Whenever I do a drop of one of the Items in the first WTreeWifget inside one of the groups of the second one, it works, but it then I move a group to the top QTreeWidget, I lose all the children...
Could you tell me what I am doing wrong?
Thanks in advance!

So, i checked it and it isnt working as required, you are not doing any wrong.
I seems the problem is that QAbstractItemModel (in wich QTreeWidget relies to encode the dragged data internally) mimeData() method is not considering nested items (se that it just encode row + column, but not parent.
It could be even that view is only passing the QModelIndex of the dragged item, to mimeData, thought, it could be better so, cause of not considering parent info...
The only solution i see is that of reimplementing the dropevent. But you dont have to destroy the items.
Use
QTreeWidgetItem * QTreeWidgetItem::takeChild ( int index )
QTreeWidgetItem * QTreeWidget::takeTopLevelItem ( int index )
to take the dragged item
and
void QTreeWidgetItem::insertChild ( int index, QTreeWidgetItem * child )
to drop it
I've checked that this way children are moved right. (See this gist)

This is how I solved it:
When you drag and drop, At creates a new object, differen than yours with the same values for the texts, but it is an other object and yours is simply removed but memory is not freed.
The first thing to do is override removeChild since it is based in indexes and not in addresses so it won't work well with what we are going to do latter. (http://sourceforge.net/p/ckmapper/code/4/tree/trunk/src/KViewer/KAbstractItem.cc#l39)
Then what we need to do is to override the dropEvent of the QTreeWidget. The startegy is the following:
get all the selected items that should be dragged but are not but Qt.
allow Qt to do its dropping.
Find the items on the target that might contain the new objects created by Qt. this can be done with itemAt(event->pos).
Find among the childrne of those items which ones are the dropped ones: this can be done with a dynamic_cast<> because the ones created by Qt are ATreeWidgetItems and ours inherit from this class.
Get its index.
Remove the "parasite" item, and delete it.
insert the selected items at the index from the parasite.
We nedded to override the removeChild because WE are removing children by hand (the selected items) and Qt is removing items (also the selected items) that it should drop latter (but it doesn't). So, if you check the Qt code you will see that it is based in indexes and this is not secure, whereas removing items based on their address is more secure.
I'll post my code latter here, I forgot to commit it ;)
Anyway, Trompa's solution is also valid and seems simpler to me. I just wanted to share my solution too ;)

Related

How do I disable special handling of & on Qt button labels?

I have inherited a virtual keyboard system with an array of buttons, one for each key. The label for each button is a single QChar. When showing the "symbols" keyboard, the code uses an '&' QChar for a key label, but the key shows as blank. I'm sure Qt is processing the '&' as a shortcut key prefix. Similarly, the entered text is shown on another, longer, button label; this label, as well, handles '&' character as an accelerator. Entering "ABC&DEF" is shown as "ABCDEF" with the 'D' underlined.
I have tried building with QT_NO_SHORTCUT #defined, but that made no difference.
Does anyone know of an easy way to disable this special handling of '&'?
The answer is found in Qt doc. QAbstractButton::text:
If the text contains an ampersand character ('&'), a shortcut is automatically created for it. The character that follows the '&' will be used as the shortcut key. Any previous shortcut will be overwritten or cleared if no shortcut is defined by the text. See the QShortcut documentation for details. To display an actual ampersand, use '&&'.
(Emphasize by me.)
QPushButton is derived from QAbstractButton inheriting this behavior.
Sample testQPushButtonAmp.cc:
#include <QtWidgets>
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
QPushButton qBtn("Text with &&");
qBtn.show();
return app.exec();
}
testQPushButtonAmp.pro:
SOURCES = testQPushButtonAmp.cc
QT = widgets
Compiled and tested on cygwin64 on Windows 10:
$ qmake-qt5 testQPushButtonAmp.pro
$ make
$ ./testQPushButtonAmp
Qt Version: 5.9.4
Concerning how to disable this default behavior:
I had a look at woboq.org QAbstractButton::setText().
void QAbstractButton::setText(const QString &text)
{
Q_D(QAbstractButton);
if (d->text == text)
return;
d->text = text;
#ifndef QT_NO_SHORTCUT
QKeySequence newMnemonic = QKeySequence::mnemonic(text);
setShortcut(newMnemonic);
#endif
d->sizeHint = QSize();
update();
updateGeometry();
#ifndef QT_NO_ACCESSIBILITY
QAccessibleEvent event(this, QAccessible::NameChanged);
QAccessible::updateAccessibility(&event);
#endif
}
So, QT_NO_SHORTCUT disables to retrieve the shortcut out of text but it has to be defined when Qt library is built from source. Actually, I'm afraid even with disabled shortcuts, the single & will still become invisible in output.
I digged deeper in woboq.org and found some promising candidates e.g.:
qt_set_sequence_auto_menmonic()
Specifies whether mnemonics for menu items, labels, etc., should be honored or not. On Windows and X11, this feature is on by default; on macOS, it is off. When this feature is off (that is, when b is false), QKeySequence::mnemonic() always returns an empty string.
Note: This function is not declared in any of Qt's header files. To use it in your application, declare the function prototype before calling it.
and a sample in QProxyStyle
#include "textedit.h"
#include <QApplication>
#include <QProxyStyle>
class MyProxyStyle : public QProxyStyle
{
public:
int styleHint(StyleHint hint, const QStyleOption *option = 0,
const QWidget *widget = 0, QStyleHintReturn *returnData = 0) const override
{
if (hint == QStyle::SH_UnderlineShortcut)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
int main(int argc, char **argv)
{
Q_INIT_RESOURCE(textedit);
QApplication a(argc, argv);
a.setStyle(new MyProxyStyle);
TextEdit mw;
mw.resize(700, 800);
mw.show();
//...
}
which I tried in my sample.
Finally, nothing of them achieved the desired effect, i.e. only "&&" was rendered as & but "&" never.
__

Reading QTableView row contents with the same order as are seen NOT stored

We have a QTableView which are filled with some arbitrary data. User can reorder rows of the table by make verticalHeader moveable. Here is a sample code:
#include <QApplication>
#include <QTableWidget>
#include <QDebug>
#include <QVBoxLayout>
#include <QPushButton>
#include <QHeaderView>
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QWidget base;
QTableWidget* tablWid = new QTableWidget(&base);
tablWid->verticalHeader()->setSectionsMovable(true);
tablWid->verticalHeader()->setDragEnabled(true);
//////////////////////////////////////////////////////////////////////////
// Fill the model with some data
tablWid->model()->insertColumn(0);
tablWid->model()->insertRows(0,10);
for (int i = 0; i < 10; ++i)
tablWid->model()->setData(tablWid->model()->index(i, 0), "Item " + QString::number(i));
//////////////////////////////////////////////////////////////////////////
QPushButton* dumpButton = new QPushButton("Dump Model", &base);
QObject::connect(dumpButton, &QPushButton::clicked, [tablWid]()->void {
for (int j = 0; j < tablWid->model()->rowCount();++j){
qDebug() << tablWid->model()->index(j, 0).data().toString();
}
});
QVBoxLayout* baseLay = new QVBoxLayout(&base);
baseLay->addWidget(tablWid);
baseLay->addWidget(dumpButton);
base.show();
return a.exec();
}
We want to read cell contents in the same order as are seen in the QTableView (as seen in the view NOT stored in the model). Currently by calling model->data() we access the cell contents as are stored in the model NOT as seen in the view (ordered are changed by vertical section moves).
How is it possible to read cell contents such this way?
So if I understood it correctly you want to reorder the columns by moving the headers and then want to know how the view looks like.
I believe ( 90% certain ) when you reorder the headers it does not trigger any change in the model! and then if you just start printing the data of the model you will only see the data in the order how it was initially before you swapper/reordered some column with the header.
But you can maintain your own little data structure maintaining the order of the headers and when you will reorder a header the slot columnMoved() will be invooked at that point of time you can utilize the method columnViewportPosition to figure out the positions of all the columns and update your small data structure storing the order of the columns.
So while printing the data you should always assume that headers are in the order as in your own data structure.
Hope that will do what you are looking for!

Why QMimeData is returning invalid QStringList at each call of member func formats()?

I was trying to use a for each [Modern c++ style] but the code is crashed each time!
It was something like :
for(auto &k:mimeData->formats())
{ ... }
And later out of my surprises I found that the QStringList returned by formats is either invalid or completely separate object though the internal data is ought to be same!
So I tried to figure out in more simple example :
#include <iostream>
#include <string>
#include <list>
#include <QCoreApplication>
#include <QMimeData>
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
cout<<boolalpha;
list<string> ls;
cout<<(ls.begin() != ls.end())<<"\n";
QStringList qsl;
cout<<(qsl.begin() != qsl.end())<<"\n";
QMimeData qme;
cout<<(qme.formats().begin() != qme.formats().end())<<"\n";
cout<<"OMG! is it empty? -> "<<qme.formats().empty()<<"\n";
return a.exec();
}
The output is something like :
false
false
true
OMG! is it empty? -> true
Until or unless I take an rvalue reference I cant decide what is happening internally!
I really need a solution to use it with range based for loops, not Qt's foreach!
P.S. I dont want to copy it to avoid O(n).
Looking at the docs, there's no guarantee QMimeData class keeps QStringList of supported formats (http://doc.qt.io/qt-4.8/qmimedata.html#formats) as a field.
The source code supports that (Qt5.4/Src/qtbase/src/corelib/kernel/qmimedata.cpp:593):
QStringList QMimeData::formats() const
{
Q_D(const QMimeData);
QStringList list;
for (int i=0; i<d->dataList.size(); i++)
list += d->dataList.at(i).format;
return list;
}
Therefore this list is constructed on every call to formats(). Farther calls to it will always yield a separate container.
Since you do need to preserve it to traverse it, I'd recommend keeping a local copy of it. Do note that C++11 allows for it to be moved constructed (or in fact - optimized even better).

Doesn't `QPoint` support `std::unique_ptr`?

I want to store QPoint in an object, which should be released automatically.
#include <QCoreApplication>
#include <memory>
#include <QPoint>
using namespace std;
class MyWidget{
public:
vector<std::unique_ptr<QPoint>> _points;
void f(QPoint point){
std::unique_ptr<QPoint> pos(new QPoint(point));
_points.push_back(pos);
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyWidget wid;
wid.f(QPoint(0,0));
return a.exec();
}
The error message is:
F:\Qt\Qt5.5.0\Tools\mingw492_32\i686-w64-mingw32\include\c++\ext\new_allocator.h:120:
error: use of deleted function 'std::unique_ptr<_Tp,
_Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = QPoint; _Dp = std::default_delete]' { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^
Does it mean that I am not supposed to use unique_ptr to store QPoint?
No, the problem is that you can't push_back a copy of a unique_ptr.
Use push_back(std::move(ptr)) or emplacement construction.
Apart from this, I don't see the need to dynamically allocate a value class such as QPoint.
On 64 bit platforms, sizeof(QPoint*) == sizeof(QPoint): the pointer has the same size as the pointed-to-object in the case of QPoint. Moving around the pointers in a vector is no faster than moving around the point values. You're prematurely pessimizing by dynamically allocating these small things one-by-one.
Even on a 32 bit system, the overhead of dynamic allocation will have the performance suffer unnecessarily.
Use a QVector<QPoint> or std::vector<QPoint>, they'll all perform well with points.
TL;DR: Understand the data types that you use, especially if they are simple tuples of integers as QPoint is.

How to get all timezones list in Qt?

Is there any way to get lists of all timezones IST, ET etc.
I have to use them in my application.
The ICU Library is portable and can be used in a Qt application. (It has a C/C++ API.) Among its many other features, is has a TimeZone class that can enumerate the time zones known by the system.
TimeZone Class
It might be overkill if all you need is a simple list, but if you expect to use these time zones and interact with other metadata (locales, etc.), this would be a good solution.
There is a another example using the new QTimeZone class in qt5.2 described here.
They create a custom Widget which lists all known timezones plus their special settings like daylight saving times and such.
The basic code posted there is:
#include <QDebug>
#include <QByteArray>
#include <QDateTime>
#include <QList>
#include <QTimeZone>
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// Fill in combo box.
QList<QByteArray> ids = QTimeZone::availableTimeZoneIds();
foreach (QByteArray id, ids) {
ui->timeZoneComboBox->addItem(id);
}
// Connect combo box to slot to update fields.
connect(ui->timeZoneComboBox, SIGNAL(currentIndexChanged(int)),
SLOT(UpdateFields()));
// Update fields for initial value.
UpdateFields();
}
void Widget::UpdateFields() {
QByteArray id = ui->timeZoneComboBox->currentText().toLatin1();
QTimeZone zone = QTimeZone(id);
// Fill in fields for current time zone.
if (zone.isValid()) {
ui->descriptionLabel->setText(tr("<b>Description:</b> ") + id);
ui->countryLabel->setText(tr("<b>Country:</b> ") +
QLocale::countryToString(zone.country()));
ui->hasDaylightTimeCheckBox->setChecked(zone.hasDaylightTime());
ui->isDaylightTimeCheckBox->setChecked(
zone.isDaylightTime(QDateTime::currentDateTime()));
ui->hasTransitionsCheckBox->setChecked(zone.hasTransitions());
QDateTime zoneTime = QDateTime(
QDate::currentDate(), QTime::currentTime(), zone).toLocalTime();
ui->dateEdit->setDate(zoneTime.date());
ui->timeEdit->setTime(zoneTime.time());
QTimeZone::OffsetData offset = zone.nextTransition(
QDateTime::currentDateTime());
if (offset.atUtc != QDateTime()) {
ui->nextTransitionLabel->setEnabled(true);
ui->nextTransitionLabel->setText(
tr("<b>Next transition:</b> %1").arg(offset.atUtc.toString()));
} else {
ui->nextTransitionLabel->setEnabled(false);
ui->nextTransitionLabel->setText(
tr("<b>Next transition:</b> none"));
}
}
}
Do you need to somehow find it during runtime, or for your source code? If the second case, you can use this list.
Yes try this example
http://www.developer.nokia.com/Community/Wiki/How_to_get_list_of_Time_Zones_in_Qt_Maemo_application

Resources