generalization of mapping a multi-variable/layer system - c++

I wrote an application with C++ / QT that communicates with a device to read/write its variables, puts/gets them in a struct and presents them in a gui for both viewing/editing purposes.
1) The device comes with a sample c code that also defines the communication protocol (in a very bad way) like:
#define VALUE_1 0x12345678
#define VALUE_2 0xDEADBEEF
#define MY_DEVICE_VAR (VALUE_1 << 4) & (VALUE_2 >> 6)
#define MY_DEVICE_VAR_1 (MY_DEVICE_VAR & (VALUE_1 << 2)
#define MY_DEVICE_VAR_2 (MY_DEVICE_VAR & (VALUE_2 << 4)
#define MY_DEVICE_VAR_2 (MY_DEVICE_VAR & (VALUE_2 << 4)
// .. and 300 more lines like above
So the variable VAR_1 is represented with: MY_DEVICE_VAR_1.
2) I've got a struct that holds all variables of the device:
struct MyDeviceData
{
int var1;
double var2;
char var3;
bool var4;
.
.
};
It's basically a storage for / a projection of the data read from the device. There are 4 different types of POD variables.
3) Finally my gui has gui elements to show and edit the instance of MyDeviceData
class MyGuI
{
QLineEdit var1;
QLineEdit var2;
QComboBox var3;
QCheckBox var4;
.
.
};
Now my questions are:
1) I'm doing the mapping of MY_DEVICE_VAR1 -> MyDeviceData::var1 -> MyGUI::var1 with if and switch/case statements which I'm not proud with. What would be a better "programmatic" way to do the mapping?
2) When the value of a gui element gets changed, I want to send only updated value to the card. besides overriding the handler functions of events like "textChanged, selectedIndexChanged" etc. Are there any "smarter" methods? (QSignalMapper?)
3) In this kind of project, is it possible to generalize whole drudge work? (a code-generator tool? templates?)

I have recently faced exactly the same problem, although I was the one designing the device, its firmware, and the communication protocol as well.
I think that one must use model/view to keep one's sanity.
I have all the variables as elements in a data model class deriving from QAbstractTableModel. That's because there's a fixed number of simple parameters (rows), and they are the same per each device (column). Quite soon, though, I'll have to move to a tree model, because some parameters internally are structured as lists, vectors or matrices, and it'd be helpful to expose them to views directly as such, and not merely as formatted strings.
The model class also has some convenience getters/setters so that you don't have to refer to the parameters by their row/column. The row/column access via a QModelIndex is only for use by the views.
I chose to use the UserRole for directly represented values (mostly doubles in SI units), and Display and Edit roles to present formatted/scaled data to the widgets.
For non-view controls, one needs a binder object. QDataWidgetMapper is provided by Qt, and you should ideally use it.
A while ago I didn't notice that there was the widget mapper, so I wrote custom a binder object (deriving from QObject), that gets instantiated for each GUI control, to bind a certain index of the model to a non-view Qt control widget. Another approach would be to use QListViews, have a proxy model per each view that exposes just one element, and properly assign delegates. This would introduce a lot of overhead, though. The binder object approach is quite lightweight.
The model-view approach also enables one to easily factor out the up-to-dateness indication of each control.
When the application is first started up, the model can indicate (via a dedicated role), that the values are invalid. This can put an x-cross or barber pole on the control to clearly indicate that there is no valid value there.
When the device is active, and the user modifies a control, a different role can indicate that the value was changed in the model, but not propagated to the device yet.
When the device communications code picks up the change from the model and commits it to the device, it can tell the model about it, and the view (the biner, really) will automatically pick it up and update the control.
Adding a Model * clone() const method to the model, or adding serialization/deserialization operators, allows you to trivially take snapshots of the model, and implement Undo/Redo, Commit/Revert, etc.
Relevant snippets of the binder are here:
// constructor
Binder::Binder(QAbstractItemModel * model_, const QModelIndex & index_, QObject * object) :
QObject(object),
model(model_),
index(index_),
sourceRole(Qt::DisplayRole),
property(""),
target(object),
lockout(false)
{
Q_ASSERT(index.isValid());
// replicate for each type of control
if (qobject_cast<QDoubleSpinBox*>(object)) {
connect(object, SIGNAL(valueChanged(double)), SLOT(doubleSpinBoxGet(double)));
connect(index.model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), SLOT(doubleSpinBoxSet(QModelIndex, QModelIndex)));
}
else if (....)
}
// getter/setter for QDoubleSpinBox
void Binder::doubleSpinBoxGet(double val)
{
if (lockout) return;
QScopedValueRollback<bool> r(lockout);
lockout = true;
model->setData(index, val, sourceRole);
}
void Binder::doubleSpinBoxSet(const QModelIndex & tl, const QModelIndex & br)
{
if (lockout) return;
if (! isTarget(tl, br)) return;
QScopedValueRollback<bool> r(lockout);
lockout = true;
if (! index.data().canConvert<double>()) return;
qobject_cast<QDoubleSpinBox*>(target)->setValue(index.data(sourceRole).toDouble());
}
// helper
bool Binder::isTarget(const QModelIndex & topLeft, const QModelIndex & bottomRight)
{
return topLeft.parent() == bottomRight.parent()
&& topLeft.parent() == index.parent()
&& topLeft.row() <= index.row()
&& topLeft.column() <= index.column()
&& bottomRight.row() >= index.row()
&& bottomRight.column() >= index.column();
}

