Renew QQuickImageProvider request - qt

To show some Pixmap in QML from a C++ model, I used a QQuickImageProvider:
class ImageProvider : public QQuickImageProvider
{
public:
ImageProvider(MyModel *model) : QQuickImageProvider(QQuickImageProvider::Pixmap), _model(model) { }
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override
{
Q_UNUSED(requestedSize);
int width = 160;
int height = 120;
QString name = id.left(id.indexOf("*"));
if (size) *size = QSize(width, height);
return _model->findThumbnail(name); // retrieve image in the model
}
private:
MyModel *_model;
};
The model is ready on startup, but the images are populated at run-time, slowly.
When the QML page is loaded it requests to the image provider the pixmaps but they are not ready yet.
After some time they are available in the model, but I don't know how to tell to the QML Image object to renew the request to the provider.

QQuickImageProvider has a well documented asynchronous mode, which you can force by passing QQmlImageProviderBase::ForceAsynchronousImageLoading to the provider constructor.
The way I understand it is that requests will be handled by a dedicated thread and delivered when done. Which means that what you should do is stall the thread until the image data can be provided, ideally busy with fetching said data.
This also would cause images to be loaded in the order they are requested by the application rather than some other order, presumably that of the model items.

The solution is simple, create a new role that indicates that the image is fully loaded.
setData(index, false, IsLoadedRole);
// finished loading
setData(index, true, IsLoadedRole);
*.qml
Image {
source: isLoaded ? "image://MyImageProvider/" : ""}
}

Related

Capture server response with QWebEngineView

I'm trying to create a Dialog in Qt which loads a URL (which I do not want to expose to the end-user, hence a Dialog). Once the user has entered their credentials on the page, the server returns a redirect URL which I want to capture. How can I do this?
QtWebkit made this easy to do as QWebView had a QNetworkAccessManager object. But with QtWebEngine, the QWebEngineView class does not have this capability. The former also allowed HTTP headers to be set for any requests by using the QNetworkRequest class and then load requests with these specific requests in QWebView. How do I do this with QWebEngineView?
Since Qt 5.6 the proposed solution for what you are trying to achieve with QWebEngineView is QWebEngineUrlRequestInterceptor:
Implementing the QWebEngineUrlRequestInterceptor interface and installing the interceptor on the profile enables intercepting, blocking, and modifying URL requests before they reach the networking stack of Chromium.
It is an abstract class which means you need to subclass it to get what you want:
#include <QWebEngineUrlRequestInterceptor>
#include <QDebug>
class RequestInterceptor : public QWebEngineUrlRequestInterceptor
{
public:
explicit RequestInterceptor(QObject * parent = Q_NULLPTR) : QWebEngineUrlRequestInterceptor(parent) {}
virtual void interceptRequest(QWebEngineUrlRequestInfo & info) Q_DECL_OVERRIDE;
};
void RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo & info)
{
// Intercepting the requested URL
QUrl url = info.requestUrl();
qDebug() << "Request URL: " << url;
// Optionally redirect the request URL but it only works for requests
// without payload data such as GET ones
info.redirect(QUrl("https://www.google.com"));
// Set HTTP header
QByteArray httpHeaderName = "SomeHeaderName";
QByteArray httpHeaderValue = "SomeHeaderValue";
info.setHttpHeader(httpHeaderName, httpHeaderValue);
}
Then you need to register the pointer to this interceptor in QWebEngineProfile for a particular QWebEnginePage, like this:
QWebEngineView * view = new QWebEngineView;
RequestInterceptor * interceptor = new RequestInterceptor(view);
QWebEngineProfile * profile = new QWebEngineProfile(view);
profile->setRequestInterceptor(interceptor);
QWebEnginePage * page = new QWebEnginePage(profile, view);
view->setPage(page);

Qt - Saving a custom QGraphicsScene with custom QGraphicsItems

