Bazilikum
Bazilikum

Reputation: 67

Qt QItemSelection::indexes() return is wrong

I implemented a derived class from QAbstractItemModel, and it seems to work fine. Then I created a QItemSelectionModel and assigned it to the mentioned QAbstractItemModel.

My QAbstractItemModel is not a widget and is not displayed, only manages a hierarchy. When the selection is changed, and the QItemSelection model emits the selection changed signal the QItemSelections selected and deselected seem to contain the right data.

The problem arises when i call their ::indexes() function to get the indexes of the selected items, it returns no items, even so I know items are selected and the ::width() and ::height() functions return correct values.

Basic example code: (A working example and files that demonstrates the problem follows)

class DerivedModel : public QAbstractItemModel {
    DerivedModel(QObject* parent) : QAbstractItemModel(parent)
                                   ,m_selectionModel(nullptr)
    {
        //create the selection model and assign this model to it
        m_selectionModel = new QItemSelectionModel(this, this);
    }
...
//all needed overload functions
//the DerivedModel works great
...
private:
    QItemSelectionModel* m_selectionModel;
}

//in a different object called SceneModel (a QGraphicsScene which shows graphical items based on the DerivedModel) which is connected to the selection models selectionChanged() signal I query the new selection

SceneModel::setSelectedItems(const QItemSelection& selected, const QItemSelection& deselected){

int selectionSize_A = selected.size(); //this returns correct number of selected items
int selectionSize_B = selected.indexes().size(); //this returns 0 -> WRONG
int selectionSize_C = selected.value(0).indexes().size(); //this returns 0 -> WRONG
int selectionSize_CA = selected.value(0).width(); //this returns correct
int selectionSize_CB = selected.value(0).height(); //this returns correct

//if I purposefully try to access the 1st selected index via QItemSelectionRange::topLeft() all is good and I get the index:
QItemSelectionRange range = selected.value(0);
QModelIndex topLeft = range.topLeft(); //cool, i get the 1st selected index

//it seems there is a problem with the ::indexes function, so dived into the Qt5 source and basically implemented again whats done there and it works.
}

A link to the files including cmake build: https://drive.google.com/file/d/0Bz03DnXr46WXYXRCeExtaHZadUU/view?usp=sharing

Whats happening there: A DerivedModel is created and holds 2 items (A and B) under root item (ROOT). Pushing a button signals the QItemSelectionModel to select/deselect A or B. If the item is found in the model "Found Item :)" is printed, showing the item exists and is available to the model. The QGraphicsView holds a Scene (derived from QGraphicsScene). That scene is empty, and only represents an object receiving the selectionChange signal from the selection model. When it receives that signal it prints "Scene received item selection change" so we can see the signal has passed. then comes the real stuff:

  1. we get a count of how many QItemRanges are in the passed "selected" variable, which is correct.
  2. we get a count of how many indexes are inside all the ranges in the passed "selected" variable (selected.indexes()) that returns 0 which is wrong as we can soon see
  3. we manually access the 1st index in the 1st range in the "selected" variable (selected.value(0).topLeft()) and see that it indeed holds an index pointing to the right item, showing the problem.

If anybody knows about something, or can see an error in my approach please let me know. Thanks!

Linux Manjaro Gcc 4.9.1 Qt5.3

DerivedModel.h:

#ifndef DERIVEDMODEL_H
#define DERIVEDMODEL_H 

#include <QAbstractItemModel>

//fwd declaration
QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)

class Item;

class DerivedModel : public QAbstractItemModel{
    Q_OBJECT
public:
    //model is a singleton, function to get instance
    static DerivedModel& instance();

    explicit DerivedModel(QObject* parent);
    virtual ~DerivedModel();

    /////////////////model overloads//////////////////////////////
    QVariant data(const QModelIndex& index, int role) const;
    Qt::ItemFlags flags(const QModelIndex& index) const;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
    QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
    QModelIndex parent(const QModelIndex& index) const;
    int rowCount(const QModelIndex& parent = QModelIndex()) const;
    int columnCount(const QModelIndex& parent = QModelIndex()) const {Q_UNUSED(parent); return 1;}
    //////////////////////////////////////////////////////////////

