QStandardItem::clone() not being called for Drag and Drop - qt

I have a Qt application where I am using a QStandardItemModel derived class, and a QTreeView to interact with it. I would like to enable drag and drop to copy and move items around the model. To enable this, I have done the following:
In QStandardItem subclasses that represent leaf nodes: setDragEnabled(true) and override clone() to return a real copy of the item.
In Folder nodes: setDropEnabled(true)
In QTreeView: setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(true);
Drag and Drop works to the extent that it honors which items can be dragged and which can accept drops. However, whether moving or copying it does not create new items using my clone() functions. It only copies the settings and data available to the QStandardItem base class, losing subclass overrides and such.
How do I get the model and views to make use of my clone() functions, or work around this?
Thank you for any assistance.

I think I have found a work around that more or less does what I was expecting the Framework to do.
The header file:
class QextDragDropModel : public QStandardItemModel
{
public:
/**
* Uses the passed indexes, and encodes a list of QStandardItem pointers into
* the mime data.
*/
virtual QMimeData* mimeData(const QModelIndexList &indexes) const;
/**
* Decodes the mimedata, and uses the each QStandardItem::clone() implmentation
* to place a copy at the requested position of the model. If it is a move
* operation Qt will remove the previous item.
*/
virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent);
};
The implmentation:
QMimeData* QextDragDropModel::mimeData(const QModelIndexList &indexes) const
{
// Need to have the base function create the initial mimeData.
// It apparently puts something in there that makes Qt call dropMimeData().
QMimeData* mimeData = QStandardItemModel::mimeData(indexes);
// The raw data that will be placed in the mimeData.
QByteArray mimeBytes;
// Scope the data stream.
{
QDataStream ds(&mimeBytes, QIODevice::WriteOnly);
// The first item encoded will be the number of pointers to expect.
ds << quint32(indexes.size());
// Now for each index get a pointer to the standardItem, and write
// itto the datastream.
for (int i = 0; i < indexes.size(); i++)
{
QStandardItem* ptrItem = itemFromIndex(indexes[i]);
ds.writeRawData((const char*)&ptrItem, sizeof(QStandardItem*));
}
}
// Add the encoded standard item pointers into the mimeData.
mimeData->setData("Qt/QStandardItemArray", mimeBytes);
return mimeData;
}
bool QextDragDropModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
// Get the QStandardItem target of the drop.
QStandardItem* target = itemFromIndex(parent);
// If the target is valid, accepts drops and the mimedata has QStandardItem pointers
// go ahead with decode and insertion. (Checking drop enabled pobably already
// done by the framework before calling this function.)
if ( NULL != target && target->isDropEnabled() && data->hasFormat("Qt/QStandardItemArray") )
{
// Fetch the encoded bytes, create a data stream for decoding,
// and variables to store the output.
QByteArray indexListBytes = data->data("Qt/QStandardItemArray");
QDataStream ds(&indexListBytes, QIODevice::ReadOnly);
quint32 numItems = 0;
// Get the number of items, allocate memory to store pointers to
// them and read the pointer data into that memory.
ds >> numItems;
int byteLen = numItems*sizeof(QStandardItem*);
QStandardItem** stdItems = (QStandardItem**)malloc(byteLen);
ds.readRawData((char*)stdItems, byteLen);
// Add items to the target at a specific child index if requested,
// using thier clone() function to create the items.
for (int i = 0; i < numItems; i++)
{
if ( 0 <= row )
target->insertRow(row, stdItems[i]->clone());
else
target->appendRow(stdItems[i]->clone());
}
// Free memory allocated to store item pointers.
free(stdItems);
return true;
}
return false;
}
For my application, I'll probably add a custom item class with functionality to accept or reject specific items, with the model querying that instead of simply dumping into anything that accepts drops, but for the main question, this is good.

Have you checked that your clone() prototype matches the one from the base class? I had a similar problem with sizeHint, which was not called during layout. The problem was a missing const modifier.

Related

Arduino Dynamic Two-dimensional array