I created a public QGraphicsItem Node class which has getter/setter methods for a bunch of properties relevant to my application. The application is a diagram editor in which the users design models. This Node class is used to populate an also public QGraphicsScene Diagram class.
I'm now trying to implement a load/save mechanism in the app that enables users to save and reuse the models - editing them as they were when they saved. I'm having some trouble figuring out how to attack this problem.
I already know I have to get the relevant properties for the QGraphicsItem objects and save those to file, and then on load, reconstruct the scene using the data I saved. My question is: when I use the QGraphicsScene::items() function, it returns a QList<QGraphicsItem *>. What can I do to have it return a QList<Node *>?
If I get a list of all the nodes I appended to the scene, I know what to do next.
I began to realise that I will probably have to reimplement the items() function in my Diagram class, but I was hoping I could get away with it more easily. In this case, can someone please explain how to do it?
I would suggest to implement the QGraphicsItem::type() method and using qgraphicsitem_cast to cast into the desired class. In your case, you could subclass all your custom GraphicsItems from a common base class.
Your base class would look like this:
class MyGraphicsItem: public QGraphicsItem
{
enum { Type = UserType + 1 };
int type() const { return Type; }
};
Your Node and your Link class would inherit this base:
class Node : public MyGraphicsItem
{
// ...
};
class Link : public MyGraphicsItem
{
// ...
};
Somewhere else you could cast a QGraphicsItem into your base class MyGraphicsItem like this for example:
QList<QGraphicsItem*> allItems = graphicsScene->items();
foreach (QGraphicsItem *item, allItems) {
// Using qgraphicsitem_cast
MyGraphicsItem* graphicsItem = qgraphicsitem_cast<MyGraphicsItem*>(item);
if (graphicsItem) {
// Do something with MyGraphicsItem
}
}
While the items function returns a list of QGraphicsItem pointers, you could try either dynamic_casting to check if it's a Node pointer or use the Qt metadata and the type() function.
However, the method I often use, which can help you in other ways too, is to maintain a unique id for every type of object and a static lists of item Ids. For example: -
class BaseItem : public QGraphicsItem
{
public:
BaseItem(QGraphicsItem* parent)
: QGraphicsItem(parent), m_Id(m_NextId++) // initialising the object's unique Id
{
}
unsigned int ID() const
{
return m_Id;
}
private:
static unsigned int m_NextId; // the next Object Id, initialised to 0 in implementation
unsigned int m_Id; // the object's unique Id
}
class Node : public BaseItem
{
public:
Node(QGrahicsItem* parent)
: BaseItem(parent)
{
m_NodesList.push_back(m_Id);
}
private:
static QList<unsigned int> m_sNodesList; // all Node ids in the scene
QList<unsigned int> m_linkedNodeList; // linked nodes
}
All items added to the GraphicsScene are inherited from the BaseItem
You also have a static list of Node Ids, so you can iterate through all of them for loading / saving and you can add a static helper function in the base class to return a list of Nodes, by searching through the List of Node Ids and matching them to their node pointers in the scene.
This architecture also allows you to include a list of linked node ids in each Node object, rather than using the parent / child system of Qt, which in the case of a node diagram, isn't always what is needed.
Overall, I've used this architecture for many QGraphicsScene applications and it has really made development very easy for items that have complex links to other items.

Beginning Qt Model / View with Nested Object Collections