    //get the item from an index
    Item* item(const QModelIndex& index) const { return static_cast<Item*>(index.internalPointer());}

    //get the index from an item name
    const QModelIndex indexFromName(const QString& name);

    //add an item
    void addItem(const QString& name, Item* parent=nullptr);

    //get the selection model
    QItemSelectionModel* selectionModel() const {return m_selectionModel;}

private:
    //the instance of the singleton to return
    static DerivedModel* m_instance;

    //the root object for the model
    //never actually used
    Item* m_rootItem;

    //selection model for handeling selection
    QItemSelectionModel* m_selectionModel;
};
#endif

DerivedModel.cpp

#include "DerivedModel.h"

#include "Item.h"

#include <QItemSelectionModel>
#include <QDebug>

//init static member
DerivedModel* DerivedModel::m_instance = nullptr;

DerivedModel& DerivedModel::instance(){
    //check if set
    if(!m_instance){
        qDebug() << "ERROR model instance not set";
        std::abort();
    }
    return *m_instance;
}

DerivedModel::DerivedModel(QObject* parent):
                            QAbstractItemModel(parent)
                            ,m_rootItem(nullptr)
                            ,m_selectionModel(nullptr)
{
    //set the instance
    m_instance = this;

    //creae root item
    m_rootItem = new Item("ROOT");

    //init selection model
    m_selectionModel = new QItemSelectionModel(this, this);
}

DerivedModel::~DerivedModel(){
    //selection model is child so gets deleted
}

QVariant DerivedModel::data(const QModelIndex& index, int role) const {
    //if the index is valid
    if(!index.isValid()) {
        qDebug() << "Index not valid!";
        return QVariant();
    }

    //switch role
    switch(role){
        case Qt::DisplayRole:{
            QString name = static_cast<Item*>(index.internalPointer())->name();
            return name;
            break;
         }
        default:
            return QVariant();
    }
}

Qt::ItemFlags DerivedModel::flags(const QModelIndex& index) const {
    //check valid
    if(!index.isValid()) return 0;

    return static_cast<Item*>(index.internalPointer())->flags();
}

QVariant DerivedModel::headerData(int section, Qt::Orientation orientation, int role) const {
    //unused for now
    Q_UNUSED(section);
    Q_UNUSED(orientation);
    if(role==Qt::DisplayRole) return QVariant("HeaderData");
    else return QVariant();
}

QModelIndex DerivedModel::index(int row, int column, const QModelIndex& parent) const {

    Item* parentItem(nullptr);

    //is valid?
    if(!parent.isValid()) {
        parentItem = m_rootItem;
    }
    else {
        parentItem = item(parent);
    }

    //child pointer holder
    Item* childItem = parentItem->children().value(row);
    //is null?
    if(childItem){
        return createIndex(row, column, childItem);
    }
    else {
        return QModelIndex();
    }
}

QModelIndex DerivedModel::parent(const QModelIndex& index) const {
    //check valid
    if(!index.isValid()) return QModelIndex();

    //get child
    Item* childItem = static_cast<Item*>(index.internalPointer());

    //find parent
    Item* parentItem = childItem->parent();

    //is null?
    if(parentItem == m_rootItem) return QModelIndex();

    return createIndex(parentItem->parent()->children().indexOf(parentItem), 0, parentItem);
}

int DerivedModel::rowCount(const QModelIndex& parent) const {
    //parent holder
    Item* parentItem;

    //check 0 column (not sure why, but is in example, maybe the model iterates also through different columns)
    if(parent.column()>0) return 0;

    //check valid
    if(!parent.isValid()) parentItem = m_rootItem;
    else parentItem = static_cast<Item*>(parent.internalPointer());

    return parentItem->children().length();
}