Related

Make C++ class partially constexpr and save RAM

I have written code for a controller that has buttons and lamps. It is based on Arduino/ATmega2560. RAM is very limited. Changing to another device is no option.
Each button and lamp is modelled as a C++ class instance. They are derived from an abstract class Thing, which contains a pointer mpTarget that indicates connections between Things. The targets are initially assigned at runtime and do not change during runtime.
I need to save memory. As I have ~600 Things, it would help me alot if all those mpTargets were stored in ROM (and not in RAM), because the targets are known at compile time.
So I played with constexpr. However, it seems to be impossible to make a class only partially constexpr. I cannot make the whole class constexpr, because there are also member variables that change during runtime (mState in the example below).
What would be a good way to achieve this behavior? constexpr? Templates? Anything?
Clarifications
I know that mpTarget is currently initialized at runtime. But each target is known at compile time, that's why I'd like to find a good way to save RAM bytes of these pointers.
I don't really want to move away from object oriented design. I used an approach with simple, "plain old" datatypes before (which actually consumed less RAM). But as development of this code is still ongoing (classes and instances are added/deleted/modified), there is a high chance to miss necessary modifications, introducing hard-to-find bugs.
For this project, it would be more than enough to save the RAM storage only for mpTarget. Keeping the overhead of vtable pointers is fine. What I'm actually looking for is - as the title suggests - a way how we could implement a class whose members are partially constexpr. I also thought about using templates, but without success.
Sample code
#include <iostream>
class Thing
{
public:
// only called in setup()
void setTarget(Thing & pTarget)
{
mpTarget = & pTarget;
}
virtual void doSomething(int value) {};
protected:
// known at compile time - can we make this constexpr?
Thing * mpTarget;
};
class Button : public Thing
{
public:
void setState(int newState)
{
mState = mState + newState;
mpTarget->doSomething(mState);
}
private:
// changes during runtime
int mState;
};
class Lamp: public Thing
{
public:
virtual void doSomething(int value)
{
std::cout << value << std::endl;
};
};
Button myButton;
Lamp myLamp;
int main()
{
myButton.setTarget(myLamp);
while (1)
{
myButton.setState(123);
}
}
Redesign from ground up
ATmega2560 has just 8KB SRAM. This is extremely low compared to normal, desktop standards. 600 objects with 3 2-byte properties each would fill almost half of the available memory.
Programming on such a restrictive environment forces you to adapt your whole design from the start around the hardware limitations. Writing normal code and then trying to fit it to your hardware after the fact just doesn't cut it here. This is a good exception to the "first write readable code, then try it to optimize it".
One idea: group by properties, not by objects
First you need to abandon virtual methods. That would add at least a vtable pointer per instance. At 600 instances it is a very heavy cost.
One ideea I have for a design here is to ditch the OOP completely. Or at least partially. Instead of properties grouped per instance, group all properties together in vectors.
This has a few big advantages:
it saves space by abandoning the vtable
because how properties are grouped it makes it possible to store the properties known at compile time in ROM
it saves space by using the minimum number of bits necessary for each property
Example
For the sake of example let's consider this scenario:
we have 100 lamps, 200 buttons and 300 LEDs
buttons and LEDs have a target property. Only lamps can be targets
buttons have state property. There are 2 possible states (ON/OFF)
LEDs have color property. There are 16 possible predefined colors
The following examples use literals to be explicit, but in code you should use constants (e.g. NUM_BUTTONS etc.)
target property
We have 500 (200 buttons + 300 LEDS) objects with target property. So we need a vector of size 500. There are 100 targets. So the data type fits in a int8_t. So target looks like this:
constexpr int8t_t targets[500] = ...
In this vector the first 200 elements represent the targets of the buttons and the next 300 elements represent the targets of the LEDs. This vector will be stored in ROM.
To get the target of a thing we have:
int8_t button_target(int button) { return targets[button]; }
int8_t led_target(int led) { return targets[200 + led]; }
Alternatively use two vectors:
constexpr int8t_t button_targets[200] = ...
constexpr int8t_t led_targets[300] = ...
Populating this vector at compile time is a problem you need to solve. I don't know exactly how you are creating your objects now. You could hard code the values in the code or you could generate the code.
state property
We have 200 elements with state. Since there are 2 possible states we just need 1 bit per state. So we just need 200 / 8 = 25 bytes:
int8_t state[25] = {};
Getting and setting the state of a button is more complicated, it requires bitmask operations, but we have saved 175 bytes here (87.5% saved space on this data) and every byte matters.
bool button_get_state(int button)
{
int8_t byte = state[button / 8];
return byte & (1 << (button % 8));
}
color property
We have 300 elements with color. Since there are 16 colors we need 4 bits per color so we can encode 2 colors per byte:
int8_t color[150] = {};
Again getting and setting color requires bit fiddling.
Conclusion
As you can see this design is by far not as pretty as OOP. And is it requires more complex code. But it has the big advantage that is saves you a lot of space, which is paramount on a device with just 8,192 bytes of SRAM.
Use small state
Eliminate all virtual methods (and probably all base classes) to reduce class state.
Use Non-Type template parameters to move references from data (RAM) to code (ROM)
Use an enum based on a char for state when possible:
enum binary_enable_state : char {
BINARY_ENABLE_ON,
BINARY_ENABLE_OFF
};
Event listeners should just listen, and have no pointers or references or any real state.
class Lamp {
public:
void doSomething(binary_enable_state value)
{
std::cout << value << std::endl;
};
};
Event sources should uses non-type template parameter references to the listeners, thus avoiding pointers and state.
template<class Listener, Listener& mpTarget>
class Button {
public:
void setState(binary_enable_state newState)
{
mState = newState;
mpTarget.doSomething(mState);
}
private:
// changes during runtime
binary_enable_state mState; //Do you actually need to store this?
};
This basically puts the graph in the code itself (ROM), rather than as data in RAM.
Lamp myLamp;
Button<decltype(myLamp), myLamp> myButton;
int main()
{
myButton.setState(BINARY_ENABLE_ON);
}
http://coliru.stacked-crooked.com/a/7171e4b9547ccbe1

