Custom widget in Qt Designer with pre-existing layout in frame - c++

A project uses custom frame widget that implements a kind of "cover" that hides widgets (think of it as of security cover that prevents pressing buttons).This is a required visual design. Qt version is 4.6
#ifndef CQFRAME_H
#define CQFRAME_H
#include <QFrame>
#include <QtDesigner/QDesignerExportWidget>
//! [0] //! [1]
class CQFrame : public QFrame
// our agreement about "custom" widgets is to start them with CQ
{
Q_OBJECT
Q_ENUMS(FrameColorStyle)
Q_PROPERTY(FrameColorStyle colorStyle READ getColorStyle WRITE setColorStyle)
Q_PROPERTY(bool border READ border WRITE setBorder)
//! [0]
private:
bool curCover;
QFrame *frameCover;
public:
enum FrameColorStyle {fcDark, fcLight, fcTransparent, fcSystemDefault, fcRed, fcGreen, fcBlue};
CQFrame(QWidget *parent = 0);
void setCoverPropertie();
void setCover(bool state);
protected:
void resizeEvent(QResizeEvent *event);
FrameColorStyle m_colorStyle;
QString pstylebord;
bool m_border;
//! [2]
};
//! [1] //! [2]
#endif
I omitted getters and setters that are irrelevant to the problem. Here is implementation:
void CQFrame::setCoverPropertie()
{
QString str, strAlpha, gradient1, gradient2;
strAlpha.setNum(200);
gradient1 = "rgba("+str.setNum(cwDisableColor.red())+", "
+str.setNum(cwDisableColor.green())
+", "+str.setNum(cwDisableColor.blue())
+" ," +strAlpha+ " )";
gradient2 = "rgba("+str.setNum(cwLbColor.red())+", "
+str.setNum(cwLbColor.green())+", "
+str.setNum(cwLbColor.blue())+" ," +strAlpha+ " )";
QStackedLayout *stackedLayout = new QStackedLayout(this);
frameCover = new QFrame(this);
frameCover->setGeometry(rect());
frameCover->setStyleSheet("QFrame{border:5px solid "+strLbColor+"; "
"border-radius: 10px; background-color: "
"qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.5, stop: 0 "
+gradient1+" , stop: 1 "+gradient2+"); }");
stackedLayout->addWidget(frameCover);
stackedLayout->setStackingMode(QStackedLayout::StackAll);
}
void CQFrame::setCover(bool state)
{
frameCover->setVisible(curCover = state);
}
void CQFrame::resizeEvent(QResizeEvent *event)
{
if (curCover)
frameCover->setGeometry(rect());
}
The design isn't mine, I was asked to fix strange visual glitches it experiences. This "frame" is used in Qt designer as one of widgets. After a while suddenly everything resizes, which prompted question "what is wrong with this code". Qt fires warning about attempt to add layout while one already exist: I suppose that may cause a problem, because a frame must have only one layout at time? Code generated by Qt Creator looks something like
void setupUi(CQFrame *CQWnd1T2SeparateONForm)
{
if (CQWnd1T2SeparateONForm->objectName().isEmpty())
CQWnd1T2SeparateONForm->setObjectName(QString::fromUtf8("CQWnd1T2SeparateONForm"));
CQWnd1T2SeparateONForm->resize(735, 241);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(CQWnd1T2SeparateONForm->sizePolicy().hasHeightForWidth());
CQWnd1T2SeparateONForm->setSizePolicy(sizePolicy);
gridLayout = new QGridLayout(CQWnd1T2SeparateONForm); // warning here
}
There is similar problem with standard QMainWindow which always got own "special" layout, which Qt Creator solves automatically, by adding a central widget to the layout and everything else is added to that widget. What I don't know that is how to simulate same behavior with a custom widget with Qt Creator plugin.
Or what alternative design for CQFrame can be used. CQFrame reused in dozen project, in about 30+ panels, so reuse of code for them all is a strict requirement.
Current plugin is very basic:
class QDESIGNER_WIDGET_EXPORT CQFramePlugin : public QObject,
public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
CQFramePlugin(QObject *parent = 0);
bool isContainer() const;
bool isInitialized() const;
QIcon icon() const;
QString domXml() const;
QString group() const;
QString includeFile() const;
QString name() const;
QString toolTip() const;
QString whatsThis() const;
QWidget *createWidget(QWidget *parent);
void initialize(QDesignerFormEditorInterface *core);
private:
bool initialized;
};
.cpp for it:
#include "cqframe.h"
#include "cqframeplugin.h"
#include <QtPlugin>
CQFramePlugin::CQFramePlugin(QObject *parent)
: QObject(parent)
{
initialized = false;
}
void CQFramePlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (initialized)
return;
initialized = true;
}
bool CQFramePlugin::isInitialized() const
{
return initialized;
}
QWidget *CQFramePlugin::createWidget(QWidget *parent)
{
return new CQFrame(parent);
}
QString CQFramePlugin::name() const
{
return "CQFrame";
}
QString CQFramePlugin::group() const
{
return "CustomWidgets";
}
QIcon CQFramePlugin::icon() const
{
return QIcon(":/Resources/frame_icon.png");
}
QString CQFramePlugin::toolTip() const
{
return "";
}
QString CQFramePlugin::whatsThis() const
{
return "";
}
bool CQFramePlugin::isContainer() const
{
return true;
}
QString CQFramePlugin::domXml() const
{
return "<ui language=\"c++\">\n"
" <widget class=\"CQFrame\" name=\"Frame\">\n"
" <property name=\"geometry\">\n"
" <rect>\n"
" <x>0</x>\n"
" <y>0</y>\n"
" <width>120</width>\n"
" <height>80</height>\n"
" </rect>\n"
" </property>\n"
" </widget>\n"
"</ui>";
}
QString CQFramePlugin::includeFile() const
{
return "cqframe.h";
}

