-1

I have a QListWidget and a QGraphicsView both subclassed to overwrite some of their members. I prepared a minimal verifiable example showing the problem I have here

From the QListWidget I can drag and drop specific field (represented by a QTableWidget) and drop them into a QGraphicsView and in order to do that I am using a QGraphicsProxyWidget approach as shown below.

The Problem

Now, how do I connect 2 QRadioButton inside cell of a QTableWidget with another cell of another QTableWidget?

It is important to mention that the green QGraphicsRectItem it is used to move around the QTableWidget as well as adjusting its dimension.

Below the result I was able to arrive so far:

pr3

And below the expected result I have been trying to achieve:

pr4

Below the most important part of the code:

scene.h

#ifndef SCENE_H
#define SCENE_H

#include <QGraphicsScene>

class Scene : public QGraphicsScene
{
public:
    Scene(QObject *parent = nullptr);

protected:
  void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
  void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
  void dropEvent(QGraphicsSceneDragDropEvent *event);
};

#endif // SCENE_H

scene.cpp

#include "arrow.h"

#include <QGraphicsSceneDragDropEvent>
#include <QMimeData>
#include <QTableWidget>
#include <QGraphicsProxyWidget>
#include <QVBoxLayout>
#include <QMetaEnum>
#include <QEvent>
#include <QSizeGrip>
#include <QRadioButton>


Scene::Scene(QObject *parent)
{
    setBackgroundBrush(Qt::lightGray);

}

void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
  if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
    event->setAccepted(true);
}

void Scene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
  if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist"))
    event->setAccepted(true);
}

void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {
    QByteArray encoded =
      event->mimeData()->data("application/x-qabstractitemmodeldatalist");
    QDataStream stream(&encoded, QIODevice::ReadOnly);

    QStringList rosTables;
    QString newString;

    while (!stream.atEnd()) {
    int row, col;
    QMap<int, QVariant> roleDataMap;
    stream >> row >> col >> roleDataMap;
    rosTables << roleDataMap[Qt::DisplayRole].toString();
    }
    for (const QString &tableType : rosTables) {
        if (tableType == "Images") {
            QPoint initPos(0, 0);
            auto *wgt = new CustomTableWidget;
            auto *proxyControl = addRect(0, 0, 0, 0, QPen(Qt::black),
                                         QBrush(Qt::darkGreen));
            auto *sizeGrip = new QSizeGrip(wgt);
            auto *layout = new QHBoxLayout(wgt);

            layout->setContentsMargins(0, 0, 0, 0);
            layout->addWidget(sizeGrip, 0, Qt::AlignRight | Qt::AlignBottom);

            connect(wgt, &CustomTableWidget::sizeChanged, [wgt, proxyControl](){
                proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
            });

            wgt->setColumnCount(4);
            wgt->setRowCount(4);

            for (int ridx = 0; ridx < wgt->rowCount(); ridx++) {
                for (int cidx = 0; cidx < wgt->columnCount(); cidx++) {
                    QRadioButton *radio1, *radio2;
                    auto* item = new QTableWidgetItem();
                    item->setText(QString("%1").arg(ridx));
                    wgt->setItem(ridx,cidx,item);
                    radio1 = new QRadioButton;
                    radio2 = new QRadioButton;
                    wgt->setCellWidget(cidx, 0, radio1);
                    wgt->setCellWidget(cidx, 3, radio2);
                    Arrow *arrow = new Arrow; 
                }
            }

            auto *const proxy = addWidget(wgt);

            proxy->setPos(initPos.x(), initPos.y()
                          + proxyControl->rect().height());
            proxy->setParentItem(proxyControl);

            proxyControl->setPos(initPos.x(), initPos.y());
            proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
            proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);
            proxyControl->setRect(wgt->geometry().adjusted(-10, -10, 10, 10));
        }
    }
}

diagramitem.h

#ifndef DIAGRAMITEM_H
#define DIAGRAMITEM_H

#include <QGraphicsPolygonItem>

class Arrow;
class DiagramItem : public QGraphicsPolygonItem
{
public:

    DiagramItem(QMenu *contextMenu, QGraphicsItem *parent = Q_NULLPTR);

    void removeArrow(Arrow *arrow);
    void removeArrows();
    void addArrow(Arrow *arrow);
    QPixmap image() const;
protected:
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
private:
    QPolygonF myPolygon;
    QList<Arrow*> arrows;
    QMenu *myContextMenu;
};

#endif // DIAGRAMITEM_H

diagramitem.cpp

#include "diagramitem.h"
#include "arrow.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>


DiagramItem::DiagramItem(QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent)
{
    myContextMenu = contextMenu;
    setPolygon(myPolygon);
    setFlag(QGraphicsItem::ItemIsMovable, true);
    setFlag(QGraphicsItem::ItemIsSelectable, true);
    setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);

}

void DiagramItem::removeArrow(Arrow *arrow)
{
    int index = arrows.indexOf(arrow);

    if (index != -1)
        arrows.removeAt(index);
}