Share array or list between C++ and QML via property

There's a list of unsigned ints in C++ class. The list can be modified inside the class in some way. There's a QML object that has to use this list. How should I declare the list to make the object able to use the values from the list and after changing the content of the list inside C++ get appropriate values in QML?
Currently the list is defined as:
QVariantList cards;
Q_PROPERTY(QVariantList cards MEMBER cards NOTIFY setChanged)
void setChanged ( QVariantList const &cards );
But QML takes only cards initial value (empty list) and does not "notice" any changes inside it later on.
The need for NOTIFY signal is optional. I guess it is for when we need to deliberately let QML know that the data is ready but the data will be consumed when it READs data. Aside from this we can almost always avoid programming NOTIFY. I even do rootItem->setProperty("propertyName", value) for occasional push in of new value to QML especially if there onPropertyNameChanged handler is ready.
The below will likely do what you want. Or it is required and adding NOTIFY to that won't hurt as well but adds 'inoperability'.
class MyMsgBoard : public QObject
{
public:
Q_PROPERTY(QVariantList cards READ cards WRITE setCards)
const QVariantList & cards() const { return qvList; }
void setCards(const QVariantList & v) { qvList = v; }
private:
QVariantList qvList;
};
More detailed explanation. I use message board concept from there.

Choose algorithm in accordance with chosen item in combobox