So I haven't worked with QT yet, but I'm going to gie it a try. First thing, I think, is that you should use the QStackedLayout to cover the widgets (source and QT manual). But you need to have a single stack.
So the stack should be a private member of CQFrame. E.g.
class CQFrame : public QFrame
{
[...]
private:
QWidget* _frame; // Frame to cover.
QFrame* _frameCover;
QStackedLayout* _stackedLayout;
[...]
And probably:
CQFrame::~CQFrame()
{
delete _stackedLayout;
delete _frameCover;
}
Then you could already initialize everything in the constructor
CQFrame::CQFrame(QWidget* parent = 0, QWidget* frame)
: QFrame(parent)
, _frame(frame)
, _frameCover(new QFrame(this))
, _stackedLayout(new QStackedLayout(this))
{
_frameCover->setGeometry(rect());
[...]
_stackedLayout->addWidget(frame);
_stackedLayout->addWidget(frameCover);
//default:_stackedLayout->setStackingMode(QStackedLayout::StackOne);
}
You could then switch between widgets in the stack using
void CQFrame::SetCover(bool state)
{
if (state) _stackedLayout->setCurrentWidget(_frameCover);
else _stackedLayout->setCurrentWidget(_frame);
}
Maybe this helps you.
edit2:
I removed this code, as it was incorrect, both in coding format, as in idea
So I checked the QT sources QStackedLayout.cpp and QLayout and it seems a QWidget can have only one layout. If you add another layout, you get an error.
Furthermore, a QStackedLayout is a QLayout, is QObject. That could indicate it is automatically removed?
I'm not sure of the cover is implemented in QT as it should. It seems like you have a QWidget you want to cover, on top of which you put a QStackedLayout that is not used as designed i.e. switching stack objects, on top of which you put a new QWidget that is made visible or not.
And maybe that bottom QWidget is already in a (stacked)layout, etc.

Related

Qt double spin box autofills when processing value changed

I have a time-consuming computation that depends on a double value. For this, I've created a GUI where I can set the value for the computation using a QDoubleSpinBox. The double spin box QDoubleSpinBox::valueChanged(double) signal is connected to a slot that starts the heavy computation in a new thread using QtConcurrent::run. The problem is that when I add a progress bar, the double spin box automatically fills its contents (zero-padding until the number of decimals) when the progress bar appears. My feeling is that this is because the double spin box loses the focus (i.e. the progress bar is the selected widget).
My question is:
How can I show the progress bar and make the double spin box
not fill the rest of the decimals with zeros?
This video shows how, when the progress bar is not shown, I can keep editing the double spin box while, when the progress bar is shown, the double spin box fills its precision with zeros. This is the current behavior, not the desired one. The desired one is that after the computation is done the double spin box is has not automatically filled its empty decimal places with zeros. Here is the code used for the video (fully available in GitHub):
Header
class DoubleSpinboxHeavyComputation : public QWidget
{
Q_OBJECT
public:
explicit DoubleSpinboxHeavyComputation(QWidget *parent = nullptr);
~DoubleSpinboxHeavyComputation();
signals:
void computationDone();
void progressSignal(int progress_state);
private slots:
void startHeavyComputationInThread();
void heavyComputation();
private:
Ui::DoubleSpinboxHeavyComputation *ui;
int n_ = 0;
};
Implementation
DoubleSpinboxHeavyComputation::DoubleSpinboxHeavyComputation(QWidget *parent)
: QWidget(parent), ui(new Ui::DoubleSpinboxHeavyComputation)
{
ui->setupUi(this);
connect(ui->doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this,
&DoubleSpinboxHeavyComputation::startHeavyComputationInThread);
}
DoubleSpinboxHeavyComputation::~DoubleSpinboxHeavyComputation()
{
delete ui;
}
void DoubleSpinboxHeavyComputation::startHeavyComputationInThread()
{
if (ui->checkBox->isChecked())
{
QProgressDialog *progress = new QProgressDialog("Computing", "", 0, 0, this);
progress->setWindowTitle(windowTitle());
progress->setWindowFlags((progress->windowFlags() | Qt::CustomizeWindowHint) &
~Qt::WindowCloseButtonHint); // Hide close button
progress->setWindowModality(Qt::WindowModal);
progress->setCancelButton(nullptr);
progress->setMaximum(1000);
progress->show();
connect(this, &DoubleSpinboxHeavyComputation::progressSignal, progress, &QProgressDialog::setValue);
connect(this, &DoubleSpinboxHeavyComputation::computationDone, progress, &QProgressDialog::close);
connect(this, &DoubleSpinboxHeavyComputation::computationDone, progress, &QProgressDialog::deleteLater);
}
QtConcurrent::run(this, &DoubleSpinboxHeavyComputation::heavyComputation);
}
void DoubleSpinboxHeavyComputation::heavyComputation()
{
int current_n = n_;
++n_;
qDebug() << "Start computation " << current_n;
for (int i = 0; i < 1000; i++)
{
emit progressSignal(i);
usleep(1000);
}
qDebug() << "End computation" << current_n;
emit computationDone();
}
Looks like you need to subclass your QDoubleSpinbox and reimplement textFromValue method
class NumericEdit : public QDoubleSpinBox
{
Q_OBJECT
public:
NumericEdit(QWidget *p_parent = nullptr);
QString textFromValue(double val) const;
};
QString NumericEdit::textFromValue(double val) const
{
//default converting
// return QString::number(val);
//converting with a local representation
QLocale locale;
return locale.toString(val);
}
But this will break default prefix and suffix functional of spinbox

Qt ListView doesn't show C++ model content

I'm creating a QList<> in C++ using a QML ListView to display it. The application runs without errors but the ListView stubbornly remains empty.
The QML will show a rectangle for the presence of each list item.
I checked the UI code by creating a list in QML.
It displays correctly for the QML created list.
Here's my QML:
import Processes 1.0
...
ListView {
id: qInterfaceList
height: parent.height;
width: parent.width;
model: myModel
orientation: ListView.Vertical
delegate:
Rectangle {
height: 30;
width: 120;
border.color: "red"
border.width: 3
}
The C++ code that creates and registers the list object:
// Register C++ classes as a QML type named Processes (version 1.0)
qmlRegisterType<Process>("Processes", 1, 0, "Process");
QQmlApplicationEngine engine;
// read the configuration file
Config conf;
if ( conf.read() )
{
QQmlContext* ctxt = engine.rootContext();
if ( ctxt )
{
qDebug()
<< "--- conf.Interfaces: "
<< conf.Interfaces.length()
;
ConfigInterface c;
QVariant v = QVariant::fromValue( conf.Interfaces );
qDebug()
<< "--- ConfigInterface: "
<< v
<< "--- typeName: "
<< v.typeName()
;
ctxt->setContextProperty("myModel", QVariant::fromValue( conf.Interfaces ));
}
}
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
To debug I output information about list from C++ and QML:
In C++ the count of list items is correct.
In C++ the conversion to QVariant is working.
In QML it sees the defined list.
The debugging output:
Debugging starts
--- conf.Interfaces: 65
--- ConfigInterface: QVariant(QList<ConfigInterface*>, ) --- typeName: QList<ConfigInterface*>
qml: myModel: QVariant(QList<ConfigInterface*>)
Debugging has finished
Any ideas what's wrong or how to debug this?
Thanks
EDIT: Here's the class being used as a list item
Class declaration:
class ConfigInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(QString sql READ getTag WRITE setTag NOTIFY tagChanged)
Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged)
public:
/*explicit*/ ConfigInterface();
/*explicit*/ ConfigInterface(QObject *parent);
~ConfigInterface();
// Copy constructor needed because these are copied when added to a QList
ConfigInterface(const ConfigInterface &p2) {_tag = p2._tag; _description = p2._description; }
QString getDescription() const;
void setDescription(QString&);
QString getTag() const;
void setTag(QString&);
signals:
void tagChanged(QString);
void descriptionChanged(QString);
public:
QString _tag;
QString _description;
QString QueryTemplate;
QString ConnectString;
QString MinimumId;
};
Q_DECLARE_METATYPE(ConfigInterface*)
C++ code:
ConfigInterface::ConfigInterface()
: QObject( nullptr )
{
}
ConfigInterface::ConfigInterface(QObject* parent)
: QObject(parent)
{
}
ConfigInterface::~ConfigInterface()
{
}
QString ConfigInterface::getTag() const
{
return _tag;
}
void ConfigInterface::setTag(QString& str)
{
_tag = str;
emit tagChanged(_tag);
}
The main problem is caused because it has a list of ConfigInterface *, according to the examples provided in the documentation should be a list of QObject *:
class Config{
[...]
public:
QList<QObject *> Interfaces;
[...]
};
In addition to this you should get the following warning:
/..../configinterface.h:17: warning: base class ‘class QObject’ should be explicitly initialized in the copy constructor [-Wextra]
ConfigInterface(const ConfigInterface &p2) {_tag = p2._tag; _description = p2._description; }
^~~~~~~~~~~~~~~
This is caused because QObject and its derived classes must not have a copy constructor or assignment operator, For more information read the following:
http://doc.qt.io/qt-5/qobject.html#no-copy-constructor-or-assignment-operator
Another improvement is that both constructors can be united in only one, in the end their class could have the following structure:
configinterface.h
#ifndef CONFIGINTERFACE_H
#define CONFIGINTERFACE_H
#include <QObject>
class ConfigInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(QString sql READ getTag WRITE setTag NOTIFY tagChanged)
Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged)
public:
ConfigInterface(QObject *parent=Q_NULLPTR);
~ConfigInterface();
QString getTag() const;
void setTag(const QString &tag);
QString getDescription() const;
void setDescription(const QString &description);
signals:
void tagChanged(QString);
void descriptionChanged(QString);
private:
QString _tag;
QString _description;
QString QueryTemplate;
QString ConnectString;
QString MinimumId;
};
#endif // CONFIGINTERFACE_H
configinterface.cpp
#include "configinterface.h"
ConfigInterface::ConfigInterface(QObject* parent)
: QObject(parent)
{
}
ConfigInterface::~ConfigInterface()
{
}
QString ConfigInterface::getDescription() const
{
return _description;
}
void ConfigInterface::setDescription(const QString &description)
{
if(_description == description)
return;
emit descriptionChanged(description);
_description = description;
}
QString ConfigInterface::getTag() const
{
return _tag;
}
void ConfigInterface::setTag(const QString &tag)
{
if(tag == _tag)
return;
emit tagChanged(tag);
_tag = tag;
}