I'm working on an Arduino project where I need to build (and work with) a two-dimensional array at runtime. I've been poking around looking for a solution, but I've had no luck. I found an example of a dynamic one-dimentional array helper here: http://playground.arduino.cc/Code/DynamicArrayHelper, so i've been trying to adopt that code for my use. I created a library using the following code:
My Header file:
#ifndef Dynamic2DArray_h
#define Dynamic2DArray_h
#include "Arduino.h"
class Dynamic2DArray
{
public:
Dynamic2DArray( bool sorted );
//Add an integer pair to the array
bool add( int v1, int v2);
//Clear out (empty) the array
bool clear();
//Get the array item in the specified row, column
int getValue(int row, int col);
//Get the number of rows in the array
int length();
private:
int _rows;
void * _slots;
bool _sorted;
void _sort();
};
#endif
The library's code:
#include "Arduino.h"
#include "Dynamic2DArray.h"
#define ARRAY_COLUMNS 2
int _rows;
void * _slots;
bool _sorted;
Dynamic2DArray::Dynamic2DArray(bool sorted) {
//Set our local value indicating where we're supposed to
//sort or not
_sorted = sorted;
//Initialize the row count so it starts at zero
_rows = 0;
}
bool Dynamic2DArray::add( int v1, int v2) {
//Add the values to the array
//implementation adapted from http://playground.arduino.cc/Code/DynamicArrayHelper
//Allocate memory based on the size of the current array rows plus one (the new row)
int elementSize = sizeof(int) * ARRAY_COLUMNS;
//calculate how much memory the current array is using
int currentBufferSize = elementSize * _rows;
//calculate how much memory the new array will use
int newBufferSize = elementSize * (_rows + 1);
//allocate memory for the new array (which should be bigger than the old one)
void * newArray = malloc ( newBufferSize );
//Does newArray not point to something (a memory address)?
if (newArray == 0) {
//Then malloc failed, so return false
return false;
}
// copy the data from the old array, to the new array
for (int idx = 0; idx < currentBufferSize ; idx++)
{
((byte*)newArray)[idx] = ((byte *)_slots)[idx];
}
// free the original array
if (_slots != NULL)
{
free(_slots);
}
// clear the newly allocated memory space (the new row)
for (int idx = currentBufferSize; idx < newBufferSize; idx++)
{
((byte *)newArray)[idx] = 0;
}
// Store the number of rows the memory is allocated for
_rows = ++_rows;
// set the array to the newly created array
_slots = newArray;
//Free up the memory used by the new array
free(newArray);
//If the array's supposed to be sorted,
//then sort it
if (_sorted) {
_sort();
}
// success
return true;
};
int Dynamic2DArray::length() {
return _rows;
};
bool Dynamic2DArray::clear() {
//Free up the memory allocated to the _slots array
free(_slots);
//And zero out the row count
_rows = 0;
};
int Dynamic2DArray::getValue(int row, int col) {
//do we have a valid row/col?
if ((row < _rows) && (col < ARRAY_COLUMNS)) {
//Return the array value at that row/col
return _slots[row][col];
} else {
//No? Then there's nothing we can do here
return -1;
}
};
//Sorted probably doesn't matter, I can probably ignore this one
void _sort() {
}
The initial assignment of the _slots value is giving me problems, I don't know how to define it so this code builds. The _slots variable is supposed to point to the dynamic array, but I've got it wrong.
When I try to compile the code into my project's code, I get the following:
Arduino: 1.8.0 (Windows 10), Board: "Pro Trinket 3V/12MHz (USB)"
sketch\Dynamic2DArray.cpp: In member function 'int Dynamic2DArray::getValue(int, int)':
sketch\Dynamic2DArray.cpp:83:22: warning: pointer of type 'void *' used in arithmetic [-Wpointer-arith]
return _slots[row][col];
^
Dynamic2DArray.cpp:83: error: 'void*' is not a pointer-to-object type
Can someone please help me fix this code? I've posted the files to https://github.com/johnwargo/Arduino-Dynamic-2D-Array-Lib.
The code you took was for a 1D dynamic array; the modifications for a 2D array are too tricky. Give up these horrors.
I think there is no reason you use dynamic array. You can assume that size max is ROW_MAX * COL_MAX, so you can define a static array int array[ROW_MAX][COL_MAX].
on one hand if you defined a dynamic array, you could free space when you dont use it anymore and take advantage of it for other work. I dont know if this is your case.
on the other hand if you define a static array (on UNO), you have 32kB available on program space, instead of 2kB available on RAM.
Because of the difference 32kB / 2kB, there are very few chances you can get bigger array with dynamic allocation.

