Qt - Saving a custom QGraphicsScene with custom QGraphicsItems - qt

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.

Related

How to make a Qt QML variable accessible anywhere

I would like to have a qt QML var accessible globally, and anywhere else in my qml files. Is this possible?
I know that upon creating a variable in a C++ object can be accessed in QML by exposing its getter function, but this only works if you know the type of the data type e.g. string, int, bool.
Is there a variable data type (or class) that can handle a QML var in C++, so that I can only call it in the other parts of the QML files?
AS Amfasis said, you can use the rootContext, so you can access it from anywhere in QML - as long as you do not shadow the name. Alternatively you can also register a Singleton to QML.
For both, you first need to create a QObject
public class MyContextObject: public QObject {
Q_OBJECT
Q_PROPERTY(QVariant myVar READ myVar NOTIFY myVarChanged)
QVariant m_myVar;
public:
MyContextObject(QObject* parent = nullptr) : QObject(parent) {}
QVariant myVar() { return m_myVar; }
void setMyVar(QVariant var) {
if (var == m_myVar) return;
m_myVar = var;
emit myVarChanged();
}
signals:
void myVarChanged();
}
This object you create in your main and set it as a contextProperty
MyContextObject* mctx = new MyContextObject();
view.rootContext()->setContextProperty("myCtx", mctx);
To set it from C++ use the setter. On the QML-side just bind to myCtx.myVar
Expose the setter also, if you want to modify it from QML also
This is not tested, I don't have a Qt development environment available right now.
To expose it as singleton, you can use this function:
https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterSingletonType-1

How to bind a property to a singleton object property from QML

There is a question about how to bind from a singleton object property to a QML property. But what about if we like to bind a QML property to a singleton object.
Here is the singleton class definition,
class Singleton : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
public:
explicit Singleton(QObject *parent = nullptr);
QString name() const;
void setName(const QString &name);
private:
QString m_name;
};
And on QML
property string qmlName: textField.text
TextField {
id: textField
}
I would like to bind textField.text to Singleton object name property. It is possible to bind it with a workaround like,
onQmlNameChanged: {
Singleton.name = qmlName;
}
But that won't be an Property Binding actually, because it is an assignment.
So is there a more nature binding way to a singleton object property?
You could try to assign the binding like this:
Component.onCompleted: Singleton.name = Qt.binding(function() { return qmlName })
It works for normal QML-Objects, not sure it works with a singleton class, though. Anyhow, you can read more about this approach in the section "Creating Property Bindings from JavaScript".
That is essentially what a property binding does, at least I assume it is what it does - it connects the changed() signals of the related variables to reevaluating the bound expression which references them.
So this in practice is a binding:
onQmlNameChanged: {
Singleton.name = qmlName;
}
You will only have a problem if you only execute the assignment once, but if it is attached to a signal it will keep updating as expected form a binding.
That would be identical to a Singleton.name : qmlName, unfortunately, the QML syntax does not allow to do it in this form. So for all intents and purposes, you do have a binding, even if it uses a different syntax to achieve it.
In fact this shouldn't be much different from what QML does under the carpet for you. For example the binding:
someProp : anotherProp + yetAnotherProp
is implement as something like this:
function unnamed(this) { return this.anotherProp + this.yetAnotherProp }
anotherPropChanged.connect(function(this) { this.someProp = unnamed(this) })
yetAnotherPropChanged.connect(function(this) { this.someProp = unnamed(this) })
Obviously, that is quite cumbersome to do manually especially as the expression becomes more complex and references more objects, so QML is doing it for you.

create object for class inheriting QLayout

I have a screen class as
class Screen : public QLayout
{
public:
Screen();
~Screen();
void paintEvent(QPaintEvent *e);
};
When I am creating the object I got an error that can not create an object for pure abstract class. Since QLayoput is pure abstract , How can I create an object for a class which is inherits the QLayout ?
definitions:
Screen::Screen( )
{
}
Screen::~Screen()
{
delete this ;
//Screen(new QSize (100,100));
}
void Screen::paintEvent(QPaintEvent *e)
{
}
QLayout is pure abstract, meaning it has virtual members without a definition. To subclass it, you need to provide definitions for all such methods in your class. Specifically, Qt Docs state that
To make your own layout manager, implement the functions addItem(),
sizeHint(), setGeometry(), itemAt() and takeAt().
For more, see there (there are additional optional advices for further functions which should be implemented): http://qt-project.org/doc/qt-4.8/qlayout.html

