Extending QFileSystemModel by a bool variable - qt

I would like to extend the QFileSystemModel. I would like to have a custom role IsSelectedRole where I can store whether a bool value which stores whether a file got selected in a QML TreeView. More precisely, I don't know how to set the setData and data function of my QFileSystemModel derived class, like where to store the data. I guess making my own tree of bool variables should work, but I hope that there is an easier way.

Qt already has a model that stores item selections: QItemSelectionModel. This model is used by the widget views as well as the QML views. All that's left for you to do is to overlay the data from that model on top of the data from QFileSystemModel, in a viewmodel.
You definitely shouldn't be deriving from QFileSystemModel. The viewmodel maintains the state for a single view only, and should be a proxy that overlays your role on top of the underlying model.
The proxy doesn't have to assume anything about the underlying model, and can work on any model, not just the QFileSystemModel.
For example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/filesystem-model-select-50132273
#include <QtWidgets>
#include <algorithm>
class SelectionProxy : public QIdentityProxyModel {
Q_OBJECT
Q_PROPERTY(QItemSelectionModel* selectionModel
READ selectionModel WRITE setSelectionModel NOTIFY selectionModelChanged)
Q_PROPERTY(QVector<int> roles READ roles WRITE setRoles)
using self_t = SelectionProxy;
using base_t = QIdentityProxyModel;
using model_t = QItemSelectionModel;
QPointer<QItemSelectionModel> m_model;
QVector<QMetaObject::Connection> m_modelConnections;
QVector<int> m_roles{IsSelectedRole};
QModelIndex topLeft() const {
return sourceModel()->index(0, 0);
}
QModelIndex bottomRight() const {
return sourceModel()->index(sourceModel()->rowCount()-1, sourceModel()->columnCount()-1);
}
void onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
auto sel = selected;
sel.merge(deselected, QItemSelectionModel::Select);
for (auto &range : qAsConst(sel)) {
auto topLeft = mapFromSource(range.topLeft());
auto bottomRight = mapFromSource(range.bottomRight());
emit dataChanged(topLeft, bottomRight, m_roles);
}
}
void onModelChanged(QAbstractItemModel *model) {
setSourceModel(model);
}
bool check(const QModelIndex &index, int role) const {
return index.isValid() && m_model && m_roles.contains(role);
}
public:
static constexpr int IsSelectedRole = Qt::UserRole + 44;
SelectionProxy(QObject *parent = {}) : QIdentityProxyModel(parent) {}
QItemSelectionModel *selectionModel() const { return m_model; }
virtual void setSelectionModel(QItemSelectionModel *model) {
if (model == m_model) return;
for (auto &conn : m_modelConnections)
disconnect(conn);
m_model = model;
m_modelConnections = {
connect(m_model, &model_t::selectionChanged, this, &self_t::onSelectionChanged),
connect(m_model, &model_t::modelChanged, this, &self_t::onModelChanged) };
setSourceModel(model->model());
emit selectionModelChanged(m_model);
}
Q_SIGNAL void selectionModelChanged(QItemSelectionModel *);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override {
if (!check(index, role))
return base_t::data(index, role);
return m_model->isSelected(mapToSource(index));
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override {
if (!check(index, role))
return base_t::setData(index, value, role);
using Sel = QItemSelectionModel;
m_model->select(mapToSource(index), value.toBool() ? Sel::SelectCurrent : (Sel::Deselect | Sel::Current));
return true;
}
QVector<int> roles() const { return m_roles; }
void setRoles(QVector<int> roles) {
std::sort(roles.begin(), roles.end());
if (roles == m_roles)
return;
std::swap(roles, m_roles);
if (!m_model)
return;
QVector<int> allRoles;
std::merge(roles.begin(), roles.end(), m_roles.begin(), m_roles.end(), std::back_inserter(allRoles));
emit dataChanged(topLeft(), bottomRight(), allRoles);
}
void setRole(int role) {
setRoles({role});
}
};
#include "main.moc"
A simple demonstrator sets up two views of the filesystem model; the bottom view shows true/false indicating the selection status of the first view.
int main(int argc, char **argv) {
QApplication app{argc, argv};
QWidget win;
QVBoxLayout layout{&win};
QTreeView left, right;
layout.addWidget(&left);
layout.addWidget(&right);
QFileSystemModel model;
SelectionProxy selProxy;
model.setRootPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
auto rootPathIndex = model.index(model.rootPath());
left.setModel(&model);
left.setSelectionMode(QAbstractItemView::MultiSelection);
left.setRootIndex(rootPathIndex);
selProxy.setRole(Qt::DisplayRole);
selProxy.setSelectionModel(left.selectionModel());
right.setModel(&selProxy);
right.setRootIndex(selProxy.mapFromSource(rootPathIndex));
for (int col : {1,2,3})
right.hideColumn(col);
win.show();
return app.exec();
}