How to save a frame using QMediaPlayer?

I want to save an image of a frame from a QMediaPlayer. After reading the documentation, I understood that I should use QVideoProbe. I am using the following code :
QMediaPlayer *player = new QMediaPlayer();
QVideoProbe *probe = new QVideoProbe;
connect(probe, SIGNAL(videoFrameProbed(QVideoFrame)), this, SLOT(processFrame(QVideoFrame)));
qDebug()<<probe->setSource(player); // Returns true, hopefully.
player->setVideoOutput(myVideoSurface);
player->setMedia(QUrl::fromLocalFile("observation.mp4"));
player->play(); // Start receving frames as they get presented to myVideoSurface
But unfortunately, probe->setSource(player) always returns false for me, and thus my slot processFrame is not triggered.
What am I doing wrong ? Does anybody have a working example of QVideoProbe ?
You're not doing anything wrong. As #DYangu pointed out, your media object instance does not support monitoring video. I had the same problem (and same for QAudioProbe but it doesn't interest us here). I found a solution by looking at this answer and this one.
The main idea is to subclass QAbstractVideoSurface. Once you've done that, it will call the method QAbstractVideoSurface::present(const QVideoFrame & frame) of your implementation of QAbstractVideoSurface and you will be able to process the frames of your video.
As it is said here, usually you will just need to reimplement two methods :
supportedPixelFormats so that the producer can select an appropriate format for the QVideoFrame
present which allows to display the frame
But at the time, I searched in the Qt source code and happily found this piece of code which helped me to do a full implementation. So, here is the full code for using a "video frame grabber".
VideoFrameGrabber.cpp :
#include "VideoFrameGrabber.h"
#include <QtWidgets>
#include <qabstractvideosurface.h>
#include <qvideosurfaceformat.h>
VideoFrameGrabber::VideoFrameGrabber(QWidget *widget, QObject *parent)
: QAbstractVideoSurface(parent)
, widget(widget)
, imageFormat(QImage::Format_Invalid)
{
}
QList<QVideoFrame::PixelFormat> VideoFrameGrabber::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const
{
Q_UNUSED(handleType);
return QList<QVideoFrame::PixelFormat>()
<< QVideoFrame::Format_ARGB32
<< QVideoFrame::Format_ARGB32_Premultiplied
<< QVideoFrame::Format_RGB32
<< QVideoFrame::Format_RGB24
<< QVideoFrame::Format_RGB565
<< QVideoFrame::Format_RGB555
<< QVideoFrame::Format_ARGB8565_Premultiplied
<< QVideoFrame::Format_BGRA32
<< QVideoFrame::Format_BGRA32_Premultiplied
<< QVideoFrame::Format_BGR32
<< QVideoFrame::Format_BGR24
<< QVideoFrame::Format_BGR565
<< QVideoFrame::Format_BGR555
<< QVideoFrame::Format_BGRA5658_Premultiplied
<< QVideoFrame::Format_AYUV444
<< QVideoFrame::Format_AYUV444_Premultiplied
<< QVideoFrame::Format_YUV444
<< QVideoFrame::Format_YUV420P
<< QVideoFrame::Format_YV12
<< QVideoFrame::Format_UYVY
<< QVideoFrame::Format_YUYV
<< QVideoFrame::Format_NV12
<< QVideoFrame::Format_NV21
<< QVideoFrame::Format_IMC1
<< QVideoFrame::Format_IMC2
<< QVideoFrame::Format_IMC3
<< QVideoFrame::Format_IMC4
<< QVideoFrame::Format_Y8
<< QVideoFrame::Format_Y16
<< QVideoFrame::Format_Jpeg
<< QVideoFrame::Format_CameraRaw
<< QVideoFrame::Format_AdobeDng;
}
bool VideoFrameGrabber::isFormatSupported(const QVideoSurfaceFormat &format) const
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
return imageFormat != QImage::Format_Invalid
&& !size.isEmpty()
&& format.handleType() == QAbstractVideoBuffer::NoHandle;
}
bool VideoFrameGrabber::start(const QVideoSurfaceFormat &format)
{
const QImage::Format imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat());
const QSize size = format.frameSize();
if (imageFormat != QImage::Format_Invalid && !size.isEmpty()) {
this->imageFormat = imageFormat;
imageSize = size;
sourceRect = format.viewport();
QAbstractVideoSurface::start(format);
widget->updateGeometry();
updateVideoRect();
return true;
} else {
return false;
}
}
void VideoFrameGrabber::stop()
{
currentFrame = QVideoFrame();
targetRect = QRect();
QAbstractVideoSurface::stop();
widget->update();
}
bool VideoFrameGrabber::present(const QVideoFrame &frame)
{
if (frame.isValid())
{
QVideoFrame cloneFrame(frame);
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
const QImage image(cloneFrame.bits(),
cloneFrame.width(),
cloneFrame.height(),
QVideoFrame::imageFormatFromPixelFormat(cloneFrame .pixelFormat()));
emit frameAvailable(image); // this is very important
cloneFrame.unmap();
}
if (surfaceFormat().pixelFormat() != frame.pixelFormat()
|| surfaceFormat().frameSize() != frame.size()) {
setError(IncorrectFormatError);
stop();
return false;
} else {
currentFrame = frame;
widget->repaint(targetRect);
return true;
}
}
void VideoFrameGrabber::updateVideoRect()
{
QSize size = surfaceFormat().sizeHint();
size.scale(widget->size().boundedTo(size), Qt::KeepAspectRatio);
targetRect = QRect(QPoint(0, 0), size);
targetRect.moveCenter(widget->rect().center());
}
void VideoFrameGrabber::paint(QPainter *painter)
{
if (currentFrame.map(QAbstractVideoBuffer::ReadOnly)) {
const QTransform oldTransform = painter->transform();
if (surfaceFormat().scanLineDirection() == QVideoSurfaceFormat::BottomToTop) {
painter->scale(1, -1);
painter->translate(0, -widget->height());
}
QImage image(
currentFrame.bits(),
currentFrame.width(),
currentFrame.height(),
currentFrame.bytesPerLine(),
imageFormat);
painter->drawImage(targetRect, image, sourceRect);
painter->setTransform(oldTransform);
currentFrame.unmap();
}
}
VideoFrameGrabber.h
#ifndef VIDEOFRAMEGRABBER_H
#define VIDEOFRAMEGRABBER_H
#include <QtWidgets>
class VideoFrameGrabber : public QAbstractVideoSurface
{
Q_OBJECT
public:
VideoFrameGrabber(QWidget *widget, QObject *parent = 0);
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
bool isFormatSupported(const QVideoSurfaceFormat &format) const;
bool start(const QVideoSurfaceFormat &format);
void stop();
bool present(const QVideoFrame &frame);
QRect videoRect() const { return targetRect; }
void updateVideoRect();
void paint(QPainter *painter);
private:
QWidget *widget;
QImage::Format imageFormat;
QRect targetRect;
QSize imageSize;
QRect sourceRect;
QVideoFrame currentFrame;
signals:
void frameAvailable(QImage frame);
};
#endif //VIDEOFRAMEGRABBER_H
Note : in the .h, you will see I added a signal taking an image as a parameter. This will allow you to process your frame anywhere in your code. At the time, this signal took a QImage as a parameter, but you can of course take a QVideoFrame if you want to.
Now, we are ready to use this video frame grabber:
QMediaPlayer* player = new QMediaPlayer(this);
// no more QVideoProbe
VideoFrameGrabber* grabber = new VideoFrameGrabber(this);
player->setVideoOutput(grabber);
connect(grabber, SIGNAL(frameAvailable(QImage)), this, SLOT(processFrame(QImage)));
Now you just have to declare a slot named processFrame(QImage image) and you will receive a QImage each time you will enter the method present of your VideoFrameGrabber.
I hope that this will help you!
After Qt QVideoProbe documentation:
bool QVideoProbe::setSource(QMediaObject *mediaObject)
Starts monitoring the given mediaObject.
If there is no media object associated with mediaObject, or if it is
zero, this probe will be deactivated and this function wil return
true.
If the media object instance does not support monitoring video, this
function will return false.
Any previously monitored objects will no longer be monitored. Passing
in the same object will be ignored, but monitoring will continue.
So it seems your "media object instance does not support monitoring video"
TL;DR: https://gist.github.com/JC3/a7bab65acbd7659d1e57103d2b0021ba (only file)
I had a similar issue (5.15.2; although in my case I was on Windows, was definitely using the DirectShow back-end, the probe attachment was returning true, the sample grabber was in the graph, but the callback wasn't firing).
I never figured it out but needed to get something working so I kludged one out of a QAbstractVideoSurface, and it's been working well so far. It's a bit simpler than some of the other implementations in this post, and it's all in one file.
Note that Qt 5.15 or higher is required if you intend to both process frames and play them back with this, since the multi-surface QMediaPlayer::setVideoOutput wasn't added until 5.15. If all you want to do is process video you can still use the code below as a template for pre-5.15, just gut the formatSource_ parts.
Code:
VideoProbeSurface.h (the only file; link is to Gist)
#ifndef VIDEOPROBESURFACE_H
#define VIDEOPROBESURFACE_H
#include <QAbstractVideoSurface>
#include <QVideoSurfaceFormat>
class VideoProbeSurface : public QAbstractVideoSurface {
Q_OBJECT
public:
VideoProbeSurface (QObject *parent = nullptr)
: QAbstractVideoSurface(parent)
, formatSource_(nullptr)
{
}
void setFormatSource (QAbstractVideoSurface *source) {
formatSource_ = source;
}
QList<QVideoFrame::PixelFormat> supportedPixelFormats (QAbstractVideoBuffer::HandleType type) const override {
return formatSource_ ? formatSource_->supportedPixelFormats(type)
: QList<QVideoFrame::PixelFormat>();
}
QVideoSurfaceFormat nearestFormat (const QVideoSurfaceFormat &format) const override {
return formatSource_ ? formatSource_->nearestFormat(format)
: QAbstractVideoSurface::nearestFormat(format);
}
bool present (const QVideoFrame &frame) override {
emit videoFrameProbed(frame);
return true;
}
signals:
void videoFrameProbed (const QVideoFrame &frame);
private:
QAbstractVideoSurface *formatSource_;
};
#endif // VIDEOPROBESURFACE_H
I went for the quickest-to-write implementation possible so it just forwards supported pixel formats from another surface (my intent was to both probe and play back to a QVideoWidget) and you get whatever format you get. I just needed to grab subimages into QImages though, which handles most common formats. But you could modify this to force any formats you want (e.g. you might want to just return formats supported by QImage or filter out source formats not supported by QImage), etc.).
Example set up:
QMediaPlayer *player = ...;
QVideoWidget *widget = ...;
// forward surface formats provided by the video widget:
VideoProbeSurface *probe = new VideoProbeSurface(...);
probe->setFormatSource(widget->videoSurface());
// same signal signature as QVideoProbe's signal:
connect(probe, &VideoProbeSurface::videoFrameProbed, ...);
// the key move is to render to both the widget (for viewing)
// and probe (for processing). fortunately, QMediaPlayer can
// take a list:
player->setVideoOutput({ widget->videoSurface(), probe });
Notes
The only really sketchy thing I had to do was const_cast the QVideoFrame on the receiver side (for read-only access), since QVideoFrame::map() isn't const:
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
...;
const_cast<QVideoFrame&>(frame).unmap();
}
But the real QVideoProbe would make you do the same thing so, I don't know what's up with that -- it's a strange API. I ran some tests with sw, native hw, and copy-back hw renderers and decoders and map/unmap in read mode seem to be functioning OK, so, whatever.
Performance-wise, the video will bog down if you spend too much time in the callback, so design accordingly. However, I didn't test QueuedConnection, so I don't know if that'd still have the issue (although the fact that the signal parameter is a reference would make me wary of trying it, as well as conceivable issues with the GPU releasing the memory before the slot ends up being called). I don't know how QVideoProbe behaves in this regard, either. I do know that, at least on my machine, I can pack and queue Full HD (1920 x 1080) resolution QImages to a thread pool for processing without slowing down the video.
You probably also want to implement some sort of auto-unmapper utility object for exception safe unmap(), etc. But again, that's not unique to this, same thing you'd have to do with QVideoProbe.
So hopefully that helps somebody else.
Example QImage Use
PS, example of packing arbitrarily-formatted QVideoFrames into a QImage in:
void MyVideoProcessor::onFrameProbed(const QVideoFrame &frame) {
if (const_cast<QVideoFrame&>(frame).map(QAbstractVideoBuffer::ReadOnly)) {
auto imageFormat = QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat());
QImage image(frame.bits(), frame.width(), frame.height(), frame.bytesPerLine(), imageFormat);
// *if* you want to use this elsewhere you must force detach:
image = image.copy();
// but if you don't need to use it past unmap(), you can just
// use the original image instead of a copy.
// <---- now do whatever with the image, e.g. save() it.
// if you *haven't* copied the image, then, before unmapping,
// kill any internal data pointers just to be safe:
image = QImage();
const_cast<QVideoFrame&>(frame).unmap();
}
}
Notes about that:
Constructing a QImage directly from the data is fast and essentially free: no copies are done.
The data buffers are only technically valid between map and unmap, so if you intend to use the QImage outside of that scope, you'll want to use copy() (or anything else that forces a detach) to force a deep copy.
You also probably want to ensure that the original not-copied QImage is destructed before calling unmap. It's unlikely to cause problems but it's always a good idea to minimize how many invalid pointers are hanging around at any given time, and also the QImage docs say "The buffer must remain valid throughout the life of the QImage and all copies that have not been modified or otherwise detached from the original buffer". Best to be strict about it.

