Applying two FileFilterProxyModel on one QFileSystemModel - c++

The problem:
I am making a sort of file explorer, that has one view of the directories, and another view shows the files in the selected directory.
So the idea was to have one QFileSystemModel, and apply two FileFilterProxyModel's on it, one to show only folders, one to show only files (for selected folder, no sub folders)
The folders view works fine.
The files view works fine, for the first (root) folder, when the UI shows up.
After that, any selection on the folder view results in invalid source index (getting the index from the folder view and transforming it back to the source model index works fine, but when I try to translate the index from the source to the file filter model it always returns an invalid index, except for the first time , which means the file view always shows only the root folder content.)
Here is my code:
The models initialization:
void MainWindow::initFilesystemModel()
{
m_pFileSystemModel = new QFileSystemModel(this);
m_pFileSystemModel->setFilter(QDir::AllDirs|QDir::NoDotAndDotDot|QDir::Hidden|QDir::Drives|QDir::Files);
m_pFileSystemModel->setRootPath(QDir::rootPath());
m_pOnlyFoldersFilterModel = new FileFilterProxyModel(ui->pFolderTreeView);
m_pOnlyFoldersFilterModel->setSourceModel(m_pFileSystemModel);
QSortFilterProxyModel* onlyFilesModel = new OnlyFilesFileterModel(ui->pDetailViewWidget);
onlyFilesModel->setSourceModel(m_pFileSystemModel);
ui->pFolderTreeView->setModel(m_pOnlyFoldersFilterModel);
ui->pDetailViewWidget->setModel(onlyFilesModel);
connect(ui->pFolderTreeView, &QTreeView::pressed, this , &MainWindow::onFolderSelected);
}
And the reaction to selecting a folder:
Note the comments in the code:
void MainWindow::onFolderSelected(const QModelIndex &index)
{
QModelIndex sourceIndex = m_pOnlyFoldersFilterModel->mapToSource(index); //sourceIndex is valid and correct
QModelIndex rootIndex = dynamic_cast<QSortFilterProxyModel*>(ui->pDetailViewWidget->model())->mapFromSource(sourceIndex); //rootIndex is always invalid -1,-1
ui->pDetailViewWidget->setRootIndex(rootIndex);
}
I have found online few examples that did a very similar thing but they solved the issue by using two QFileSystemModel's, one for each view.
To me this counters the idea of Qt Mode/View Framework since it should be possible to show the same data in differently filtered in multiple views.
I must be missing something, or overlooking something, would very much appreciate any pointers, and things to try.
Thanks!

Presumably OnlyFilesFileterModel filters out everything that isn't a file, which will exclude subdirectories of your root directory.
mapFromSource will only return a valid index for a file.
You could add an intermediate model that presents only the direct children of a particular model's element, then you would set it's source root in onFolderSelected
class TreeToTableProxyModel : public QAbstractProxyModel
{
QModelIndex sourceRoot;
public:
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override
{
if (sourceIndex.parent() != sourceRoot) return QModelIndex();
return index(sourceIndex.row(), sourceIndex.column());
}
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override
{
if (!proxyIndex.isValid()) return QModelIndex();
return sourceModel().index(proxyIndex.row(), proxyIndex.column(), sourceRoot);
}
void setSourceRoot(const QModelIndex &sourceIndex)
{
beginResetModel();
sourceRoot = sourceIndex;
endResetModel();
}
};

Related

In QT C++ while am editing Qtable model data i need to get a small box and in that i need to show the editing text how is it possible?

tmodel = new QSqlTableModel(this);
tmodel->setTable("trainee_info");
tmodel->select();
by this we will get a table model as output and it can be editable but while editing i need to show a label below the field in which am trying to edit and text should populate in it . so how can i do that.?
If you are using a QStandardItemModel you have to set the item editable before adding to the model, like the code below:
item->setFlags(item->flags() | Qt::ItemIsEditable);
Then, when you will double click on the item it would open a line edit by default for editing.
For finer controls you can also install a delegate on your table and override the createEditor function.
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem & /*inOption*/, const QModelIndex & inIndex ) const override
{
auto editor = new QLineEdit( parent );
return editor;
}
this will give you a line edit to edit your model.
But your case is pretty simple and the first approach will work for you!

