Search code examples
c++11qt5qtablewidgetqstringqenum

How to properly compare Q_ENUM and QStringList


I have a small example .ui where the user can drag and drop from a QListWidget to a QGraphicsView using QGraphicsProxyWidget a specific widget (in my case is a QTableWidget) as shown below.

Basically the behavior I have been looking for is:

If I drag and drop "Imgaes" than the QTableWidget on the QGraphicsView will have 2 columns and 2 rows; (which is correct) If I drag and drop "Path" than the QTableWidget on the QGraphicsView will have 3 columns and 3 rows; (which is wrong)

pr1

[1] : https://i.sstatic.net/oVuea.jpg

Below the selection of "Path", which still give 2 rows and 2 columns instead of 3 rows and 3 columns

pr2

[2] : https://i.sstatic.net/kLRSx.jpg

Below the code:

scene.h

class Scene : public QGraphicsScene {

    enum LaserTableWidget {
      Images,
      Path
    };
    Q_ENUM(LaserTableWidget)

public:
  Scene(QObject *parent = nullptr);

  void compare(const QString& comp);

  template<typename QEnum>
  std::string QtEnumToString (const QEnum value)
  {
    return std::string(QMetaEnum::fromType<QEnum>().valueToKey(value));
  }

protected:
  void dropEvent(QGraphicsSceneDragDropEvent *event);

scene.cpp

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

void Scene::compare(const QString &comp)
{
    // get information about the enum named "LaserTableWidget"
    QMetaObject MetaObject = this->staticMetaObject;
    QMetaEnum MetaEnum = MetaObject.enumerator(MetaObject.indexOfEnumerator("LaserTableWidget"));
    QStringList tabs;

    switch (MetaEnum.keyToValue(comp.toUpper().toLatin1()))
    // or simply switch (MetaEnum.keyToValue(word)) if no string modification is needed
    {
        case Images:
        for (const QString &color : tabs) {
            QPoint initPos(0,0);

            QTableWidget *wgt = new QTableWidget;
            QGraphicsRectItem *proxyControl = addRect(initPos.x(), initPos.y(), wgt->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen)); // widget->width() works properly here because of the resize(layout->sizeHint()) that we have used inside it
            proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
            proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);

            wgt->setColumnCount(2);
            wgt->setRowCount(2);
            for (int ridx = 0 ; ridx < wgt->rowCount() ; ridx++ )
            {
                for (int cidx = 0 ; cidx < wgt->columnCount() ; cidx++)
                {
                    QTableWidgetItem* item = new QTableWidgetItem();
                    item->setText(QString("%1").arg(ridx));
                    wgt->setItem(ridx,cidx,item);
                }
            }
            QGraphicsProxyWidget * const proxy = addWidget(wgt);
            // In my case the rectangular graphics item is supposed to be above my widget so the position of the widget is shifted along the Y axis based on the height of the rectangle of that graphics item
            proxy->setPos(initPos.x(), initPos.y()+proxyControl->rect().height());
            proxy->setParentItem(proxyControl);
        }

        break;
        case Path:
        for (const QString &color : tabs) {
            QPoint initPos(0,0);

            QTableWidget *wgt = new QTableWidget;
            QGraphicsRectItem *proxyControl = addRect(initPos.x(), initPos.y(), wgt->width(), 20, QPen(Qt::black), QBrush(Qt::darkGreen)); // widget->width() works properly here because of the resize(layout->sizeHint()) that we have used inside it
            proxyControl->setFlag(QGraphicsItem::ItemIsMovable, true);
            proxyControl->setFlag(QGraphicsItem::ItemIsSelectable, true);

            wgt->setColumnCount(3);
            wgt->setRowCount(3);
            for (int ridx = 0 ; ridx < wgt->rowCount() ; ridx++ )
            {
                for (int cidx = 0 ; cidx < wgt->columnCount() ; cidx++)
                {
                    QTableWidgetItem* item = new QTableWidgetItem();
                    item->setText(QString("%1").arg(ridx));
                    wgt->setItem(ridx,cidx,item);
                }
            }
            QGraphicsProxyWidget * const proxy = addWidget(wgt);
            // In my case the rectangular graphics item is supposed to be above my widget so the position of the widget is shifted along the Y axis based on the height of the rectangle of that graphics item
            proxy->setPos(initPos.x(), initPos.y()+proxyControl->rect().height());
            proxy->setParentItem(proxyControl);
        }
        break;