Qt does not update several QTableView cells when editing in the code

I have a simple logic, that basically goes like this: a few entries are filtered from source model (my custom QAbstractTableModel) and presented to the user using QSortFilterProxyModel, which does have only modified filterAcceptsRow function. This presentation is done using simple dialog. User selects desired entries from filtered ones and those selected entries from model must be updated (actually two fields have to be modified). So simplified code goes like this:
QModelIndexList selectedRows = myProxyModel->selectionModel()->selectedRows();
for (int i = 0; i < selectedRows.count(); i++) {
myProxyModel->setData(myProxyModel->index(selectedRows.at(i).row(), (int) LoanStatusCol, QModelIndex()), (int) ReturnedLoan, Qt::EditRole);
myProxyModel->setData(myProxyModel->index(selectedRows.at(i).row(), (int) LoanRetEntriesCol, QModelIndex()), (lastEntryNo + 1), Qt::EditRole);
}
However, this does not work. And each time behavour is quite weird. What I noticed is that, when it gets to second selected row in this cycle and when it reaches setData() code in the model:
bool TransactionModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
it returns invalid index. However, when I swaped these two setData() code lines, one row was updated, but second row was not - due to invalid index. I do not know, whether I explained that correctly, but probably this should be my silly mistake, because I am new at this.
UPDATE:
Since model consists of QList data, where Transaction is a custom class that defines fields of entry, I created a function, that updated underlying entry by column number (so to say...). I use function setValueByColumnNo. I just could not find a better way to do that, when working with lists of custom classes.
bool TransactionModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid()) {
return false;
}
if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) {
transactionData[index.row()].setValueByColumnNo(index.column(), value);
emit dataChanged(index, index);
return true;
}
return false;
}
Any ideas?
Thanks.