Good day,
I'm writing a program 'geocalculator' as an assignment at our university. I faced up with a design problem. An interface contains two groups: A and B. Each group consists of two comboboxes and a table. The first combobox allows user to choose between a couple of reference systems (WGS-84, SK-42, SK-95, etc.). The second - between geodetic, spatial and planimetric coordinate systems. The table is used to input a bunch of points (label, X (or B), Y (or L), Z (or H)). Conversion functions involve a lot of constants and massive formulas.
My question is: what is the best way to organize code, connect comboboxes and functions in order to call appropriate conversion function?
Let's come to the point. I subclassed an abstract model (PointsModel) where all my points are being stored. It has no idea about comboboxes and algorithms. The comboboxes are declared inside of a MainWindow class. My model do have custom delegate: it is used to convert representation of the input coordinates from sexadecimal (degrees, minutes and seconds) to decimal (decimal degrees). I have two buttons (actions on the toolBar): convert points from A to B and vice versa. I imagine the process in the next way:
The user presses 'convert' button and the appropriate function is called.
The general conversion function reads value of the comboboxes and chooses required conversion function.
...
I don't want to do it via "if else" statements! Because there would be a huge number of condition statements (if the one combobox is WGS-84 and the other is SK-42 then do this, if the one combobox is SK-42 and the other is WGS-84 then do this and so on and so forth for a bunch of reference systems).
I have some ideas about declaring functions and corresponding functors. I would like to attach this functors to the comboboxes in some way and then the time comes just call a general method, which would automatically redirect the call to the required function.
I'll describe two popular choices: Look up table and event handler (a.k.a. button press). The selection of the algorithm depends on your calculator implementation.
Look up table
The fundamentals here is that you want to associate a function with a symbol. For example, your parser wants to evaluate the '+' for the expressions on the stack. Rather than using a switch or an if-elseif ladder, you could look up a function that is associated with '+' and execute the function with the two expressions.
// Synonym for a pointer that accepts two parameters,
// performs an operation, and returns the result as a string.
typedef std::string (*Function_Pointer(const std::string& param1, const std::string& param2))
struct Table_Entry
{
const char * operator_text;
Function_Pointer p_operaton_function;
};
const Table_Entry Operation_Table[] =
{
{"+", Perform_Addition},
{"-", Perform_Subtraction},
{"*", Perform_Multiplication},
};
// Or
typedef std::map<std::string, Function_Pointer> Operation_Container;
//...
Operation_Container operations_map;
operations_map["+"] = Perform_Addition;
operations_map["-"] = Perform_Subtraction;
Event Handler
Another idea is to put the computation inside the handler for the button.
void On_Button_Dash(Event& e)
{
result = operand_1 - operand2;
}
Edit 1: OOP
Another alternative is to create a base class for the operations. The base class would have an abstract method to evaluate or perform the operation. Define child classes for each operation. Create the operation classes during lexing phase and store in a vector of pointers to base classes.
You main loop could be:
std::vector<Base_Operation *> operations;
for (unsigned int i = 0; i < operations.size(); ++i)
{
operations[i].Evaluate(parameter_1, parameter_2);
}
There are a number of ways that you can handle this, but I think the best is by setting a functor on each element in your QComboBox.
When you do (QComboBox::addData)[http://qt-project.org/doc/qt-5/qcombobox.html#addItem] you'll notice the defaulted userData parameter. You can assign a functor here.
When you're ready to use it just call (QBomboBox::currentData)[http://qt-project.org/doc/qt-5/qcombobox.html#currentData-prop] to get it back out and use it.
As far as storing your functor in a QVariant, you can either store your functors in your own array and just store an index into that array in the QVariant of you can directly store the functor as a void* using QVariant(int typeId, const void * copy).
EDIT:
From your comment it sounds like something like this might be in order:
Store your functions in a std::map<std::string, std::map<std::string, std::function<X>>> Where the key to the outer map is the from-type and the key to the inner map is the to-type.
Then store the keys in your QComboBox's userData.

Qt4: Read Default mimeData from QAbstractItemModel

What I want to do is very similar to this. Except that I am working with a QAbstractItemModel that has a tree structure and am interested in more than just the row and column. In fact, in my model, column is always 0. But in order to implement drag and drop, I need to get the parent, children, and the opaque pointer that internalPointer() returns. Here is some relevant code. CTreeView extends QTreeView.
void CTreeView::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
{
event->acceptProposedAction();
}
}
void CTreeView::dropEvent(QDropEvent* event)
{
const QMimeData* mime_data = event->mimeData();
QByteArray encoded_data =
mime_data->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded_data, QIODevice::ReadOnly);
while (!stream.atEnd())
{
// I can do this.
int row, column;
stream >> row >> column;
// But how do I construct the QModelIndex to get the parent, children,
// and opaque pointer?
// I have seen other advice that mentions doing this.
QMap<int, QVariant> role_data_map;
stream >> row >> col >> role_data_map;
// Which allows you to do this.
QList<int> keys = role_data_map.keys();
BOOST_FOREACH(int key, keys)
{
QVariant variant = role_data_map[key];
// use the variant
}
// But that only gets me part of the way there.
}
}
Any ideas? I only want to support drag and drop within the tree view so I'm thinking about storing the QModelIndexList of selectedIndexes() in a member variable of my subclass and just manipulating it directly in dropEvent(). That seems like cheating somehow so I'm still interested in the Qt way. Please let me know what you think of this idea.
First it looks like from your code that you are doing dnd the wrong way: you should not overload dropEvent in your view, but instead dropMimeData in your model. The following document explains how to do dnd with the model/view framework of Qt:
http://doc.trolltech.com/latest/model-view-dnd.html
As for your specific problem, which is to access the internalPointer() of the dropped items. Storing the indexes in an index of your class is dangerous and error-prone. What you want to do is store the information you need in the mime data. I don't know what your use case is so I cannot guess what this useful data is - but if you just need the value of internalPointer (and can make sure this value will still be valid when the drop event is received), you can just store it, as you decide the format. For instance, if your data is referenced by a unique id somewhere (like the row id in a database), you can store this information and have a custom index(int rowid) method in your model that constructs a QModelIndex from this information. Normally the internalPointer of an index is set during its creation, so this would allow to fetch all the needed information.
If you tell us how you create your indexes maybe we can help further.