Related

Using QT Tree Model with Custom data

This is my Header file for Container object.
class Container
{
private:
std::string stdstrContainerName;
std::string stdstrPluginType;
int iSegments;
float fRadius;
public:
Container();
Container(std::string , std::string , int , float);
Container(const Container& obj);
~Container();
std::string GetName();
std::string GetType();
void SetName(std::string stdstrName);
};
Q_DECLARE_METATYPE( Container )
I use Container as the datatype in TreeItem.
class TreeItem
{
public:
explicit TreeItem( const Container &data , TreeItem *parent = 0 );
~TreeItem();
TreeItem *parent();
TreeItem *child(int iNumber);
int childCount() const;
int childNumber() const;
Container data() const;
bool setData(const Container &data);
bool insertChildren(int position, int count );
bool removeChildren( int position , int count );
private:
QList<TreeItem*> childItems;
Container itemData;
TreeItem* parentItem;
};
The problem is that when i add rows to the Tree Structure the rows gets added but with no text on them.
I want to set the data in stdstrContainerName as the data on the row.
if i try to write something it does not stay.
i think the problem is with my functions which set the data to TreeItem object
Container TreeItem::data() const
{
return itemData;
}
bool TreeItem::setData(const Container &data , QVariant value )
{
itemData = data;
itemData.SetName(value.toString().toStdString() );
return true;
}
/
Data Function for Tree Model
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
TreeItem *item = getItem(index);
return QString::fromStdString(item->data().GetName());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &val, int
role )
{
if (role != Qt::EditRole)
return false;
Container c = val.value<Container>();
TreeItem *item = getItem(index);
bool result = item->setData(c);
if (result)
emit dataChanged(index, index, { role });
return result;
}
I can also post any additional code of the project.
What's missing here is the code of your view.
A QAbstractItemView derived view cannot display Container QVariants by itself.
Either you implement your view in such a way that it
interprets Container-QVariants and displays e.g. its GetName()
provides an editor that can manipulate Container-QVariants
or (simpler method) you shift the concretion layer into your model and
return item->GetName(), ->GetType() depending on role and index.column()
manipulate the Container instance in the TreeItem from the text passed in QVariant val

QT table/model doesn't call data()

