views:

398

answers:

2
+3  A: 

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.

drhirsch
+1 for your O(n) analysis and the global quality of your answer
neuro
(1) When I wrote O(n^2) I meant that I had a nested loop, not that I was fretting about slow performance. I realize that number of objects != number of properties. (2) Please read amendment. I need static members to basically hold the strings that are displayed in the sidebar, and the valid range for the value. Those properties are shared. Not the values. (3) Eh? I want to display the changes immediately, and they will likely be constantly changing (if physics is enabled). (4) I'm going to digest this paragraph a bit later.
Mark
1. A nested loop has is only O(N^2) if both loop bounds depend linearly on N, which is not the case here. 2. For common Properties like limits etc. using a static member is ok of course. 3. I tried to clarify what i meant in the answer.
drhirsch
Oh. That's exactly what I did actually -- I have a QTimer that calls updateGL at 62.50 FPS (stupid millisecond precision!). Using QGLWidget. There will be way too much stuff moving around, plus full screen scrolling, so I don't think there's much point trying to optimize what gets updated. I do understand big-O btw ;)
Mark
+3  A: 

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;
};
TimW
No, I didn't know about this. Looks cool, but I'm not sure it's compatible with the QtTreePropertyBrowser class... I need to be able to add those properties to that sidebar so that they're displayed and editable. The QtTreePropertyBrowser doesn't know anything about the internal values it's editing, you have to set those yourself in the callback. At which point, these Q_PROPERTYs would be kind of redundant, since we don't need a 2nd callback after that. Let me know if I'm missing something.
Mark
I don't know the QtPropertyBrowser. If i have time I'll look into it.
TimW
There we go! That looks pretty nice. I think I'd like to set the valid ranges inside the SelectableItem though, rather than the Watcher, but I'm sure I can figure something out for that.
Mark
Couple bugs in your code that baffled me for awhile: `connect(m_propertyMap, ...` should be `m_variantManager`. `while(i.hasNext())` needs `i.next();` at the start of the loop. Otherwise, this seems to be working quite well! I'd say this is a pretty elegant solution.
Mark
Actually there are a lot more bugs than that... blank properties magically get added inside the valueChanged() function which creates all sorts of problems, and calling itemUpdated from inside setSelectedItem causes valueChanged to be fired too (setValue triggers it) which then updates the object... very odd behaviour :\
Mark
Mark
Also, every time SelectableItem::update() is emitted, the Watcher calls itemUpdated, which then updates the property as it should, but then this triggers valueChanged, which calls SelectableItem::setProperty again (the very function that started the chain -- bad!!). Fortunately the `old != new` check prevents an infinite loop, but still.
Mark
Sorry for the bugs ... i added QObject::blockSignal to prevent the loop back
TimW
I think blockSignals causes other issues -- http://stackoverflow.com/questions/1325758/weird-qt-bug-to-do-with-properties-editor/1325940#1325940 -- but I found a solution. Don't worry about it, seems to be working *now* :D
Mark