Returning managed pointer from a clone method

In C++11, two types of "managed" pointer types were introduced - shared_ptr and unique_ptr. Let's now assume we have a set of classes that support a clone() method, such as foo->clone() would return a copy of the foo object. If your goal was to return a managed pointer from the clone() method, how would you allow the user of the interface to select which kind of pointer he wants to be returned?
As a sub-question, would you rather return a raw pointer from the clone() method and let the user construct either shared_ptr or unique_ptr by himself? If not, why?
The standard smart pointer to manage a dynamic allocation is always unique_ptr. By contrast, shared_ptr is a very specific tool with specialized features (e.g. type-erased deleter, weak pointer observers) and higher costs (virtual dispatch, locked atomic operations) that should only be used when you definitely know you want it. Public raw pointers are a taboo out of principle, and so the natural clone interface looks like this:
struct Base
{
// must have virtual destructor to destroy through base pointer
virtual ~Base() {}
// non-leaf classes are abstract
virtual std::unique_ptr<Base> clone() const = 0;
};
struct Derived : Base
{
virtual std::unique_ptr<Base> clone() const override
{
return std::unique_ptr<Derived>(new Derived(*this));
// or "return std::make_unique<Derived>(*this)" in C++14
}
};
(Unfortunately, we cannot use any kind of covariant return types here, since the template classes unique_ptr<Base> and unique_ptr<Derived> are unrelated. If you prefer to have a clone function that returns the derived type, you could add a non-virtual function like direct_clone that returns a std::unique_ptr<Derived>, and implement the virtual clone() in terms of that.)
Something along this lines would give you the means to select the kind of smart pointer returned. Would probably be better if encapsulated in a mixin Clonable class template, for maintainability and reusability of the idea.
#include <iostream>
#include <memory>
class Base {
public:
virtual ~Base() {
std::cout << "deleting Base\n";
}
template <template <typename ...Args> class SmartPtr>
SmartPtr<Base> clone() {
return SmartPtr<Base>(this->inner_clone());
}
virtual void speak() const = 0;
private:
virtual Base *inner_clone() const = 0;
};
class C: public Base {
public:
~C() {
std::cout << "deleting C\n";
}
template <template <typename ...Args> class SmartPtr>
SmartPtr<C> clone() {
return SmartPtr<C>(this->inner_clone());
}
void speak() const {
std::cout << "I am C and I inherit from Base!\n";
}
private:
C *inner_clone() const override {
return new C(*this);
}
};
// End boilerplate.
int main()
{
auto original = C{};
// the declarations below should use auto, and are just explicitly typed to
// show the correct return type of clone();
std::shared_ptr<C> shared = original.clone<std::shared_ptr>();
std::unique_ptr<C> unique = original.clone<std::unique_ptr>();
// the declarations below show it working through conversion to a base class
// smart pointer type
std::shared_ptr<Base> sharedBase = original.clone<std::shared_ptr>();
std::unique_ptr<Base> uniqueBase = original.clone<std::unique_ptr>();
// the declarations below show it working through the base class for real
std::shared_ptr<Base> sharedBaseFromBase = sharedBase->clone<std::shared_ptr>();
std::unique_ptr<Base> uniqueBaseFromBase = uniqueBase->clone<std::unique_ptr>();
shared->speak();
unique->speak();
sharedBase->speak();
uniqueBase->speak();
sharedBaseFromBase->speak();
uniqueBaseFromBase->speak();
}
Compiles with gcc 4.8.1, and should in any compiler supporting variadics.
I would still prefer to simply return a unique_ptr and move the result into a shared_ptr, which would be automatic since the call to clone() is in itself an rvalue.

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.

Resources