const QModelIndex DerivedModel::indexFromName(const QString& name){
    //make a match based on the name
    //and return 1st match
    QModelIndex index = match(DerivedModel::index(0,0,QModelIndex()),
            Qt::DisplayRole, name, 1, 
            Qt::MatchFlags(Qt::MatchExactly|Qt::MatchRecursive))
            .value(0);

    return index;
}

void DerivedModel::addItem(const QString& name, Item* parent){
    //check parent
    if(!parent) parent = m_rootItem;

    //create the item
    //will be deleted once parent is deleted
    new Item(name, parent); 
}

Item.h:

#ifndef ITEM_H
#define ITEM_H

#include <QString>
#include <QList>
#include <QDebug>

class Item {

public:
    Item(const QString& name, Item* parent=nullptr) :
                                m_name(name), m_parent(parent){
        //add as child to parent
        if(parent) parent->addChild(this);

        //set the flag to enable selection
        m_flags = Qt::ItemIsSelectable;

        qDebug() << "Created Item "+name;
    };

    ~Item(){
        //delete children
        for (auto& child : m_children){
            delete child;
        }
    };

    //get the name
    const QString& name() const {return m_name;}

    //get the flags
    const Qt::ItemFlags& flags() const {return m_flags;}

    //gte the parent
    Item* parent() const {return m_parent;}

    //get the children
    const QList<Item*>& children() {return m_children;}

    //add a child
    void addChild(Item* item) {m_children.append(item);}

private:
    //name
    QString m_name;

    //flags
    Qt::ItemFlags m_flags;

    //parent
    Item* m_parent;

    //list og children
    QList<Item*> m_children;
};


#endif

Scene.h:

#ifndef GRAPHICSSCENE_H
#define GRAPHICSSCENE_H

#include <QGraphicsScene>

//fwd dec
QT_FORWARD_DECLARE_CLASS(QItemSelection)
QT_FORWARD_DECLARE_CLASS(QGraphicsRectItem)

class Scene : public QGraphicsScene {
public:
    Scene(QObject* parent);
    virtual ~Scene(){}

public slots:
    //pass the selection to the squares
    void setSelection(const QItemSelection& selected, const QItemSelection& deselected);
};

#endif

Scene.cpp:

#include "Scene.h"
#include "DerivedModel.h"
#include "Item.h"

#include <QItemSelectionModel>
#include <QGraphicsRectItem>
#include <QRect>
#include <QDebug>

Scene::Scene(QObject* parent) : QGraphicsScene(parent)
{
    //connect to the models selection change
    connect(DerivedModel::instance().selectionModel(), &QItemSelectionModel::selectionChanged,
            this, &Scene::setSelection);
}