QML ListModel file read/write and good practices

I am just starting to learn QML and I am kinda lost at what should I do when I want to read ListModel from settings.
My dilemma is:
1) If I define the model in C++ code I have no problems loading it (I have done similar stuff loads of times) but I am sacrificing my time to actually write (and later update) model code and then recompile each time I need to do it.
2) My other idea is to read settings file into QList of QVariantMap and create the model in QML file reading this list using javascript. This way I will only need 2 C++ functions, one to read the file section by section and one to write it. But as I said - I am only starting QML programming and not sure if it is sane or discouraged.
Can someone comment on good practices when one needs dynamic QML ListModel?
UPD: I seem to need to clarify the question:
Do I even need C++ data model if the stuff is simple enough to read from settings and then parse directly into ListModel via Javascript? Or are there pitfalls I am not aware of that make C++ way the only reasonable choice?
UPD2 After some mroe researching I am tempted to go with LocalStorage and forgo c++ altogether
Making an existing C++ model editable is quit easy, since you get everything from Qt.
You have the following in .h
class MyModel : public QAbstractListModel
{
Q_OBJECT
public:
enum MyRoles {
SomeRole = Qt::UserRole,
// ...
}
// ...
bool setData(const QModelIndex &index, const QVariant &value, int role);
in .cpp
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
switch (role) {
case SomeRole:
// your writer code
emit dataChanged(index, index, (QVector<int>(0) << SomeRole));
return true;
case SomeOtherRole:
// ...
return true;
default:
qCritical() << "MyModel.setData: Unknown role:" << role;
return false;
}
}
Now your can use QML internals to perfrom a write.
MyModelDelegate {
Button {
text: somerole
onClicked: {
// this will call setData with the correct row index and SomeRole
somerole = "some other value"
}
}
}
This C++ code is only recompiled when you add new roles, which should not happen very often or your write method changed.