How to clear a QTreeView / QFileSystemModel

I have a list of items displayed in a QTableWidget, each one corresponding to a specific folder.
Next to this, I have a QTreeView, with a QFileSystemModel. When I select an item from the QTableWidget, it calls a slot (shown below) in order to display the corresponding folder content.
void MyWidget::diplayFolder(int row)
{
if (auto item = table->item(row, 1))
{
QString correspondingDirectory = item->text();
if (QDir(correspondingDirectory).exists())
{
// treeModel => QFileSystemModel
// tree => QTreeView
treeModel->setRootPath("");
treeModel->setRootPath(correspondingDirectory);
tree->setRootIndex(treeModel->index(correspondingDirectory));
}
else
{
qDebug() << "Reset tree => do not display anything!";
// treeModel->setRootPath("");
// tree->reset();
}
}
}
If the directory does not exist, I don't want to display anything. However, when I try to set an empty root path or reset the view, it show all my computer drives.
How can I reset or clear the QTreeView ?
Had a similar issue. Im not quite sure how i solved it, because it was a long time ago. I think it should work if you set the QTreeview a nullptr as model. So when the QDir exists you set a new QFileSystemModel otherwise you call:
tree->setModel(nullptr);
Hope this helps you.
EDIT:
If your doing it this way the header is deleted too.