C++/QML: ListView is not updated on dataChanged signal from QAbstractListModel

I am trying to write a QML Gui for a large dynamic C/Fortran simulation. The data I want to display is stored in Fortran Common blocks and updated on fixed time steps. My problem is that QML ListView does not refresh when the dataChanged signal is emitted after each time step, although the signal is received by the Gui (test is in the code below).
I am probably missing out something really obvious because when I flick my ListView down and up again, the displayed data is updated and correct (I guess because the QML engine re-renders elements when they get "out of sight" and back in again). So the only thing that does not work is that the ListView gets updated every time the dataChanged signal is received and not only when it is re-rendered. Below is a more detailed description of my approach and the relevant code parts.
Each simulation entity has several attributes (alive, position...), so I decided to create a ListModel containing a DataObject for each entity. This is the corresponding header file (the actual simulation data is declared as extern structs in "interface.h", so I can access it via pointer):
"acdata.h"
#include <QtCore>
#include <QObject>
#include <QtGui>
extern "C" {
#include "interface.h"
}
class AcDataObject : public QObject
{
Q_OBJECT
public:
explicit AcDataObject(int id_, int *pac_live, double *pac_pos_x, QObject *parent = 0) :
QObject(parent)
{
entity_id = id_;
ac_live = pac_live;
ac_pos_x = pac_pos_x;
}
int entity_id;
int *ac_live;
double *ac_pos_x;
};
class AcDataModel : public QAbstractListModel
{
Q_OBJECT
public:
enum RoleNames {
IdRole = Qt::UserRole,
LiveRole = Qt::UserRole + 1,
PosXRole = Qt::UserRole + 2
};
explicit AcDataModel(QObject *parent = 0);
virtual int rowCount(const QModelIndex &parent) const;
virtual QVariant data(const QModelIndex &index, int role) const;
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
void do_update();
protected:
virtual QHash<int, QByteArray> roleNames() const;
private:
QList<AcDataObject*> data_list;
QHash<int, QByteArray> m_roleNames;
QModelIndex start_index;
QModelIndex end_index;
signals:
void dataChanged(const QModelIndex &start_index, const QModelIndex &end_index);
};
Like the header, the .cpp file is also adapted from what you can find in the Qt5 Cadaques Book here, except that my constructor iterates over all simulation entities to set the pointers. Additionally, there is the do_update function that emits the dataChanged signal for the whole list.
"acdata.cpp"
#include "acdata.h"
AcDataModel::AcDataModel(QObject *parent) :
QAbstractListModel(parent)
{
m_roleNames[IdRole] = "entity_id";
m_roleNames[LiveRole] = "ac_live";
m_roleNames[PosXRole] = "ac_pos_x";
for (int i = 0; i < MAX_ENTITIES; i++) // MAX_ENTITIES is defined in interface.h
{
AcDataObject *data_object = new AcDataObject( i,
&fdata_ac_.ac_live[i], // fdata_ac_ is the C struct/Fortran common block defined in interface.h
&fdata_ac_.ac_pos_x[i] );
data_list.append(data_object);
}
}
int AcDataModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return data_list.count();
}
QVariant AcDataModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
if(row < 0 || row >= data_list.count()) {
return QVariant();
}
const AcDataObject *data_object = data_list.at(row);
switch(role) {
case IdRole: return data_object->entity_id;
case LiveRole: return *(data_object->ac_live);
case PosXRole: return *(data_object->ac_pos_x);
}
return QVariant();
}
QHash<int, QByteArray> AcDataModel::roleNames() const
{
return m_roleNames;
}
void AcDataModel::do_update() {
start_index = createIndex(0, 0);
end_index = createIndex((data_list.count() - 1), 0);
dataChanged(start_index, end_index);
}
Qt::ItemFlags AcDataModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {return 0;}
return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}
When the simulation is running, do_update() is called every second. I have created a test Gui with a ListView and exposed my model to it with:
Excerpt from "threadcontrol.cpp"
acdata = new AcDataModel();
viewer = new QtQuick2ApplicationViewer();
viewer->rootContext()->setContextProperty("acdata", acdata);
viewer->setMainQmlFile(QStringLiteral("../lib/qml_gui/main.qml"));
viewer->showExpanded();
(This code is part of a larger file that controls the different threads. I am quite sure the rest is not relevant to the actual problem and this question is getting really long...)
So finally there is main.qml. It contains a list with MAX_ENTITIES elements and each elements holds text fields to display my data. I have also added a Connections element to check if the dataChanged signal is received by the Gui.
"main.qml"
ListView {
id: listviewer
model: acdata
delegate: Rectangle {
/* ... some formatting stuff like height etc ... */
Row {
anchors.fill: parent
Text {
/* ... formatting stuff ... */
text: model.entity_id
}
Text {
/* ... formatting stuff ... */
text: model.ac_live
}
Text {
/* ... formatting stuff ... */
text: model.ac_pos_x
}
}
}
Connections {
target: listviewer.model // EDIT: I drew the wrong conclusions here, see text below!
onDataChanged: {
console.log("DataChanged received")
}
}
}
When running the simulation, the "DataChanged received" message is printed every second.
Edit: I was connecting to the ListModel and not to the ListView here, although the ListView has to receive the dataChanged signal. As the console log does not work when connecting to listviewer, I am probably missing the connection between listView and dataChanged signal. However, I think this should work automatically when implementing the dataChanged signal?
Additional information: I have found a similar problem here with Qt Map and it actually seemed to be a bug that was fixed in Qt 5.6. However, running qmake with Qt 5.7 did not fix my problem.
You mustn't declare the dataChanged() signal in your class, because you want to emit the signal AbstractItemModel::dataChanged(). If you re-declare it you add a comleptely new and different Signal that is not connected anywhere. If you remove the declaration in acdata.h everything should work fine.