Track QStandardItem lifetime

I'm writing some wrappers over QStandardItemModel. Is it possible to track lifetime (delete events) of QStandardItems?
I think that the only way is to interhit QObject + QStandardItem. But I don't want to do it for some reasons.
UPDATE:
I need to delete my object, that contains pointer to QStandardItem, when this item removed from model.
Here is solution. But I want to do the same for external (not mine) QStandardItem.
class ItemWrap : public QObject, public QStandardItem
{
// ...
};
class MyObject : public QObject
{
MyObject( ItemWrap *item ) // I need MyObject( QStandardItem *item )
{
connect( item, &QObject::destroyed, this, &MyObject::deletelater );
}
// ...
};
As is often the case in Qt, there are objects that are not QObjects, but that are managed by a QObject (or otherwise accessible via one). You need to make MyObject monitor the model the items are in. The code below could be a starting point.
Another approach, not implemented but certainly feasible, is to dynamically replace all items in a model with copies that are instances that you yourself created. By monitoring the relevant model signals, you can be notified of all item additions and replace items with instances that you are a factory for. It would be a thinly veiled dependency injection into a QStandardItemModel.
The lowest-overhead approach would be to move the signals and slots from individual objects to the model itself, so that you avoid the overhead of having potentially very many QObjects, while still retaining their signal/slot functionality.
class MyObject : public QObject {
Q_OBJECT
QStandardItem * m_item;
Q_SLOT void onRowsAboutToBeRemoved(const QModelIndex & parent, int start, int end) {
if (m_item->parent() == parent &&
m_item->index().row() >= start &&
m_item->index().row() <= end) onItemGone;
}
Q_SLOT void onColumnsAboutToBeRemoved(const QModelIndex & parent, int start, int end) {
if (m_item->parent() == parent &&
m_item->index().column() >= start &&
m_item->index().column() <= end) onItemGone;
}
Q_SLOT void onItemGone() {
m_item = 0;
deleteLater();
}
public:
MyObject(QStandardItem* item, QObject * parent = 0) :
QObject(parent), m_item(item)
{
Q_ASSERT(m_item.model());
connect(m_item.model(), SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)));
connect(m_item.model(), SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
SLOT(onColumnsAboutToBeRemoved(QModelIndex,int,int)));
connect(m_item.model(), SIGNAL(modelAboutToBeReset()), SLOT(onItemGone());
connect(m_item.model(), SIGNAL(destroyed()), SLOT(onItemGone());
}
};
Every data model has signals about changes in the model, see documentation of QAbstractItemModel and this is what you need.
Note that QStandardItem is not a QObject so it doesn't have any signals or slots.
Can you please clarify what you mean by track lifetime.
Why do you want to subclass QObject?
Are you going to use SIGNALS and SLOTs? If no then I don't think it is going to be of much use.
Apart from that you can subclass QStandardItem and track the lifetime using your constructor and destructor or a suitable function?

Resources