How can I organize QStandardItemModel`s in one QTreeView

I have two QTreeView's.
First (QTreeView1) displayed folders, second (QTreeView2) - displayed subfolders, that are loaded on folder click in first QTreeView1.
On click by folder in QTreeView1, I create QStandardItemModel with subfolders and set this model to QTreeView2. Also all items in both of QTreeView`s is checkable and I want to save all checked items state.
How can I organize models storage for each loaded folders.
Is it should be something like this:
// store folder model on subfolders check state changed
QMap<QStandardItemModel*, QString> modelStorage;
modelStorage.push_back(folderModel, folderPath);
and restore folder on folder click with:
QStandardItemModel* findFolderModel(QString folderPath)
{
QStandardItemModel* model;
foreach(auto path, modelStorage)
{
if (path == folderPath)
{
model = modelStorage.find(folderPath);
}
else model = nullptr;
}
return model;
}
and show model then ... Is it correct way to store all folder models? or it must be dynamically loaded? But in that case I need to store all model data by myself (for example, checked items state ...). Also model`s data can be changed for a while and I cant show "correct" data if I restore model from "snapshots".
UPD also I have question about implementation of my suggestion:
I store/restore models on click by folders in QTreeView1 and it seems to be worked ... but restored models doesn't contains/displays QStandardItems. It happens because treeItem allocated with new operator in local scope? How can I save all QStandardItems in each model in that case?
model = new QStandardItemModel;
QStandardItem* treeItem = new QStandardItem("item");
model->appendRow(treeItem);
//..
modelStorage.insert(folderItem, model);
ui.treeView->setModel(model);
// after restore model pointer is valid, but hadn't contains any items.
I think, you can try to use two QFileSystemModels for your both QTreeViews and don't create QStandardItemModel every time you click item in first QTreeView. As you can see in documentation, QFileSystemModel have setRootPath method. You can use this method for second model every time you click on folder in first QTreeView.
To make your items checkable, browse this helpful articles:
http://www.qtcentre.org/threads/27253-QFileSystemModel-with-checkboxes
QFilesystemmodel with Checkboxes
http://doc.qt.io/qt-5/qidentityproxymodel.html

QT tree that allows multiselection

I'm making a simple file explorer and I ran into some problems with Qt. I want to show the user a tree view of files on his computer, but I also want to be able to select multiple files/directories and do something with them later on (by selecting checkboxes or multiple select using ctrl+left click or shift+left click). I've placed the QTreeView element and set up a model to it (QFileSystemModel). It gives me a good tree view, but I can't modify the headers (column names) or add my own column with checkbox in every row (for example). Qt is new to me, I've searched for few good hours for some tips/solutions, but nothing is working with QFileSystemModel. Is there anything I can do to get this working?
The code is short and simple:
QString lPath = "C:/";
QString rPath = "C:/";
leftTree_model = new QFileSystemModel(this);
rightTree_model = new QFileSystemModel(this);
leftTree_model->setRootPath(lPath);
rightTree_model->setRootPath(rPath);
//i have actually 2 tree views that work the same
ui->leftTree->setModel(leftTree_model); //ui->leftTree is the first tree view
ui->rightTree->setModel(rightTree_model); //the second
Use something of the following:
CheckStateRole to add checkboxes to your model. To do this, you inherit your custom item model (which you're going to use) from the QFileSystemModel, and reimplement the data() method, where you return bool values for CheckStateRole. You will also need the QAbstractItemModel::setData method to handle changes. You can also check the docs for QAbstractItemModel to see how to change header texts (headerData())
Change the selection mode of your view to allow multiple selections
EDIT:
here's a sample code to inherit from the model
class MyFancyModel : public QFileSystemModel
{
public:
MyFancyModel(QObject* pParent = NULL) : QFileSystemModel(pParent)
{
}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole ) const
{
if (role == Qt::CheckStateRole)
{
// stub value is true
return true; // here you will return real values
// depending on which item is currently checked
}
return QFileSystemModel::data(index, role);
}
};

QTreeWidget editItem fails with "edit: editing failed"

I have a QTreeWidgetItem added to a QTreeWidget:
QTreeWidgetItem* item = new QTreeWidgetItem(ui->trwPairs);
item->setFlags(item->flags() | Qt::ItemIsEditable);
If the item is edited, I want to do a few checks on the new value:
Pairs::Pairs(QWidget *parent) :
QWidget(parent),
ui(new Ui::Pairs)
{
ui->setupUi(this);
connect(this->ui->trwPairs, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(Validate(QTreeWidgetItem*,int)));
}
void Pairs::Validate(QTreeWidgetItem* item, int column)
{
if (item->text(column).toInt() < 1)
{
QMessageBox::critical(this, "Error", QString("Node ID ") + item->text(column) + " is invalid.");
ui->trwPairs->editItem(item, column);
}
}
Naturally, if it's less than 1, it catches it, and gives me the message box. However, printed to cerr is edit: editing failed and the item is not in edit mode. What am I missing?
Stepping through it in the debugger reveals the following:
In quabstractitemview.cpp line false is returned on line 3953. Somehow it looks like your item is still in editing state and you are trying to edit it again or something.
bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger,
const QModelIndex &index) const
{
// ..
if (state == QAbstractItemView::EditingState)
return false;
}
IIRC I had a similar problem with tables with multiple lines per cell. Check out the classes QAbstractItemDelegate views have item delegates which allow you to control which editor is used and how it behaves. I believe by default the QLineEdit is used. Editors like QLineEdit can have validators which control how the data is validated, in your case reject it if the numerical value is < 0. But I think you have to use the model / view classes and implement your own model for that. The Qt documentation for QTreeWidget::setItemWidget(..) says:
This function should only be used to display static content in the place of a tree widget item. If you want to display custom dynamic content or implement a custom editor widget, use QTreeView and subclass QItemDelegate instead.
I am not sure however if there is a simpler way to do this using the widget classes.
The problem could be, that you are setting the flags for your items in a very strange way.
Simply enable both item-selection, and edit:
item->setFlags(Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
I had a similar issue where I was attempting to edit the subsequent column upon receiving the itemChanged signal. Based on Nils' analysis that the item was still in the edit state, I changed the signal connection type to QueuedConnection, which allowed the item to leave the state before re-entering it.
I had a similar problem where I'd get the 'edit: editing failed' error when invoking edit() via a shortcut key. I was passing currentIndex() to edit(), but I wasn't checking that the correct column of the selected row was current. I only had the first column editable, so if I had clicked the row (but in any other column) and then invoked my edit key I'd get the error.
I was able to solve my problem by passing the result of sibling(currentIndex().row(), 0) to edit() instead.