Set renderer of QGraphicsSvgItem on preprocessed svg xml document very slow

I am using QGraphicsSvgItem subclass, that reads some content from a file, places content into a QDomDocument, does some initial processing, then sets the processed DOM onto a renderer.
During program processing, additional changes are required on a copy of pre-processed DOM, so the DOM is stored in class. After changes, the DOM is placed on renderer.
class MyGraphicsSvgItem : public QGraphicsSvgItem
{
public:
MyGraphicsSvgItem (QGraphicsItem *parent = 0):
QGraphicsSvgItem(parent),
_svgXML() {}
~MyGraphicsSvgItem () { delete renderer(); }
void CheckAndChangeSomeThings() {}
void LoadStuff (QString fileName)
{
QFile file(fileName);
file.open(QFile::ReadOnly | QFile::Text);
QTextStream in(&file);
QString svgContent = in.readAll();
file.close();
_svgXML.setContent(svgContent);
CheckAndChangeSomeThings(); // this modifies _svgXML
QByteArray _data = _svgXML.toByteArray();
setSharedRenderer(new QSvgRenderer(_data)); // very slow
}
void ChangeThingslater();
void ChangeSomeThingslater()
{
ChangeThingslater(); // this modifies _svgXML
renderer()->load(_svgXML.toByteArray()); // very slow - no file involved
}
protected:
QDomDocument _svgXML;
};
There seems to be a significant slow processing during the lines that assign the DOM to the renderer.
QByteArray _data = _svgXML.toByteArray();
setSharedRenderer(new QSvgRenderer(_data));
If I skip the DOM processing - if I set the renderer to the file - the code speeds up considerably:
Leaving all the code in, but replacing
setSharedRenderer(new QSvgRenderer(_data)); // VERY SLOW
with
setSharedRenderer(new QSvgRenderer(fileName)); // FAST
So it seems the bottleneck is loading the svg renderer from QByteArray.
I looked for alternatives... there is no mention of performance in documentation
QSvgRenderer::QSvgRenderer(const QString & filename, QObject * parent
= 0)
Constructs a new renderer with the given parent and loads the contents of the SVG file with the specified filename.
QSvgRenderer::QSvgRenderer(const QByteArray & contents, QObject *
parent = 0)
Constructs a new renderer with the given parent and loads
the SVG data from the byte array specified by contents.
QSvgRenderer::QSvgRenderer(QXmlStreamReader * contents, QObject *
parent = 0)
Constructs a new renderer with the given parent and loads
the SVG data using the stream reader specified by contents.
Looking in QXmlStreamReader Class, I find that its constructors are similar ! In addition, it says
In some cases it might also be a faster and more convenient alternative for use in applications that would otherwise use a DOM tree
I seem to be going in circles, and even though having already a well formed xml in the DOM, it seems I cannot take advantage of it !
What are my alternatives, to either loading the renderer from the pre-processed DOM, or to a different way of pre-processing the xml - using something other than DOM that the renderer can read fast ?
qt 4.8. c++
You can do all the DOM processing in a worker method that's executed on the thread queue using QtConcurrent::run.
You can use the QSvgRenderer directly in your item. Initialize it in the worker method, and load from QByteArray and not a file. You can then pass the renderer to the GUI thread and use it to render the graphics item by setting it on the QGraphicsSvgItem.
Caveats:
Since you create the renderer in a worker thread, you must move it to a null thread after you've done using it in the worker thread. Conversely, you must move it to the GUI thread once it has been received by the GUI thread.
Recall that moveToThread can only be called from the object's current thread, or any thread if thread() == 0.
In Qt 4.8, there's a bug in QGraphicsSvgItem::setSharedRenderer: it doesn't properly connect the renderer's repaintNeeded signal to its update method. The work around this to connect the signal manually to your own update slot.
This will prevent the GUI from getting blocked by the long processing.
Reentering the event loop, as you do, from within the item is a source of bugs and just a very bad idea from the design standpoint. Use the non-blocking API of the file dialog instead.
Below is an example that demonstrates this technique. It also displays a small spinner when the item is being loaded/processed. There's a simulated delay for this purpose.
#include <QGraphicsView>
#include <QGraphicsSvgItem>
#include <QGraphicsSceneMouseEvent>
#include <QFileDialog>
#include <QSvgRenderer>
#include <QDomDocument>
#include <QtConcurrentRun>
#include <QFutureWatcher>
#include <QThread>
#include <QApplication>
struct Thread : public QThread { using QThread::sleep; }; // Needed for Qt 4 only
class RendererGenerator {
QString m_fileName;
void process(QDomDocument &) {
Thread::sleep(3); /* let's pretend we process the DOM for a long time here */
}
QByteArray generate(const QByteArray & data) {
QDomDocument dom;
dom.setContent(data);
process(dom);
return dom.toByteArray();
}
public:
typedef QSvgRenderer * result_type;
RendererGenerator(const QString & fileName) : m_fileName(fileName) {}
QSvgRenderer * operator()() {
QFile file(m_fileName);
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll();
QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer);
renderer->load(generate(data));
renderer->moveToThread(0);
return renderer.take();
}
return 0;
}
};
class UserSvgItem : public QGraphicsSvgItem {
Q_OBJECT
QSvgRenderer m_spinRenderer, * m_lastRenderer;
QScopedPointer<QSvgRenderer> m_renderer;
QFuture<QSvgRenderer*> m_future;
QFutureWatcher<QSvgRenderer*> m_watcher;
QGraphicsView * aView() const {
QList<QGraphicsView*> views = scene()->views();
return views.isEmpty() ? 0 : views.first();
}
Q_SLOT void update() { QGraphicsSvgItem::update(); }
void mousePressEvent(QGraphicsSceneMouseEvent * event) {
if (event->button() == Qt::LeftButton) askForFile();
}
void setRenderer(QSvgRenderer * renderer) {
if (m_lastRenderer) disconnect(m_lastRenderer, SIGNAL(repaintNeeded()), this, SLOT(update()));
setSharedRenderer(renderer);
m_lastRenderer = renderer;
connect(renderer, SIGNAL(repaintNeeded()), SLOT(update()));
if (aView()) aView()->centerOn(this);
}
void askForFile() {
QFileDialog * dialog = new QFileDialog(aView());
connect(dialog, SIGNAL(fileSelected(QString)), SLOT(loadFile(QString)));
dialog->setAcceptMode(QFileDialog::AcceptOpen);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->show();
}
Q_SLOT void loadFile(const QString & file) {
if (m_future.isRunning()) return;
setRenderer(&m_spinRenderer);
m_future = QtConcurrent::run(RendererGenerator(file));
m_watcher.setFuture(m_future);
}
Q_SLOT void rendererReady() {
m_renderer.reset(m_future.result());
m_renderer->moveToThread(thread());
setRenderer(m_renderer.data());
}
public:
UserSvgItem(const QString & fileName = QString(), QGraphicsItem *parent = 0) :
QGraphicsSvgItem(fileName, parent), m_lastRenderer(0) {
connect(&m_watcher, SIGNAL(finished()), SLOT(rendererReady()));
setFlags(QGraphicsItem::ItemClipsToShape);
setCacheMode(QGraphicsItem::NoCache);
}
void setWaitAnimation(const QByteArray & data) { m_spinRenderer.load(data); }
};
namespace {
const char svgCircle[] =
"<svg height=\"100\" width=\"100\"><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" /></svg>";
const char svgRectangle[] =
"<svg width=\"400\" height=\"110\"><rect width=\"300\" height=\"100\" style=\"fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)\"></svg>";
const char svgThrobber[] =
"<svg width=\"16\" height=\"16\" viewBox=\"0 0 300 300\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"><path d=\"M 150,0 a 150,150 0 0,1 106.066,256.066 l -35.355,-35.355 a -100,-100 0 0,0 -70.711,-170.711 z\" fill=\"#3d7fe6\"><animateTransform attributeName=\"transform\" attributeType=\"XML\" type=\"rotate\" from=\"0 150 150\" to=\"360 150 150\" begin=\"0s\" dur=\"1s\" fill=\"freeze\" repeatCount=\"indefinite\" /></path></svg>";
void write(const char * str, const QString & fileName) {
QFile out(fileName);
if (out.open(QIODevice::WriteOnly | QIODevice::Truncate)) out.write(str);
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
write(svgRectangle, "rectangle.svg"); // Put svg resources into the working directory
write(svgCircle, "circle.svg");
QGraphicsScene scene;
UserSvgItem item("circle.svg");
QGraphicsView view(&scene);
scene.addItem(&item);
item.setWaitAnimation(QByteArray::fromRawData(svgThrobber, sizeof(svgThrobber)-1));
view.show();
return app.exec();
}
#include "main.moc"

Sample code for dialog like Qt Creator Options tab

I would like to build up dialog same as Qt Creator "Options" tab that left side page titles with scroll bar and detailed page on the right side.
It would be really helpful if there were code samples or sample applications for reference.
Qt Creator Source Code
Qt Creator has it source code both in Gitorious and in GitHub. But because Qt Creator is such a large and complex project, it can be overwhelming finding its sub parts.
The Github version is very searchable. It ends up that the source related to the nice options page in Qt Creator uses the IOptionsPage as the base class for any of the pages that show up in the Options dialog.
https://github.com/qtproject/qt-creator/search?utf8=%E2%9C%93&q=ioptionspage&type=Code
The ioptionspage.cpp has all the comments explaining the purpose of the different slots.
https://github.com/qtproject/qt-creator/blob/9926fc2ab12ccaa02b7f03b416c54cd58ef30b31/src/plugins/coreplugin/dialogs/ioptionspage.cpp
Basically for Qt Creators options page, it has an interface that is used by several different sub modules.
https://github.com/qtproject/qt-creator/blob/9926fc2ab12ccaa02b7f03b416c54cd58ef30b31/src/plugins/coreplugin/dialogs/ioptionspage.h
#ifndef IOPTIONSPAGE_H
#define IOPTIONSPAGE_H
#include <coreplugin/id.h>
#include <QIcon>
#include <QObject>
#include <QStringList>
namespace Core {
class CORE_EXPORT IOptionsPage : public QObject
{
Q_OBJECT
public:
IOptionsPage(QObject *parent = 0);
virtual ~IOptionsPage();
Id id() const { return m_id; }
QString displayName() const { return m_displayName; }
Id category() const { return m_category; }
QString displayCategory() const { return m_displayCategory; }
QIcon categoryIcon() const { return QIcon(m_categoryIcon); }
virtual bool matches(const QString &searchKeyWord) const;
virtual QWidget *widget() = 0;
virtual void apply() = 0;
virtual void finish() = 0;
protected:
void setId(Id id) { m_id = id; }
void setDisplayName(const QString &displayName) { m_displayName = displayName; }
void setCategory(Id category) { m_category = category; }
void setDisplayCategory(const QString &displayCategory) { m_displayCategory = displayCategory; }
void setCategoryIcon(const QString &categoryIcon) { m_categoryIcon = categoryIcon; }
Id m_id;
Id m_category;
QString m_displayName;
QString m_displayCategory;
QString m_categoryIcon;
mutable bool m_keywordsInitialized;
mutable QStringList m_keywords;
};
/*
Alternative way for providing option pages instead of adding IOptionsPage
objects into the plugin manager pool. Should only be used if creation of the
actual option pages is not possible or too expensive at Qt Creator startup.
(Like the designer integration, which needs to initialize designer plugins
before the options pages get available.)
*/
class CORE_EXPORT IOptionsPageProvider : public QObject
{
Q_OBJECT
public:
IOptionsPageProvider(QObject *parent = 0) : QObject(parent) {}
Id category() const { return m_category; }
QString displayCategory() const { return m_displayCategory; }
QIcon categoryIcon() const { return QIcon(m_categoryIcon); }
virtual QList<IOptionsPage *> pages() const = 0;
virtual bool matches(const QString & /* searchKeyWord*/) const = 0;
protected:
void setCategory(Id category) { m_category = category; }
void setDisplayCategory(const QString &displayCategory) { m_displayCategory = displayCategory; }
void setCategoryIcon(const QString &categoryIcon) { m_categoryIcon = categoryIcon; }
Id m_category;
QString m_displayCategory;
QString m_categoryIcon;
};
} // namespace Core
#endif // IOPTIONSPAGE_H
The search box uses an index the all the titles/labels of the children of each options page that gets added.
bool Core::IOptionsPage::matches(const QString &searchKeyWord) const
{
if (!m_keywordsInitialized) {
IOptionsPage *that = const_cast<IOptionsPage *>(this);
QWidget *widget = that->widget();
if (!widget)
return false;
// find common subwidgets
foreach (const QLabel *label, widget->findChildren<QLabel *>())
m_keywords << label->text();
foreach (const QCheckBox *checkbox, widget->findChildren<QCheckBox *>())
m_keywords << checkbox->text();
foreach (const QPushButton *pushButton, widget->findChildren<QPushButton *>())
m_keywords << pushButton->text();
foreach (const QGroupBox *groupBox, widget->findChildren<QGroupBox *>())
m_keywords << groupBox->title();
// clean up accelerators
QMutableStringListIterator it(m_keywords);
while (it.hasNext())
it.next().remove(QLatin1Char('&'));
m_keywordsInitialized = true;
}
foreach (const QString &keyword, m_keywords)
if (keyword.contains(searchKeyWord, Qt::CaseInsensitive))
return true;
return false;
}
Finding the rest of the components of the original dialog may take some time, but it is doable.
Included Example
When in Qt Creator > Welcome (tab) > Examples, the best one for a complex settings dialog is probably:
Tab Dialog Example
http://doc.qt.io/qt-5/qtwidgets-dialogs-tabdialog-example.html
Persistent Settings
QSettings is probably the best bet for storing settings. Other options include XML, and JSON. Qt 5 has a great implementation of JSON.
Hope that helps.