I'm primarily a .Net developer and have been investigating Qt for a while now. I'm now at the stage of trying to implement the model / view framework in Qt. I think I have a grasp of the basic principles but am unclear of how to hang things together in a more complex UI where widgets need to communicate with each other. Given the following:
// 'domain' model classes
class NestedDomainModel1
{
public:
NestedDomainModel1();
QString name() const;
void setName(const QString& newName);
// other properties
private:
QString m_name;
};
class NestedDomainModel2
{
public:
NestedDomainModel2();
QString name() const;
void setName(const QString& newName);
// other properties
};
class MyDomainModel
{
public:
MyDomainModel();
void addNestedModel1(const NestedDomainModel1& modelToAdd);
NestedDomainModel& nestedObjectModel1At(int index);
int nestedObjectModel1Count() const;
// repeat for model 2
private:
QList<NestedDomainModel1> m_nestedModels1;
QList<NestedDomainModel2> m_nestedModels2;
};
// 'GUI' classes
class MainWindow : public QMainWindow
{
private:
MyDomainModel* m_model;
MyTreeViewWidget* m_treeWidget; // -> this sits in a left dock window
MyInfoDisplayWidget* m_infoWidget; // -> this sits in a right dock window and display details about the item selected in the tree
};
class MyDomainModelTreeModel : public QAbstractItemModel
{
public:
explicit MyDomainModelTreeModel(MyDomainModel* model);
// required overrides for QAbstractItemModel
private:
MyDomainModel* m_model;
};
class MyTreeViewWidget : public QWidget
{
public:
// Take a pointer to the domain model and create a model for the 'view'.
// Will create a tree like:
// Nested Objects 1
// |- object 001
// |- object 002
// |- you get the idea
// Nested Objects 2
// |- other object 001
// |- more of the same
explicit MyTreeViewWidget(MyDomainModel* model);
public slots:
// Used to notify widget when an item is added to the underlying model.
void nestedModel1Added();
void nestedModel2Added();
signals:
void nestedModel1Selected(NestedDomainModel1& selectedModel);
void nestedModel2Selected(NestedDomainModel2& selectedModel);
private slots:
// connect to tree view event when an item is selected and if all ok, emit one of the selected events
void onTreeItemSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
QTreeView* m_treeView;
MyDomainModelTreeModel* m_treeModel;
};
class MyNestedClass1ViewModel : QAbstractItemModel
{
public:
explicit MyNestedClass1ViewModel(NestedDomainModel1* model);
setModel(NestedDomainModel1* model);
// required overrides for QAbstractItemModel
private:
NestedDomainModel1* m_model
};
class MyInfoDisplayWidget : public QWidget
{
public:
explicit MyInfoDisplayWidget(QWidget* parent = 0);
public slots:
// this is connected to the 'tree' widget signal in MainWindow
void setModel(NestedDomainModel1& selectedModel);
};
The basic premise of the UI is something similar in feel to Visual Studio. The tree is similar to the Solution Explorer and the 'info display' is similar to the properties window.
Is this how you use the model / view framework? For those familar with WPF / Silverlight development, is the model / view framework similar to MVVM (at a high level) in that it is the 'model of the view' and wraps / contains the domain model?
Is this how you connect the widgets using the model / view framework (ie. one widget passes a pointer or reference of the model to another)? Or should I be using the SelectionModel? Does that work since the tree model contains different types of objects?
How do you identify the root nodes? For instance, when a MyNestedObject1 is created and needs to be added to tree do I rely on the knowledge that root node is at a model index QModelIndex(0, 0) (ie. row 0 with an invalid parent index)?
I'm finding the terminology you're using a bit awkward, for example MyNestedClass1ViewModel is just a model. I'm not sure what a ViewModel would be.
What you're missing in this example is an actual view. MyTreeViewWidget is just a dumb widget that isn't actually a view in Qt terms at all, it's essdentialy just a dumb 'canvas' that you want to display data in. So the way to do this is:
You have underlying data in ordinary objects such as NestedDomainModel2. These are not Models in the Qt sense though and I wouldn't name them as such. They're just ordinary objects and don't implement any of the MVC interfaces.
Your MyNestedClass1ViewModel, which is a Qt model class. It accesses the underlying data objects above (1) in the implementation of it's data() and setData() methods.
A view class subclassed from QAbstractItemView. This is what you're missing. It has all the magic hooks to plug into the API of the model class from (2) above. It gets signals from the model telling it when there have been changed, which invoke methods such as dataChanged(), rowsInserted(). You implement these methods to make appropriate changes in your display widget below in point (4).
Your display widget. It doesn't implement any of the model/view API itself and is updated by your view. If it's interactive and can be used to change model data, you do that by calling setData(), insertRows(), removeRows(), etc on the model. The display changes will automatically propagate back to the widget via the view. Be careful not to generate infinite loops of changes propagating from widget->model->view->widget->model->view etc.
I have done a similar thing to use a QGraphicsScene/QGraphicsView to display items in a model. Despite it's name QGraphicsView isn't part of the model/view framework, so I implemented a custom view class which drew the model data on the QGraphicsScene.
Here's my code, in Python. It draws worlds on a map for an SF wargame:
class WorldItemView(QtGui.QAbstractItemView):
""" Hidden view which interfaces between the model and the scene.
"""
def __init__(self, model, parent=None):
QtGui.QAbstractItemView.__init__(self, parent)
self.hide()
self.setModel(model)
self.my_model = model
self.scene = MapScene(self.my_model)
self.resetWorlds()
def dataChanged(self, topLeft, bottomRight):
top_row = topLeft.row()
bottom_row = bottomRight.row()
#debug_log("Top row " + str(top_row) + " Bottom row " + str(bottom_row))
for row in range(top_row, (bottom_row + 1)):
self.scene.worldChanged(row)
def rowsInserted(self, parent, start, end):
for row in range(start, (end + 1) ):
pmi = self.my_model.getPMI(row)
self.scene.insertWorld(pmi)
def rowsAboutToBeRemoved(self, parent, start, end):
for row in range(start, (end + 1)):
self.scene.removeWorld(row)
def resetWorlds(self):
self.scene.clearWorlds()
# Add worlds to scene
last_row = self.my_model.rowCount() - 1
self.rowsInserted(None, 0, last_row)
I hope that helped.