Properties editor design pattern?

Warning: This is super in-depth. I understand if you don't even want to read this, this is mostly for me to sort out my thought process.
Okay, so here's what I'm trying to do. I've got these objects:
When you click on one (or select several) it should display their properties on the right (as shown). When you edit said properties it should update the internal variables immediately.
I'm trying to decide on the best way to do this. I figure the selected objects should be stored as a list of pointers. It's either that, or have an isSelected bool on each object, and then iterate over all of them, ignoring the non-selected ones, which is just inefficient. So we click on one, or select several, and then the selectedObjects list is populated. We then need to display the properties. To keep things simple for the time being, we'll assume that all objects are of the same type (share the same set of properties). Since there aren't any instance-specific properties, I figure we should probably store these properties as static variables inside the Object class. Properties basically just have a name (like "Allow Sleep"). There is one PropertyManager for each type of property (int,bool,double). PropertyManagers store all the values for properties of their respective type (this is all from the Qt API). Unfortunately, because PropertyManagers are required to create Properties I can't really decouple the two. I suppose this means that I have to place the PropertyManagers with the Properties (as static variables). This means we have one set of properties, and one set of property managers to manage all the variables in all the objects. Each property manager can only have one callback. That means this callback has to update all the properties of its respective type, for all objects (a nested loop). This yields something like this (in pseudo-code):
function valueChanged(property, value) {
if(property == xPosProp) {
foreach(selectedObj as obj) {
obj->setXPos(value);
}
} else if(property == ...
Which already bothers me a little bit, because we're using if statements where we shouldn't need them. The way around this would be to create a different property manager for every single property, so that we can have unique callbacks. This also means we need two objects for each property, but it might be a price worth paying for cleaner code (I really don't know what the performance costs are right now, but as I know you'll also say -- optimize when it becomes a problem). So then we end up with a ton of callbacks:
function xPosChanged(property, value) {
foreach(selectedObj as obj) {
obj->setXPos(value);
}
}
Which eliminates the entire if/else garbage but adds a dozen more event-listeners. Let's assume I go with this method. So now we had a wad of static Properties, along with their corresponding static PropertyManagers. Presumably I'd store the list of selected objects as Object::selectedObjects too since they're used in all the event callbacks, which logically belong in the object class. So then we have a wad of static event callbacks too. That's all fine and dandy.
So now when you edit a property, we can update the interal variables for all the selected objects via the event callback. But what happens when the internal variable is updated via some other means, how do we update the property? This happens to be a physics simulator, so all the objects will have many of their variables continuously updated. I can't add callbacks for these because the physics is handled by another 3rd party library. I guess this means I just have to assume all the variables have been changed after each time step. So after each time step, I have to update all the properties for all the selected objects. Fine, I can do that.
Last issue (I hope), is what values should we display when multiple objects are selected an there is an inconsistency? I guess my options are to leave it blank/0 or display a random object's properties. I don't think one option is much better than the other, but hopefully Qt provides a method to highlight such properties so that I can at least notify the user. So how do I figure out which properties to "highlight"? I guess I iterate over all the selected objects, and all their properties, compare them, and as soon as there is a mismatch I can highlight it. So to clarify, upon selected some objects:
add all objects to a selectedObjects list
populate the properties editor
find which properties have identical values and update the editor appropriately
I think I should store the properties in a list too so that I can just push the whole list onto the properties editor rather than adding each property individually. Should allow for more flexibility down the road I think.
I think that about covers it... I'm still not certain how I feel about having so many static variables, and a semi-singleton class (the static variables would be initialized once when the first object is created I guess). But I don't see a better solution.
Please post your thoughts if you actually read this. I guess that's not really a question, so let me rephrase for the haters, What adjustments can I make to my suggested design-pattern to yield cleaner, more understandable, or more efficient code? (or something along those lines).
Looks like I need to clarify. By "property" I mean like "Allow Sleeping", or "Velocity" -- all objects have these properties -- their VALUES however, are unique to each instance. Properties hold the string that needs to be displayed, the valid range for the values, and all the widget info. PropertyManagers are the objects that actually hold the value. They control the callbacks, and the value that's displayed. There is also another copy of the value, that's actually used "internally" by the other 3rd party physics library.
Trying to actually implement this madness now. I have an EditorView (the black area drawing area in the image) which catches the mouseClick event. The mouseClick events then tells the physics simulator to query all the bodies at the cursor. Each physics body stores a reference (a void pointer!) back to my object class. The pointers get casted back to objects get pushed onto a list of selected objects. The EditorView then sends out a signal. The EditorWindow then catches this signal and passes it over to the PropertiesWindow along with the selected objects. Now the PropertiesWindow needs to query the objects for a list of properties to display... and that's as far as I've gotten so far. Mind boggling!
The Solution
/*
* File: PropertyBrowser.cpp
* Author: mark
*
* Created on August 23, 2009, 10:29 PM
*/
#include <QtCore/QMetaProperty>
#include "PropertyBrowser.h"
PropertyBrowser::PropertyBrowser(QWidget* parent)
: QtTreePropertyBrowser(parent), m_variantManager(new QtVariantPropertyManager(this)) {
setHeaderVisible(false);
setPropertiesWithoutValueMarked(true);
setIndentation(10);
setResizeMode(ResizeToContents);
setFactoryForManager(m_variantManager, new QtVariantEditorFactory);
setAlternatingRowColors(false);
}
void PropertyBrowser::valueChanged(QtProperty *property, const QVariant &value) {
if(m_propertyMap.find(property) != m_propertyMap.end()) {
foreach(QObject *obj, m_selectedObjects) {
obj->setProperty(m_propertyMap[property], value);
}
}
}
QString PropertyBrowser::humanize(QString str) const {
return str.at(0).toUpper() + str.mid(1).replace(QRegExp("([a-z])([A-Z])"), "\\1 \\2");
}
void PropertyBrowser::setSelectedObjects(QList<QObject*> objs) {
foreach(QObject *obj, m_selectedObjects) {
obj->disconnect(this);
}
clear();
m_variantManager->clear();
m_selectedObjects = objs;
m_propertyMap.clear();
if(objs.isEmpty()) {
return;
}
for(int i = 0; i < objs.first()->metaObject()->propertyCount(); ++i) {
QMetaProperty metaProperty(objs.first()->metaObject()->property(i));
QtProperty * const property
= m_variantManager->addProperty(metaProperty.type(), humanize(metaProperty.name()));
property->setEnabled(metaProperty.isWritable());
m_propertyMap[property] = metaProperty.name();
addProperty(property);
}
foreach(QObject *obj, m_selectedObjects) {
connect(obj, SIGNAL(propertyChanged()), SLOT(objectUpdated()));
}
objectUpdated();
}
void PropertyBrowser::objectUpdated() {
if(m_selectedObjects.isEmpty()) {
return;
}
disconnect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
bool diff;
while(i.hasNext()) {
i.next();
diff = false;
for(int j = 1; j < m_selectedObjects.size(); ++j) {
if(m_selectedObjects.at(j)->property(i.value()) != m_selectedObjects.at(j - 1)->property(i.value())) {
diff = true;
break;
}
}
if(diff) setBackgroundColor(topLevelItem(i.key()), QColor(0xFF,0xFE,0xA9));
else setBackgroundColor(topLevelItem(i.key()), Qt::white);
m_variantManager->setValue(i.key(), m_selectedObjects.first()->property(i.value()));
}
connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)),
this, SLOT(valueChanged(QtProperty*, QVariant)));
}
With a big thanks to TimW
Did you have a look at Qt's (dynamic) property system?
bool QObject::setProperty ( const char * name, const QVariant & value );
QVariant QObject::property ( const char * name ) const
QList<QByteArray> QObject::dynamicPropertyNames () const;
//Changing the value of a dynamic property causes a
//QDynamicPropertyChangeEvent to be sent to the object.
function valueChanged(property, value) {
foreach(selectedObj as obj) {
obj->setProperty(property, value);
}
}
Example
This is an incomplete example to give you my idea about the property system.
I guess SelectableItem * selectedItem must be replaced with a list of items in your case.
class SelectableItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName );
Q_PROPERTY(int velocity READ velocity WRITE setVelocity);
public:
QString name() const { return m_name; }
int velocity() const {return m_velocity; }
public slots:
void setName(const QString& name)
{
if(name!=m_name)
{
m_name = name;
emit update();
}
}
void setVelocity(int value)
{
if(value!=m_velocity)
{
m_velocity = value;
emit update();
}
}
signals:
void update();
private:
QString m_name;
int m_velocity;
};
class MyPropertyWatcher : public QObject
{
Q_OBJECT
public:
MyPropertyWatcher(QObject *parent)
: QObject(parent),
m_variantManager(new QtVariantPropertyManager(this)),
m_propertyMap(),
m_selectedItem(),
!m_updatingValues(false)
{
connect(m_variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), SLOT(valueChanged(QtProperty*,QVariant)));
m_propertyMap[m_variantManager->addProperty(QVariant::String, tr("Name"))] = "name";
m_propertyMap[m_variantManager->addProperty(QVariant::Int, tr("Velocity"))] = "velocity";
// Add mim, max ... to the property
// you could also add all the existing properties of a SelectableItem
// SelectableItem item;
// for(int i=0 ; i!=item.metaObject()->propertyCount(); ++i)
// {
// QMetaProperty metaProperty(item.metaObject()->property(i));
// QtProperty *const property
// = m_variantManager->addProperty(metaProperty.type(), metaProperty.name());
// m_propertyMap[property] = metaProperty.name()
// }
}
void setSelectedItem(SelectableItem * selectedItem)
{
if(m_selectedItem)
{
m_selectedItem->disconnect( this );
}
if(selectedItem)
{
connect(selectedItem, SIGNAL(update()), SLOT(itemUpdated()));
itemUpdated();
}
m_selectedItem = selectedItem;
}
private slots:
void valueChanged(QtProperty *property, const QVariant &value)
{
if(m_updatingValues)
{
return;
}
if(m_selectedItem && m_map)
{
QMap<QtProperty*, QByteArray>::const_iterator i = m_propertyMap.find(property);
if(i!=m_propertyMap.end())
m_selectedItem->setProperty(m_propertyMap[property], value);
}
}
void itemUpdated()
{
m_updatingValues = true;
QMapIterator<QtProperty*, QByteArray> i(m_propertyMap);
while(i.hasNext())
{
m_variantManager->next();
m_variantManager->setValue(
i.key(),
m_selectedItem->property(i.value()));
}
m_updatingValues = false;
}
private:
QtVariantPropertyManager *const m_variantManager;
QMap<QtProperty*, QByteArray> m_propertyMap;
QPointer<SelectableItem> m_selectedItem;
bool m_updatingValues;
};
Calm down, your code has not O(n^2) complextity. You have a nested loop, but only one counts to N (the number of objects), the other counts to a fixed number of properties, which is not related to N. So you have O(N).
For the static variables, you write "there aren't any instance-specific properties", later you write about updates of the individual properties of your objects, which are exactly instance-specific properties. Maybe you are confusing the "class Properties" (which is of course shared among all properties) with the individual properties? So I think you don't need static members at all.
Do you want to display changes to the objects only if they appear, or do you want a continuos display? If your hardware is able to handle the latter, I would recommend going that way. In that case, you have to iterate over all objects anyway and update them along the way.
Edit: The difference is that in the former (update on change) the drawing is initiated by the operation of changing the values, for example a object movement. For the latter, a continuos display, you would add a QTimer, which fires say 60 times a second and calls a SLOT(render()) which does the actual rendering of all objects. Depending on the rate of changes this may actually be faster. And it is probably easier to implement.
Another possibilty is let Qt handle the whole drawing, using a Graphics View, which handles the objects-to-draw internally in a very efficient tree structure. Take a look at
http://doc.trolltech.com/4.5/graphicsview.html
If you want to display only the changes, you could use individual callbacks for each properties value. Each time the value of a property is changed (in this case making the properties vlaues private and using setSomeThing(value)), you call the update function with an emit(update()). If you are absolutly concernd about emit being slow, you could use "real" callbacks via function pointers, but I don't recommend that, Qt's connect/signal/slot is so much easier to use. And the overhead is in most cases really neglible.