        default:
        break;
    }
}


void Scene::dropEvent(QGraphicsSceneDragDropEvent *event) {

  QByteArray encoded =
      event->mimeData()->data("application/x-qabstractitemmodeldatalist");
  QDataStream stream(&encoded, QIODevice::ReadOnly);

  QStringList rosTables;

  while (!stream.atEnd()) {
    int row, col;
    QMap<int, QVariant> roleDataMap;
    stream >> row >> col >> roleDataMap;
    rosTables << roleDataMap[Qt::DisplayRole].toString();
  }

  compare(const QString &comp)
}

I tried to use the template function declared on the header file that for completeness I am also attaching below:

  template<typename QEnum>
  std::string QtEnumToString (const QEnum value)
  {
    return std::string(QMetaEnum::fromType<QEnum>().valueToKey(value));
  }

Maybe I the template function is the wrong choice? I was trying to find a way to use it if possible.

That is the reason why I switched to a void compare(const QString& comp); function and tried to delegate the job to a switch - case statement but that also is not working as I expect and I still see the same exact QtableWidget dropped onto the QGraphicsView.

Of course I did more research and came across this source and above all this post which was useful to understand the basic comparison and following this post I decided to go ahead and try to apply the Q_ENUM - QString or even the QStringList as a valuable tool. But I could not figure out what I was doing wrong.

Can anyone please shed light on which approach could be better? (or maybe they are both correct) and try to explain what I am missing to solve this problem.


Solution

  • There are two problems I see in your code:

    1. Wrong parameter passed to keyToValue(). Since you have Image and Path in your enum, the valid values to pass to keyToValue() are "Image" (returns 0), and "Path" (return 1), other values will returns -1.

    2. In the function Scene::compare(), tabs is just created as an empty QStringList, so the code inside the loops for (const QString &color : tabs) are never executed

    Below is a test program to show you what I mean:

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    #include <QMetaEnum>
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
        enum LaserTableWidget
        {
            Images,
            Path
        };
        Q_ENUM(LaserTableWidget)
    
        template<typename enum_type>
        QString QtEnumToString (const enum_type value)
        {
            return QMetaEnum::fromType<enum_type>().valueToKey(value);
        }
    };
    #endif // MAINWINDOW_H
    

    mainwindow.cpp

    #include "mainwindow.h"
    
    #include <QDebug>
    #include <QStringList>
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
        // First problem: wrong parameter passed to keyToValue()
    
        qDebug() << 1 << QtEnumToString<LaserTableWidget>(Images);
        qDebug() << 2 << QtEnumToString<LaserTableWidget>(Path);
    
        QMetaObject MetaObject = this->staticMetaObject;
        QMetaEnum MetaEnum = MetaObject.enumerator(MetaObject.indexOfEnumerator("LaserTableWidget"));
    
        qDebug() << 3 << MetaEnum.keyToValue(QtEnumToString<LaserTableWidget>(Path).toUpper().toLatin1());
        qDebug() << 4 << MetaEnum.keyToValue(QtEnumToString<LaserTableWidget>(Path).toStdString().c_str());
    
        switch (MetaEnum.keyToValue(QtEnumToString<LaserTableWidget>(Path).toUpper().toLatin1()))
        {
            case Images:
                qDebug() << "switch Images";
                break;
            case Path:
                qDebug() << "switch Path";
                break;
            default:
                qDebug() << "switch default";
                break;
        }
    
        // Second problem: tabs is empty
        QStringList tabs;
        for (const QString &color : tabs)
            qDebug() << color; // this line is never executed
    }
    
    MainWindow::~MainWindow()
    {
    }
    

    Output:

    1 "Images"
    2 "Path"
    3 -1
    4 1
    switch default