Filtering in QFileDialog

I would like to filter the files that are shown in a QFileDialog more specifically than just by file extensions. The examples I found in the Qt documentation only show filters like Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml) and such. In addition to this I would also like to specify a filter for files that should not show up in the file dialog, e.g. XML files (*.xml) but not Backup XML files (*.backup.xml).
So the problem I have is that I would like to show some files in the file dialog that have certain file extension, but I would not like to show other files with a specific file name suffix (and the same file extension).
For example:
Files to show:
file1.xml
file2.xml
Files not to show:
file1.backup.xml
file2.backup.xml
I would like to ask if it is possible to define filters like these for a QFileDialog?
I believe what you can do is:
Create a custom proxy model. You can use QSortFilterProxyModel as a base class for your model;
In the proxy model override the filterAcceptsRow method and return false for files which have the ".backup." word in their names;
Set new proxy model to the file dialog: QFileDialog::setProxyModel;
Below is an example:
Proxy model:
class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
};
bool FileFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex index0 = sourceModel()->index(sourceRow, 0, sourceParent);
QFileSystemModel* fileModel = qobject_cast<QFileSystemModel*>(sourceModel());
return fileModel->fileName(index0).indexOf(".backup.") < 0;
// uncomment to call the default implementation
//return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
}
dialog was created this way:
QFileDialog dialog;
dialog.setOption(QFileDialog::DontUseNativeDialog);
dialog.setProxyModel(new FileFilterProxyModel);
dialog.setNameFilter("XML (*.xml)");
dialog.exec();
The proxy model is supported by non-native file dialogs only.
The solution of #serge_gubenko is working well. Create your own ProxyModel by inheriting from the QSortFilterProxyModel.
class FileFilterProxyModel : public QSortFilterProxyModel
{
protected:
virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
};
bool FileFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
// Your custom acceptance condition
return true;
}
Just make sure to set DontUseNativeDialog before setting the Proxy model (Edit: #serge_gubenkos answer does that now). Native dialogs do not support custom ProxyModels.
QFileDialog dialog;
dialog.setOption(QFileDialog::DontUseNativeDialog);
dialog.setProxyModel(new FileFilterProxyModel);
dialog.setNameFilter("XML (*.xml)");
dialog.exec();
It took quite some time for me to find this out. This was written here
Okay, I've used it with QFileDialog object. And this only shows me the files listed in the appropriate directory. It is excellent to just choose the files to be processed. For example, an XML file, a PNG image, etcetera.
Here I present my example
OlFileDialog QFileDialog (this);
QString slFileName;
olFileDialog.setNameFilter (tr ("Files (* xml)"));
olFileDialog.setFileMode (QFileDialog :: anyfile);
olFileDialog.setViewMode (QFileDialog :: Detail);
if (olFileDialog.exec ())
olFileDialog.selectFile (slFileName);
else
return;
The dialog box will display only presents xml files.

Blocking a Qt application during downloading a short file

I'm writing an application using Qt4.
I need to download a very short text file from a given http address.
The file is short and is needed for my app to be able to continue, so I would like to make sure the download is blocking (or will timeout after a few seconds if the file in not found/not available).
I wanted to use QHttp::get(), but this is a non-blocking method.
I thought I could use a thread : my app would start it, and wait for it to finish. The thread would handle the download and quit when the file is downloaded or after a timeout.
But I cannot make it work :
class JSHttpGetterThread : public QThread
{
Q_OBJECT
public:
JSHttpGetterThread(QObject* pParent = NULL);
~JSHttpGetterThread();
virtual void run()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
m_pHttp->setHost("127.0.0.1");
m_pHttp->get("Foo.txt", &m_GetBuffer);
exec();
}
const QString& getDownloadedFileContent() const
{
return m_DownloadedFileContent;
}
private:
QHttp* m_pHttp;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
private slots:
void onRequestFinished(int Id, bool Error)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
}
};
In the method creating the thread to initiate the download, here is what I'm doing :
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait();
But that doesn't work and my app keeps waiting. It looks lit the slot 'onRequestFinished' is never called.
Any idea ?
Is there a better way to do what I'm trying to do ?
Instead of using a thread you can just go into a loop which calls processEvents:
while (notFinished) {
qApp->processEvents(QEventLoop::WaitForMore | QEventLoop::ExcludeUserInput);
}
Where notFinished is a flag which can be set from the onRequestFinished slot.
The ExcludeUserInput will ensure that GUI related events are ignored while waiting.
A little late but:
Do not use these wait loops, the correct way is to use the done() signal from QHttp.
The requestFinished signal from what I have seen is just for when your application has finished the request, the data may still be on its way down.
You do not need a new thread, just setup the qhttp:
httpGetFile= new QHttp();
connect(httpGetFile, SIGNAL(done(bool)), this, SLOT(processHttpGetFile(bool)));
Also do not forget to flush the file in processHttpGetFile as it might not all be on the disk.
you have to call QThread::quit() or exit() if you are done - otherwise your thread will run forever...
I chose to implement David's solution, which seemed to be the easiest.
However, I had handle a few more things :
I had to adapt the QEventLoop enum values for Qt4.3.3 (the version I'm using);
I had to track the request Id, to make sure to exit the while loop when the download request is finished, and not when another request is finished;
I added a timeout, to make sure to exit the while loop if there is any problem.
Here is the result as (more or less) pseudo-code :
class BlockingDownloader : public QObject
{
Q_OBJECT
public:
BlockingDownloaderBlockingDownloader()
{
m_pHttp = new QHttp(this);
connect(m_pHttp, SIGNAL(requestFinished(int, bool)), this, SLOT(onRequestFinished(int, bool)));
}
~BlockingDownloader()
{
delete m_pHttp;
}
QString getFileContent()
{
m_pHttp->setHost("www.xxx.com");
m_DownloadId = m_pHttp->get("/myfile.txt", &m_GetBuffer);
QTimer::singleShot(m_TimeOutTime, this, SLOT(onTimeOut()));
while (!m_FileIsDownloaded)
{
qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);
}
return m_DownloadedFileContent;
}
private slots:
void BlockingDownloader::onRequestFinished(int Id, bool Error)
{
if (Id == m_DownloadId)
{
m_DownloadedFileContent = "";
m_DownloadedFileContent.append(m_GetBuffer.buffer());
m_FileIsDownloaded = true;
}
}
void BlockingDownloader::onTimeOut()
{
m_FileIsDownloaded = true;
}
private:
QHttp* m_pHttp;
bool m_FileIsDownloaded;
QBuffer m_GetBuffer;
QString m_DownloadedFileContent;
int m_DownloadId;
};
I used QNetworkAccsessManager for same necessity. Because this class managing connections RFC base (6 proccess same time) and non-blocking.
http://qt-project.org/doc/qt-4.8/qnetworkaccessmanager.html
How about giving the GUI some amount of time to wait on the thread and then give up.
Something like:
JSHttpGetterThread* pGetter = new JSHttpGetterThread(this);
pGetter->start();
pGetter->wait(10000); //give the thread 10 seconds to download
Or...
Why does the GUI thread have to wait for the "downloader thread" at all? When the app fires up create the downloader thread, connect the finished() signal to some other object, start the downloader thread, and return. When the thread has finished, it will signal the other object which can resume your process.

Resources