void DiagramItem::removeArrows()
{
    foreach (Arrow *arrow, arrows) {
        arrow->startItem()->removeArrow(arrow);
        arrow->endItem()->removeArrow(arrow);
        scene()->removeItem(arrow);
        delete arrow;
    }
}

void DiagramItem::addArrow(Arrow *arrow)
{
    arrows.append(arrow);
}

void DiagramItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    scene()->clearSelection();
    setSelected(true);
    myContextMenu->exec(event->screenPos());
}

QVariant DiagramItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    if (change == QGraphicsItem::ItemPositionChange) {
        foreach (Arrow *arrow, arrows) {
            arrow->updatePosition();
        }
    }
    return value;
}

arrow.h

#ifndef ARROW_H
#define ARROW_H

#include <QGraphicsLineItem>
#include "diagramitem.h"
class Arrow : public QGraphicsLineItem
{
public:

    enum { Type = UserType + 4 };
    Arrow(DiagramItem *startItem, DiagramItem *endItem,
      QGraphicsItem *parent = nullptr);

    DiagramItem *startItem() const { return myStartItem; }
    DiagramItem *endItem() const { return myEndItem; }
    QPainterPath shape() const override;

    void setColor(const QColor &color) {
        myColor = color;
    }

    int type() const override { return Type; }
    void updatePosition();

protected:
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;

private:
    QColor myColor;
    DiagramItem *myStartItem;
    DiagramItem *myEndItem;
    QPolygonF arrowHead;
};
#endif // ARROW_H

arrow.cpp

#include "arrow.h"
#include <QPen>
#include <QPainter>
#include "qmath.h"

Arrow::Arrow(DiagramItem *startItem, DiagramItem *endItem, QGraphicsItem *parent) : QGraphicsLineItem(parent)
{
    myStartItem = startItem;
    myEndItem = endItem;
    myColor = Qt::GlobalColor::black;
    setPen(QPen(myColor, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
    setFlag(QGraphicsItem::ItemIsSelectable, true);
}

QPainterPath Arrow::shape() const
{
    QPainterPath path = QGraphicsLineItem::shape();
    path.addPolygon(arrowHead);
    return path;
}

void Arrow::updatePosition()
{
    QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
    setLine(line);
}

void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)
    if (myStartItem->collidesWithItem(myEndItem))
        return;

    QPen myPen = pen();
    myPen.setColor(myColor);
    qreal arrowSize = 20;
    painter->setPen(myPen);
    painter->setBrush(myColor);

    QLineF centerLine(myStartItem->pos(), myEndItem->pos());
    QPolygonF endPolygon = myEndItem->polygon();
    QPointF p1 = endPolygon.first() + myEndItem->pos();
    QPointF p2;
    QPointF intersectPoint;
    QLineF polyLine;
    for (int i = 1; i < endPolygon.count(); ++i) {
        p2 = endPolygon.at(i) + myEndItem->pos();
        polyLine = QLineF(p1, p2);
        QLineF::IntersectType intersectType =
            polyLine.intersect(centerLine, &intersectPoint);
        if (intersectType == QLineF::BoundedIntersection)
            break;
        p1 = p2;
    }

    setLine(QLineF(intersectPoint, myStartItem->pos()));

    double angle = std::atan2(-line().dy(), line().dx());

    QPointF arrowP1 = line().p1() + QPointF(sin(angle + M_PI / 3) * arrowSize,
                                    cos(angle + M_PI / 3) * arrowSize);
    QPointF arrowP2 = line().p1() + QPointF(sin(angle + M_PI - M_PI / 3) * arrowSize,
                                    cos(angle + M_PI - M_PI / 3) * arrowSize);

    arrowHead.clear();
    arrowHead << line().p1() << arrowP1 << arrowP2;

    painter->drawLine(line());
    painter->drawPolygon(arrowHead);
    if (isSelected()) {
        painter->setPen(QPen(myColor, 1, Qt::DashLine));
        QLineF myLine = line();
        myLine.translate(0, 4.0);
        painter->drawLine(myLine);
        myLine.translate(0,-8.0);
        painter->drawLine(myLine);
    }
}

What I have done so far to solve the problem:

1) I came across this post which was useful to understand the initial idea on how to do that, but it didn't really provide a way, or an implementation idea on how to best proceed

2) I researched the official documentation and before asking this question I went through the whole Diagram Scene example provided and understood how to create an Arrow object. The documentation about that was very good and made me understand how the graphics line item has to be formed. However I was not able (coming back to my example) how to make "aware" the QRadioButton that I am trying to use its center as starting point for an arrow ad, therefore, how do I make "aware" the destination QRadioButton in another cell that it has to be connected there?

Below a particular of what I mean:

pr5

So basically the start point of the QRadioButton change color (or style) and the arrival point also change color.

3) I thought that the Arrow object has to be created inside the subclassed QGraphicsScene since it already handles the mouse events.

4) Despite what I tried so far I could not find any other useful help. Although I am still investigating how to do that.

