Need some advice: how to draw a line between two objects.



  • Hi,

    I am working in a project where I have several items in a view (lets say rectangles, for example).
    I want to draw a line between some pairs of them and I want the line to be attached to the rectangles as if they were the edges of a graph. The ends of the line should be in the center of the rectangles. This means that when I move any of the two rectangles, the line will move in consequence.

    I don't really know how to do it, that is why I am asking you some advice ^^
    My only idea is to draw a rectangle (of height = 1) to be the line and set its position to the center of one of the two rectangles, then, compute the angle and distance to get to the other rectangle and apply a rotation (and change the width). But I think it can be very complicated since it need to compute the angle and width of the line each time one of the two rectangles is moved.
    I also though about the anchors, but then the line is horizontal and too thick. I can afterwards apply a rotation and change the width, but I think there must be an easier way.

    Is there any other easy way to do it?

    Thank you again.



  • The probably easiest solution is to cover the scene with a Canvas or a C++ QQuickPaintedItem and then draw the lines between items using QPainter. Note that this works through rasterization so you will loose hardware acceleration and might have significantly higher memory requirements as a tradeoff. But if it works in your use case it would be a lot simpler, and you are not constrained to straight lines only.

    Note this comment applies to Qt5. In Qt4, a standard QDeclarativeItem* would be your best bet.

    *Edit : As Chris pointed out, I incorrectly said QQuickItem here.



  • Hi, thank you for your answer!

    I am using Qt4.8 (I am forced to...) so if I understang well you are telling me to use QQuickItem. But I couldn't find any documentation on that class for Qt4.8, only for Qt5: http://qt-project.org/doc/qt-5.0/qtquick/qquickitem.html

    Also, I don't really like loosing hardware acceleration. But I could give it a try. Could you just explain me a little bit more how to do it?

    Any way, is it really the only good option? I was hoping there was an easy solution using something like Line Element of Qt3D (http://doc.qt.digia.com/qt-quick3d-snapshot/qml-line.html) but for only 2D. (I think I cannot use QtQuick3D for this project)

    Thank you again.



  • Hi,

    Jens meant QDeclarativeItem in Qt4, where you can override the paint() function to do whatever painting you like.

    As Jens said Qt5/QtQuick2, you can use either the Canvas type or a QQuickPaintedItem, with a low z-value, as the "background" and dynamically draw lines between x/y coordinates as required (then just have rectangles with higher z-value above the canvas).

    If you're using Qt 4.8, and QDeclarativeItem, you're probably not using hardware acceleration to do your painting anyway, so you shouldn't worry too much about that. Just subclass QDeclarativeItem, override the paint() method, register it as a QML type, and it should work.

    Cheers,
    Chris.



  • Ok thank you, I already thought about that, but it seamed to me a bit "too much" for just drawing a line ^^. But I think that is what I will do (I have already done that for the two "rectangles" that I want to attach, which are no rectangles in fact)

    In that case, my class Line should be something like this?:
    @
    class A : public QDeclarativeItem{ /plenty of code/ };

    class Line : public QDeclarativeItem{
    private:
    A* _attachPoint1;
    A* _attachPoint2;
    public:
    Line(A* pt1 = NULL, A* pt2 = NULL);
    public slots:
    void attachTo(A* pt1, A* pt2);
    void changePosition(A* newPosition);
    };
    @

    and then I have to connect:
    @
    connect(_attachPoint1,signal(onPositionChanged(A*)),this,SLOT(changePosition(A*)))

    void changePosition(A* newPosition){
    //Then I change my Line object properties thanks to the coordenates from the passed A object.
    }
    @

    But there is no onPositionChanged signal in my class A ... How can I code it?
    The position of my A objects changes when it is dragged (setting drag.target in a MouseArea into my object in QML), so, how can connect the fact of being dragged to my Line object?
    @
    A {
    id: myObject
    color: "red"
    width: 100
    height: 100
    MouseArea{
    id: ma;
    anchors.fill : parent;
    drag.target: myObject
    }
    }
    @
    I mean, when I drag it, the x and y properties of the object change, but how can I catch that changing?

    thank you again!



  • [quote author="excalibur1491" date="1357123707"]
    But there is no onPositionChanged signal in my class A … How can I code it?
    The position of my A objects changes when it is dragged (setting drag.target in a MouseArea into my object in QML), so, how can connect the fact of being dragged to my Line object?
    [/quote]

    If I understand you correctly you want to catch when your component is moving, right? If so, you may connect two signals to your slots, for example:
    @
    connect(item, SIGNAL(xChanged()), SLOT(onXChanged()));
    connect(item, SIGNAL(yChanged()), SLOT(onYChanged()));
    @

    In both methods which you set as slot you may get a sender objects and get his X and Y positions. The code for that may looks like:
    @

    void QmlApplicationViewer::onXChanged()
    {
    QDeclarativeItem* item = sender();

    if(item != NULL)
    {
        qDebug()<<"x: "<<item->x();
    }
    

    }

    void QmlApplicationViewer::onYChanged()
    {
    QDeclarativeItem* item = sender();

    if(item != NULL)
    {
        qDebug()<<"y: "<<item->y();
    }
    

    }
    @

    I hope this will help you.



  • Hi!

    Thanks you a lot! I didn't knew about "sender()" and that "connect".
    Ok, so I see more or less how to do the Line object and the two slots.
    But I dont really see how to emit those signals because when my object is moved, I'm not even sure that the object itself notices since it's moved with a drag in a MouseArea inside the object. So the object won't emit those signals.
    The big difficulty I have is how to emit those signals.
    The only way I see is:
    @
    A{
    id: a
    MouseArea{
    drag.target: a
    onPositionChanged:{
    if (drag.active){
    emit a.onXChanged()
    emit a.onYChanged()
    }
    }
    }
    }
    @

    Is that ven possible?
    There must be a better way to do it, isn't it?

    Thank you again, you're all being of big help :)



  • The signals xChanged() and yChanged() is a standard signals for QDeclarativeItem objects. So, If your component is a subclass from QDeclarativeItem you can use it like:
    @
    A{
    id: a

    onXChanged: {
    //Some code here
    }
    onYChanged: {
    //Some code here
    }
    }
    @

    If you need to get information about new position inside QML object. If you need get this information in C++ class which you want to use this signals, you can connect to the signal to some slot insdie you C++ class.

    If you want to send a some signal you may use something like this:
    @
    //<id_of_component>.<signal_name>(<list_of_parameters>);
    a.onXChanged(a.x);
    @

    By the way, when you try to set a new position for object with id like 'a' the signal 'onXChanged' will called automatically for current object. So you don't needed call this signal manually.



  • Thank you a lot!
    I didn't knew those signals were standard. When I read the documntation of QDeclarativeItem I was looking for somethink like "onPoistionChanged" and I forgot that signals don't appear in the methods list. My bad.

    Any way, thank you a lot to all of you. I will try to do that then.



  • [quote author="Jens" date="1357046647"]... Note that this works through rasterization so you will loose hardware acceleration and might have significantly higher memory requirements as a tradeoff. [/quote]

    Doesn't this use OpenGL accelerated QPainter, which IIRC draws paths by triangulating and drawing hardware accelerated geometry?

    On a side note, I myself have done a "mindmap network" style of app and think it will be significantly easier to implement with the trusty old QGraphicsView/Item/scene stack than with QML. Performance was pretty good too.



  • Hi again, I have started coding and it works mor or less, but I have a problem that I don't undestand at all:
    I have a line between two rectangles, and when I move them the line moves. The problem is that when I move one of them, the line moves properly, but when I move the other the line becomes "dashed", and ugly... it does not move properlly, and I don't get why since it only happens on one side.

    Here is the code:
    here is the base qml, nothing special abut it:
    @
    import QtQuick 1.0

    Rectangle{
    color: "white"
    width: 500
    height: 500

    Rectangle{
    objectName: "rec1"
    id: rec1
    x: 10
    y: 10
    width: 100
    height: 100
    radius: 50;
    color: "red"
    MouseArea{
    id:marec1
    anchors.fill: parent
    drag.target: rec1
    }
    }

    Rectangle{
    objectName: "rec2"
    id: rec2
    x: 200
    y: 200
    width: 100
    height: 100
    radius: 50;
    color: "blue"

     MouseArea{
        id:marec2
     anchors.fill: parent
     drag.target: rec2
     }     
    

    }
    }
    @

    Now, the Line class:
    @

    #include <QDeclarativeItem>
    #include <QPainter>
    #include <QPen>
    #include <QColor>

    class Line: public QDeclarativeItem{

    Q_OBJECT

    private:
    QDeclarativeItem* pt1;
    QDeclarativeItem* pt2;

    public:
    Line(QDeclarativeItem* pt1 = NULL, QDeclarativeItem* pt2 = NULL);

    public slots:
    void attachPt1(QDeclarativeItem* pt1);
    void attachPt2(QDeclarativeItem* pt2);
    void paint(QPainter* p, const QStyleOptionGraphicsItem* o = NULL, QWidget* w= NULL);
    void _update(); //The aim of this slot is that update() is not a slot, so it only calls update().
    };
    @

    The implementation:
    @
    #include "Line.h"
    #include <QTextStream>
    #include <QIODevice>

    Line::Line(QDeclarativeItem* pt1, QDeclarativeItem* pt2):QDeclarativeItem(NULL),pt1(pt1),pt2(pt2){
    setFlag(QGraphicsItem::ItemHasNoContents, false);
    }

    void Line::attachPt1(QDeclarativeItem* pt1){
    this->pt1 = pt1;
    update();
    QObject::connect(pt1,SIGNAL(xChanged()),this,SLOT(_update()));
    QObject::connect(pt1,SIGNAL(yChanged()),this,SLOT(_update()));
    }
    void Line::attachPt2(QDeclarativeItem* pt2){
    this->pt2 = pt2;
    update();
    QObject::connect(pt2,SIGNAL(xChanged()),this,SLOT(_update()));
    QObject::connect(pt2,SIGNAL(yChanged()),this,SLOT(_update()));
    }
    void Line::_update(){update();}

    void Line::paint(QPainter* p, const QStyleOptionGraphicsItem* o, QWidget* w){

    QTextStream ts (stderr,QIODevice::WriteOnly);
    ts << "paint " << pt1<< " " << pt2 << " " <<sender()<< endl;
    QPen pen(QColor("red"),2);
    p->setPen(pen);
    QLine line(pt1->x()+pt1->width()/2,
    pt1->y()+pt1->height()/2,
    pt2->x()+pt2->width()/2,
    pt2->y()+pt2->height()/2);
    p->drawLine(line);

    }
    @
    When the two extremities are set, the connection is done.
    The drawing function only uses a QLine, nothing special about it.

    Last, the main:
    @
    #include <QApplication>
    #include <QDebug>
    #include <QObject>
    #include <QDeclarativeEngine>
    #include <QDeclarativeComponent>
    #include <QDeclarativeItem>
    #include <QDeclarativeView>
    #include <QString>
    #include <QTextStream>
    #include <QIODevice>

    #include "Line.h"

    int main(int argc, char* argv[]){

    QApplication app(argc, argv);
    QTextStream ts(stderr,QIODevice::WriteOnly);

    qmlRegisterType<Line>("LinesTest", 1,0, "Line");

    QDeclarativeView* view = new QDeclarativeView;
    view->setSource(QUrl::fromLocalFile("Base.qml";);

    QDeclarativeEngine* e = view->engine();
    QDeclarativeComponent c (e,QUrl("example.qml"));
    Line *l = qobject_cast<Line *>(c.create());

    qWarning() << c.errors();

    QDeclarativeItem* r1 = view->rootObject()->findChild<QDeclarativeItem*>("rec1");
    QDeclarativeItem* r2 = view->rootObject()->findChild<QDeclarativeItem*>("rec2");

    l->attachPt1(r1);
    l->attachPt2(r2);

    l->setParentItem(qobject_cast<QDeclarativeItem*>(view->rootObject()));
    view->show();

    return app.exec();
    }
    @
    The example.qml file does not contain anythin special, just this:
    @
    import LinesTest 1.0
    import QtQuick 1.0

    Line{
    smooth: true
    //color: "red";
    }
    @

    There is nt big code, but I really dont see where the bug comes from. It is the first time I use a QLine class, I hope it is OK.

    Thank you again!



  • Any idea, please?

    thanks again



  • I didn't review your code at detail but lately I've been doing the same as you (a graph editor) and, while in my case I wanted to find a pure QML+JavaScript solution with Qt 5, I found a Line component example from Nokia, implemented in Qt 4. Maybe you should follow their code:
    http://www.developer.nokia.com/Community/Wiki/Creating_a_custom_QML_element_with_Qt



  • For taking advantage of the scenegrapgh, you might want to look at this example, which pretty much implements a geometry node for a bezier curve:

    http://doc-snapshot.qt-project.org/5.0/qtqml/quick-scenegraph-customgeometry.html



  • Hi, I have already ued this example: http://www.developer.nokia.com/Community/Wiki/Creating_a_custom_QML_element_with_Qt
    and it works great.
    I thought about the Beziers curve, but I need to have a mousearea covering the line so it would be way more dificult.
    Any way, thank you all


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.