I'm trying to create a widget representing a table and update some data.
In order to do this I am following the Qt Model/View tutorial.
I've created classes (that you can find at the end of the post)
EmittersTableModel that inherits from QAbstractTableModel
EmittersTableWidget that inherits from QTableView
I havethe EmittersTableModel object as private member of EmittersTableWidget. In its constructor I instantiate the model and use the setModel() method.
Then, when I try to update data, I call the EmittersTableWidget::setRadioData() method, and I emit the datachanged() signal.
I've verified with the debugger that:
emit dataChanged(topLeft, bottomRight) is called
EmittersTableModel::rowCount() is called
EmittersTableModel::columnCount() is called
EmittersTableModel::flags() is never called
EmittersTableModel::data() is never called.
It seems for me that I'm doing all that tutorial says (use setModel(), implement needed virtual functions, emit the signal).
What I'm missing?
EmittersTableModel.h
#include <QAbstractTableModel>
#include <QVector>
#include "Radio.h"
typedef QMultiMap<QString, MapScenario::Core::RadioPtr> PlayerRadioMap;
class EmittersTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
EmittersTableModel(QObject *parent);
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const ;
virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags ( const QModelIndex & index ) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
void setRadioData(const PlayerRadioMap &xRadioMap);
private:
typedef struct
{
QString xName;
MapScenario::Core::RadioPtr pxRadio;
} TableRowData;
PlayerRadioMap m_xRadioMap;
QVector<TableRowData> m_xDataVector;
};
EmittersTableModel.cpp
#include "EmittersTableModel.h"
EmittersTableModel::EmittersTableModel(QObject *parent)
:QAbstractTableModel(parent)
{
}
int EmittersTableModel::rowCount(const QModelIndex & /*parent*/) const
{
return m_xDataVector.size() - 1;
}
int EmittersTableModel::columnCount(const QModelIndex & /*parent*/) const
{
return 8;
}
QVariant EmittersTableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
{
switch (index.column())
{
case 0 :
{
return m_xDataVector.at(index.row()).xName;
} break;
case 1 :
{
return m_xDataVector.at(index.row()).pxRadio->getName();
} break;
}
return QString("Row%1, Column%2")
.arg(index.row() + 1)
.arg(index.column() +1);
}
return QVariant();
}
Qt::ItemFlags EmittersTableModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}
QVariant EmittersTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole)
{
if (orientation == Qt::Horizontal) {
switch (section)
{
case 0:
return QString("Player");
case 1:
return QString("Emitter");
case 2:
return QString("Freq.");
case 3:
return QString("Power");
case 4:
return QString("Modulation");
case 5:
return QString("Freq. Hopp.");
case 6:
return QString("Silent");
case 7:
return QString("Rec. Power");
}
}
}
return QVariant();
}
void EmittersTableModel::setRadioData(const PlayerRadioMap &xRadioMap)
{
m_xDataVector.clear();
PlayerRadioMap::const_iterator xIt;
for (xIt = xRadioMap.begin(); xIt != xRadioMap.end(); ++xIt)
{
TableRowData xData;
xData.xName = xIt.key();
xData.pxRadio = xIt.value();
m_xDataVector.append(xData);
}
if (false == m_xDataVector.empty())
{
QModelIndex topLeft = createIndex(0, 0);
QModelIndex bottomRight = createIndex(m_xDataVector.size() - 1, 7);
emit dataChanged(topLeft, bottomRight);
}
}
EmittersTableWidget.h
#include <QTableView>
#include <QHeaderView>
#include <QMultiMap>
#include <boost/smart_ptr.hpp>
#include "EmittersTableModel.h"
#include "Scenario.h"
#include "Radio.h"
namespace MapScenario
{
namespace Core
{
class Player;
}
}
/** Class for the player properties table model window */
class EmittersTableWidget : public QTableView
{
Q_OBJECT
public:
EmittersTableWidget(QWidget *xParent = 0);
~EmittersTableWidget();
public slots:
void refreshScenarioDataSlot(const MapScenario::Core::ScenarioPtr pxScenario);
private:
EmittersTableModel *m_pxModel;
void getTransmitterMap(const MapScenario::Core::ScenarioPtr pxScenario, PlayerRadioMap *pxRadioMap) const;
void sendDataToTableModel(const PlayerRadioMap &xRadioMap);
};
EmittersTableWidget.cpp
#include "EmittersTableWidget.h"
#include "Player.h"
#include "CoreException.h"
using MapScenario::Core::ScenarioPtr;
using MapScenario::Core::Radio;
using MapScenario::Core::PlayerPtr;
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SECTION //
///////////////////////////////////////////////////////////////////////////////
EmittersTableWidget::EmittersTableWidget(QWidget *xParent)
: QTableView(xParent)
{
m_pxModel = new EmittersTableModel(0);
setModel(m_pxModel);
horizontalHeader()->setVisible(true);
verticalHeader()->setVisible(false);
setShowGrid(true);
setGridStyle(Qt::NoPen);
setCornerButtonEnabled(false);
setWordWrap(true);
setAlternatingRowColors(true);
setSelectionMode(QAbstractItemView::SingleSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
setSortingEnabled(true);
}
EmittersTableWidget::~EmittersTableWidget()
{
delete m_pxModel;
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC SLOTS SECTION //
///////////////////////////////////////////////////////////////////////////////
void EmittersTableWidget::refreshScenarioDataSlot(const ScenarioPtr pxScenario)
{
PlayerRadioMap xRadioMap;
getTransmitterMap(pxScenario, &xRadioMap);
sendDataToTableModel(xRadioMap);
}
void EmittersTableWidget::getTransmitterMap(const ScenarioPtr pxScenario, PlayerRadioMap *pxRadioMap) const
{
QVector<QString> xNameList;
QVector<QString>::const_iterator xNameIt;
QStringList::const_iterator xRadioIt;
pxScenario->getPlayersNameList(xNameList);
for (xNameIt = xNameList.begin(); xNameIt != xNameList.end(); ++xNameIt)
{
QStringList xRadioList;
PlayerPtr pxPlayer = pxScenario->getPlayer(*xNameIt);
pxPlayer->getRadioNameList(xRadioList);
for (xRadioIt = xRadioList.begin(); xRadioIt != xRadioList.end(); ++xRadioIt)
{
pxRadioMap->insert(pxPlayer->getName(), pxPlayer->getRadio(*xRadioIt));
}
}
}
void EmittersTableWidget::sendDataToTableModel(const PlayerRadioMap &xRadioMap)
{
m_pxModel->setRadioData(xRadioMap);
}
I've found my problem.
In the tutorial that I've seen it was supposed that row and column numbers are Always constant. Instead I start with zero rows, and then I add or remove them when I need. In order to do this, I need to re-implement following methods:
virtual bool insertRows(int row, int count, const QModelIndex &parent)
virtual bool insertColumns(int column, int count, const QModelIndex &parent)
virtual bool removeRows(int row, int count, const QModelIndex &parent)
virtual bool removeColumns(int column, int count, const QModelIndex &parent)
as explained in QAbstractItemModel page in subclassing section. Now I insert and remove rows when needed and table is updated correctly.

How to access Items stored in a QAbstractListmodel in QML(by delegates) otherwise than using item roles?

I just want to display elements from list using QML, but not using item roles.
For ex. I want to call a method getName() that returns the name of the item to be displayed.
Is it possible? I didn't find nothing clear reffering to this.
You can use one special role to return the whole item as you can see below:
template<typename T>
class List : public QAbstractListModel
{
public:
explicit List(const QString &itemRoleName, QObject *parent = 0)
: QAbstractListModel(parent)
{
QHash<int, QByteArray> roles;
roles[Qt::UserRole] = QByteArray(itemRoleName.toAscii());
setRoleNames(roles);
}
void insert(int where, T *item) {
Q_ASSERT(item);
if (!item) return;
// This is very important to prevent items to be garbage collected in JS!!!
QDeclarativeEngine::setObjectOwnership(item, QDeclarativeEngine::CppOwnership);
item->setParent(this);
beginInsertRows(QModelIndex(), where, where);
items_.insert(where, item);
endInsertRows();
}
public: // QAbstractItemModel
int rowCount(const QModelIndex &parent = QModelIndex()) const {
Q_UNUSED(parent);
return items_.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (index.row() < 0 || index.row() >= items_.count()) {
return QVariant();
}
if (Qt::UserRole == role) {
QObject *item = items_[index.row()];
return QVariant::fromValue(item);
}
return QVariant();
}
protected:
QList<T*> items_;
};
Do not forget to use QDeclarativeEngine::setObjectOwnership in all your insert methods. Otherwise all your objects returned from data method will be garbage collected on QML side.

QTableView doesn't seem to react on dataChanged signal

Shown below is the my custom table model. I am trying to use that tablemodel together with a QTableView. If the method append of the table model is called I would expect the table view to update its contents. But it doesn't and I don't know why. If however I use that same table model together with a QListView, everything works fine, i.e. the list view does update its contents, when append of the table model gets called. Is there anything special I need to do in case of the QTableView?
class MyModel : public QAbstractTableModel
{
public:
MyModel(QObject* parent=NULL) : QAbstractTableModel(parent) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const {
return mData.size();
}
int columnCount(const QModelIndex &parent = QModelIndex()) const {
return 2;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const {
if (!index.isValid()) {
return QVariant();
}
if (role == Qt::DisplayRole) {
if (index.column()==0) {
return QVariant(QString::fromStdString(getFirst(index.row())));
}
if (index.column()==1) {
return QVariant(QString::fromStdString(getSecond(index.row())));
}
}
return QVariant();
}
void append(std::string const& first, std::string const& second) {
mData.push_back(std::make_pair(first, second));
emit dataChanged(index(mData.size()-1, 0), index(mData.size()-1, 1));
}
std::string const& getFirst(int i) const {
return mData[i].first;
}
std::string const& getSecond(int i) const {
return mData[i].second;
}
protected:
std::vector<std::pair<std::string, std::string> > mData;
};
As you're inserting a new row instead of changing existing data, you should use beginInsertRows and endInsertRows instead:
void append(std::string const& first, std::string const& second) {
int row = mData.size();
beginInsertRows( QModelIndex(), row, row );
mData.push_back(std::make_pair(first, second));
endInsertRows();
}
See if that helps.

View doesn't display data from a model

could someone please tell me, why this code for the freaking hell, doesn't display data in a view?
#include <QApplication>
#include <QtGui>
class File_Model : public QAbstractItemModel
{
private:
QStringList data_;
public:
File_Model()
{}
QVariant data(const QModelIndex &index, int role) const
{
return data_.at(index.row());
}
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole)
{
switch(role)
{
case Qt::DisplayRole:
data_ = value.toStringList();
emit dataChanged(index,index);
return true;
}
return false;
}
virtual QModelIndex index(int row, int column, const QModelIndex&) const
{
return createIndex(row,column);
}
virtual QModelIndex parent(const QModelIndex&) const
{
return QModelIndex();
}
virtual int rowCount(const QModelIndex&) const
{
return data_.size();
}
virtual int columnCount(const QModelIndex&) const
{
return 1;
}
};
int main(int argc,char** argv)
{
QApplication app(argc,argv);
QDir dir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
File_Model* model = new File_Model;//(dir.entryList());
bool t = model->setData(QModelIndex(),dir.entryList());
QListView* view = new QListView;
view->setModel(model);
view->show();
return app.exec();
}
The problem is on your data function. You should check the role before displaying something:
QVariant data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
return QVariant(data_.at(index.row()));
return QVariant();
}
Also note that you don't have to use the setData in your case. setData is udes for editing models, not initializing them with some values.
To enable editing in your model, you must also implement setData(),
and reimplement flags() to ensure that ItemIsEditable is returned.
Instead you could add a public function in your model and call it instead:
void setEntries(QStringList entries)
{
beginInsertRows(createIndex(0,0), 0, entries.count());
data_ = entries;
endInsertRows();
}

Resources