If anyone has ever been in the same situation please provide guidance on how to better proceed to solve this problem and find a solution to this issue.

scopchanov
  • 7,966
  • 10
  • 40
  • 68
Emanuele
  • 2,194
  • 6
  • 32
  • 71
  • Is your question actually, how to get the screen position of both, start and end, widgets? – scopchanov Oct 19 '20 at 10:06
  • Hi! Yes I think it that is also ok because the arrow has to be drawn from positionA to positionB of both widget. The arriving point is where the arrow will be pointing. So the screen position of radio button A to screen position of radio button B – Emanuele Oct 19 '20 at 13:13
  • Is this an example, or your real project? – scopchanov Oct 19 '20 at 14:27
  • Hey thanks for asking :) . I am building a major interface with Qt5 and ROS. And in order to shrink the problem I have I had to extract the most important concepts and organize a minimal example. It really took me a while to structure it and explain the problem I have. So yes this is an example project that does replicate the problem I have on the major gui. – Emanuele Oct 19 '20 at 16:05
  • If you are interested I can describe more what I am building though :) – Emanuele Oct 19 '20 at 16:06
  • It would be interesting for me for sure. However, I am asking not just out of curiosity, but because I see some flaws in your code. This prevents me to provide a nice solution to the problem you describes. We could try to discuss that in the chat here. – scopchanov Oct 19 '20 at 18:45
  • Sure no problem! I would be happy to talk about it so we can arrive to a solution! :) – Emanuele Oct 19 '20 at 21:19
  • Which other details you need from me? I also attached the working code with my bitbucket account if you need to look at the implementation. – Emanuele Oct 19 '20 at 21:22
  • Which project is this? There are several there. – scopchanov Oct 19 '20 at 21:47
  • Yes, it is [this question](https://bitbucket.org/ERaggi/so_qproxy/src/master/) – Emanuele Oct 19 '20 at 21:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/223322/discussion-between-scopchanov-and-emanuele). – scopchanov Oct 19 '20 at 22:57
  • 1
    Hi @scopchanov! how are you doing today? I just wanted to let you know that I committed all the changes I made today after we discussed yesterday. It still does not work but we maybe close to the solution. You can see [here](https://bitbucket.org/ERaggi/so_qproxy/src/master/) the latest commits. – Emanuele Oct 20 '20 at 20:20
  • Also [here](https://i.imgur.com/2mA2YWS.png) I prepared the real pipeline based on the example I just committed on bitbucket. I modified the code so that it reflects the passages from ROS beginning -> reading the messages -> going to shutdown after done – Emanuele Oct 20 '20 at 20:43
  • I am working on it. It will take time. Be patient. – scopchanov Oct 20 '20 at 21:08
  • Thanks for your help! I am on it too! So will let you know soon if I have good updates! in the next hours :) – Emanuele Oct 20 '20 at 21:11

1 Answers1

0

Solution

When a start and end radio buttons are checked, you need to create the arrow with those buttons as start and end nodes, e.g.:

void Backend::onInputRadioButton(bool checked)
{
    m_endNode = checked ? static_cast<QRadioButton *>(sender()) : nullptr;

    if (m_startNode && m_endNode)
        m_scene->addItem(new ArrowItem(m_startNode, m_endNode));
}

Then you need to connect the signal of the top-most graphics items, which hold the tables, with the updatePosition slot of the ArrowItem, e.g.:

connect(m_startItem->property("item").value<MovableItem *>(),
        &MovableItem::itemMoved, this, &ArrowItem::updatePosition);
connect(m_endItem->property("item").value<MovableItem *>(),
        &MovableItem::itemMoved, this, &ArrowItem::updatePosition);

Note: I am using a property to hold a reference to the container item.

Finally, you need to update the arrow line, e.g.:

void ArrowItem::updatePosition()
{
    QPointF offset(7, 15);
    QPointF p1 = m_startItem->property("item").value<MovableItem *>()->pos()
                 + m_startItem->parentWidget()->mapToParent(m_startItem->pos())
                 + offset;
    QPointF p2 = m_endItem->property("item").value<MovableItem *>()->pos()
                 + m_endItem->parentWidget()->mapToParent(m_endItem->pos())
                 + offset;

    setLine(QLineF(p1, p2));
}

Example

I have dared to suggest improvements in your code. You can find the complete example I wrote for you on GitHub.

Result

The provided example produces the following result:

App window with interconnected tables

Note: The arrow heads are missing. Check once again the Diagram Scene Example to get an idea of how to draw them.

scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • 1
    Thanks!! :) I am going through it right now! Will let you know shortly. The refactored code looks much cleaner and I can actually read it better! :) I see the grid a other great details! Wow this just great! Let me compile it and build it! Give me some minutes! :) – Emanuele Oct 22 '20 at 01:03
  • Hey on my profile there is my lab email. I'd like to touch base with you to thank you! I would upvote your answer 1000 times if I could! :) Gonna be working on it a bit more and than going to bed! – Emanuele Oct 22 '20 at 04:03