void Scene::setSelection(const QItemSelection& selected, const QItemSelection& deselected){
    Q_UNUSED(deselected);
    //testing changes
    int A = selected.size();
    int B = selected.indexes().size();

    QModelIndex index = selected.value(0).topLeft();
    QString name = "";
    if(index.isValid()) name = static_cast<Item*>(index.internalPointer())->name();

    qDebug() << "Scene recieved item selection change";
    qDebug() << "Number of selected QItemRanges from source = "+QString::number(A); 
    qDebug() << "Number of selected INDEXES from source = "+QString::number(B); 
    qDebug() << "Manually accessd 1st index in 1st range returns item named: "+name;
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

//fwd dec
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
QT_FORWARD_DECLARE_CLASS(QGraphicsView)

class DerivedModel;
class Scene;

class Widget : public QWidget{

public:
    Widget(QWidget* parent=nullptr);
    virtual ~Widget(){}

private slots:
    //button toggle alot
    void toggle(bool state);

private:
    //layout
    QVBoxLayout* m_mainVBLayout;

    //horizontal layout for the buttons
    QHBoxLayout* m_HBButtonsLayout;

    //GraphicsView widget for the scene
    QGraphicsView* m_graphicsView;

    //the graphics scene recieving the change event where the problem is
    Scene* m_scene;

    //push buttons
    QPushButton* m_button1;
    QPushButton* m_button2;

    //the DerivedModel
    DerivedModel* m_model;
};


#endif

Widget.cpp:

#include "Widget.h"
#include "DerivedModel.h"
#include "Scene.h"

#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGraphicsView>
#include <QDebug>
#include <QItemSelection>

Widget::Widget(QWidget* parent) : QWidget(parent)
                                  ,m_mainVBLayout(nullptr)
                                  ,m_HBButtonsLayout(nullptr)
                                  ,m_graphicsView(nullptr)
                                  ,m_scene(nullptr)
                                  ,m_button1(nullptr)
                                  ,m_button2(nullptr)
                                  ,m_model(nullptr)
{
    //init the DerivedModel
    m_model = new DerivedModel(this);

    //add two items to the model
    m_model->addItem("A");
    m_model->addItem("B");

    //create the main layout
    m_mainVBLayout = new QVBoxLayout(this);

    //create the buttons layout
    m_HBButtonsLayout = new QHBoxLayout;

    //add it to the main layout
    m_mainVBLayout->addLayout(m_HBButtonsLayout);

    //create the buttons
    m_button1 = new QPushButton("A", this);
    m_button2 = new QPushButton("B", this);

    //set them to be checkable
    m_button1->setCheckable(true);
    m_button2->setCheckable(true);

    //connect their signals
    connect(m_button1, &QPushButton::toggled, this, &Widget::toggle);
    connect(m_button2, &QPushButton::toggled, this, &Widget::toggle);

    //add them to the layout
    m_HBButtonsLayout->addWidget(m_button1);
    m_HBButtonsLayout->addWidget(m_button2);

    //create the graphics view
    m_graphicsView = new QGraphicsView(this);

    //create the scene
    m_scene = new Scene(this);
    m_scene->setSceneRect(QRect(0,0,50,25));

    //set its scene
    m_graphicsView->setScene(m_scene);

    //add the graphics view to the layout
    m_mainVBLayout->addWidget(m_graphicsView);
}   

void Widget::toggle(bool state){
    //get the sender
    QPushButton* button(nullptr);
    if(sender()==m_button1) button = m_button1;
    else button = m_button2;

    //get the name of the item related to the button to change
    QString name = button->text();

    //get the index based on the name
    //im using the instance of the DerivedModel because this is how I implement it in my project;
    QModelIndex itemIndex = DerivedModel::instance().indexFromName(name);

    //check if index is valid
    if(!itemIndex.isValid()){
        qDebug() << "Index for item "+name+" not valid!";
        return;
    }
    else 
        qDebug() << "Found Item :)";

    //create a QItemSelection as how it is in my project
    QItemSelection selection;

    //add the index to the selection
    selection.select(itemIndex, itemIndex);

    //check the state
    if(state){
        //add to the selection
        DerivedModel::instance().selectionModel()->select(selection, QItemSelectionModel::Select);
    }
    else{
        //remove from selection
        DerivedModel::instance().selectionModel()->select(selection, QItemSelectionModel::Deselect);
    }
}

Upvotes: 2

Views: 3439

Answers (2)

Absoluum
Absoluum

Reputation: 11

My issue was that I was not calling the flags function of the inherited class.

def flags(self, index):
        super_flags = super().flags(index)
        flags = Qt.ItemIsEnabled
        node = index.internalPointer()
        if index.column() == 0:
            flags |= Qt.ItemIsUserCheckable
            if node.parent == self._rootNode:
                flags |= Qt.ItemIsAutoTristate

        
        return flags|super_flags

Upvotes: 1

Bazilikum
Bazilikum

Reputation: 67

Ok, so I found the culprit :)

In case anybody else has the same problem, my error was in the ::flags() function of my derived QAbstractItemModel class: Inside the definition I didnt call the base class QAbstractItemModel::flags(index) function, once I called that instead of returning the flags myself all went well.

So I think that as long as your item has a Qt::flags flags() function that the model can call, you dont have to reimplement the QAbstractItemModel::flags() function.

It seems that the model is quering the flags through the QModelIndex::flags() function anyway.

Thanks you "Ezee" and "Kuba Ober" for willing to help.

Upvotes: 1

Related Questions