Hide items of a QFileSystemModel/QTreeView via indexes using a specific rule - c++

I'm displaying the contents of a folder in my Qt program using a QTreeView + QFileSystemModel.
Now I want to hide specific items of that view. The display rule is not based on the file names, so I can't use setNameFilters(). What I have is a simple list of QModelIndex containing all the items I want to hide. Is there a way of filtering the view using only this list?
In my research I came across the QSortFilterProxyModel class, but I couldn't figure how to use it in order to achieve what I want. Any help would be appreciated.

Subclass QSortFilterProxyModel and override the method filterAcceptsRow to set the filter logic.
For example, to filter on current user write permissions :
class PermissionsFilterProxy: public QSortFilterProxyModel
{
public:
PermissionsFilterProxy(QObject* parent=nullptr): QSortFilterProxyModel(parent)
{}
bool filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
QFileDevice::Permissions permissions = static_cast<QFileDevice::Permissions>(index.data(QFileSystemModel::FilePermissions).toInt());
return permissions.testFlag(QFileDevice::WriteUser); // Ok if user can write
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QFileSystemModel* model = new QFileSystemModel();
model->setRootPath(".");
QTreeView* view = new QTreeView();
PermissionsFilterProxy* proxy = new PermissionsFilterProxy();
proxy->setSourceModel(model);
view->setModel(proxy);
view->show();
return app.exec();
}

Related

How to connect two QComboBox so that the second shows only certain items?

I have two QComboBoxes to connect with each other.
In particular, I'd like that if a scaling is made in the first QComboBox, this is not also shown in the second QComboBox and vice versa..
This is my code:
auto lingua_originaleT = new QComboBox();
lingua_originaleT->addItems({"Italiano", "Inglese", "Francese", "Spagnolo", "Portoghese", "Tedesco", "Cinese"});
auto lingua_targetT = new QComboBox();
lingua_targetT->addItems({"Italiano", "Inglese", "Francese", "Spagnolo", "Portoghese", "Tedesco", "Cinese"});
The result should be like this:
The same language should not appear in the second drop-down menu as well
One possible solution is to use QSortFilterProxyModel to do the filtering:
#include <QApplication>
#include <QComboBox>
#include <QHBoxLayout>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QWidget>
class FilterProxyModel: public QSortFilterProxyModel{
public:
using QSortFilterProxyModel::QSortFilterProxyModel;
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const{
if(filterRegExp().isEmpty())
return true;
return !QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget widget;
widget.resize(640, 480);
widget.show();
QStandardItemModel *model = new QStandardItemModel(&widget);
const QStringList values{"Italiano", "Inglese", "Francese", "Spagnolo", "Portoghese", "Tedesco", "Cinese"};
for(const QString & value: values){
model->appendRow(new QStandardItem(value));
}
QComboBox *lingua_originaleT = new QComboBox;
FilterProxyModel *proxy_originalT = new FilterProxyModel(&widget);
proxy_originalT->setSourceModel(model);
lingua_originaleT->setModel(proxy_originalT);
QComboBox *lingua_targetT = new QComboBox;
FilterProxyModel *proxy_targetT = new FilterProxyModel(&widget);
proxy_targetT->setSourceModel(model);
lingua_targetT->setModel(proxy_targetT);
QHBoxLayout *lay = new QHBoxLayout(&widget);
lay->addWidget(lingua_originaleT);
lay->addWidget(lingua_targetT);
QObject::connect(lingua_originaleT, &QComboBox::currentTextChanged, proxy_targetT, &FilterProxyModel::setFilterFixedString);
QObject::connect(lingua_targetT, &QComboBox::currentTextChanged, proxy_originalT, &FilterProxyModel::setFilterFixedString);
lingua_originaleT->setCurrentIndex(0);
lingua_targetT->setCurrentIndex(1);
return a.exec();
}
Internally, QCombobox uses QStandardItemModel - unless you provided a custom one using setModel().
That means you can do things like:
// Just some setup
auto combo = new QComboBox(this);
combo->addItems({ "Item0", "Item1", "Item2" });
// Here is the interesting bit
auto model = qobject_cast<QStandardItemModel*>(combo->model());
auto item = model->item(0); // <-- index in the combobox
item->setEnabled(false); // <-- You can't select it anymore
if(combo->currentIndex() == 0) // Choose another one if it's already selected
combo->setCurrentIndex(1);
// From now, Item 0 will be visible in the dropdown but not selectable by the user.
I leave you find a way to get the 2 boxes disabling each other items (And importantly, enabling them back once if the selection change). This is a matter of listening the index changed signals of each boxes and and updating the other model accordingly.
I solved and the solution works perfectly!
Here's how I did it:
lingua_originaleT = new QComboBox();
lingua_targetT = new QComboBox();
QStringList traduzioneLangList = {"Italiano", "Inglese", "Francese", "Spagnolo", "Portoghese", "Tedesco", "Cinese"};
lingua_originaleT->clear();
lingua_originaleT->addItems(traduzioneLangList);
lingua_originaleT->setCurrentIndex(-1);
lingua_targetT->clear();
lingua_targetT->addItems(traduzioneLangList);
lingua_targetT->setCurrentIndex(-1);
connect(lingua_originaleT, &QComboBox::currentTextChanged, lingua_targetT, [=](const QString &selection) {
auto tarLangList = traduzioneLangList;
lingua_targetT->blockSignals(true);
tarLangList.removeOne(selection);
lingua_targetT->clear();
lingua_targetT->addItems(traduzioneLangList);
lingua_targetT->blockSignals(false);
});
connect(lingua_targetT, &QComboBox::currentTextChanged, lingua_originaleT, [=](const QString &selection) {
auto tarLangList = traduzioneLangList;
lingua_originaleT->blockSignals(true);
tarLangList.removeOne(selection);
lingua_originaleT->clear();
lingua_originaleT->addItems(traduzioneLangList);
lingua_originaleT->blockSignals(false);
});
lingua_originaleT->setCurrentIndex(0);
Many thanks to #lifof on reddit!

Set a QStandartItem as Expandable without having a child item

i am trying to send a folder structure between two differnt programs which can be on different computers. On my server I have a QFileSystemModel and on my client I have a QTreeView which has a QStandardItemModel as a model. And i have a prebuild signal/slot system which can send QString and QStringList between the programs.
Client:
auto *m_stdItemModel = new QStandardItemModel(this);
auto *m_treeView = new QTreeView(this);
m_treeView->setModel(m_stdItemModel);
I want to sent the child directories from the server everytime I click on the expand button on the client m_treeView. The problem is that an entry is only expandable when it has a children.
My approach to do so was adding a dummy child and removing it when the user clicks the expand button.
Adding dummy:
void addChildToParent(QString child, QStandardItem *parentItem, bool isExpandable)
{
auto *childItem = new QStandardItem(child);
if(isExpandable)
{
childItem->appendRow(new QStandardItem("dummy"));
}
parentItem->appendRow(childItem);
}
Is there a workaround to do add an expand button without adding a dummy child?
Kind regards
I think your best bet would be to override QStandardItemModel::hasChildren...
class item_model: public QStandardItemModel {
using super = QStandardItemModel;
public:
virtual bool hasChildren (const QModelIndex &parent = QModelIndex()) const override
{
if (const auto *item = itemFromIndex(parent)) {
/*
* Here you need to return true or false depending on whether
* or not any attached views should treat `item' as having one
* or more child items. Presumably based on the same logic
* that governs `isExpandable' in the code you've shown.
*/
return is_expandable(item);
}
return super::hasChildren(parent);
}
};

QTreeView doesn't work outside the main function

I'm trying to generate a simple QTreeView inside another widget (QMainWindow). The following code works as expected and displays the tree-view,
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
QString rootPath = "C:/";
QFileSystemModel model;
model.setRootPath("");
QTreeView tree;
tree.setModel(&model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model.index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree.setRootIndex(rootIndex);
}
tree.setParent(&w);
tree.show();
return app.exec();
}
but if I extract the code that generates the tree-view, nothing seems to happen. The extracted function is as follows:
void create_tree(QMainWindow *w) {
QString rootPath = "C:/";
QFileSystemModel model;
model.setRootPath("");
QTreeView tree;
tree.setModel(&model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model.index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree.setRootIndex(rootIndex);
}
tree.setParent(w);
tree.show();
}
and the corresponding function call in main function is as follows:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.show();
create_tree(&w);
return app.exec();
}
How does the extracted function create_tree work and why is it not showing the tree view?
QFileSystemModel model;
and
QTreeView tree;
Are local stack variables, meaning they will be gone once you exit the create_tree function.
You can solve your issue by creating them on the heap by using new, which will keep them alive. Be careful, that you need to think about how you destroy these created objects. The Qt parenting system is a great help there, because the parent will destroy its children when it is destroyed, so your tree view is fine. You should think about good parent for your model to make sure you create no memory leak.
A working version of your function looks like this - be careful that you still need to handle the models deletion:
void create_tree(QMainWindow *w) {
QString rootPath = "C:/";
QFileSystemModel* model = new QFileSystemModel();
model->setRootPath("");
QTreeView* tree = new QTreeView();
tree->setModel(model);
if (!rootPath.isEmpty()) {
const QModelIndex rootIndex = model->index(QDir::cleanPath(rootPath));
if (rootIndex.isValid())
tree->setRootIndex(rootIndex);
}
tree->setParent(w);
tree->show();
}

How to get selected items from a QListView which uses custom QAbstractListModel in C++

I have created a custom list model following this guide Creating a cusotm model for a QListView. I am able to show a list of custom objects (such as the Employee as in the example) but I don't know how to retrieve back the selected ones (can I retrieve back the "linked" objects directly?).
Maybe do I have to do something with this command:
myLV->selectionModel()->selectedIndexes();
But I don't really know how to retrieve back the original custom objects.
[EDIT]
So far I have solved retrieving back the object adding a custom method inside my custom list model:
Employee* MyEmployeeListModel::getAtSelectedIndex(const QModelIndex& index){
return employees_.at(index.row());
}
And then calling this on the main window:
QModelIndexList selectedRows;
QItemSelectionModel * selmodel = ui->employeesLV->selectionModel();
selectedRows = selmodel->selectedRows();
MyEmployeeListModel* currModel = dynamic_cast <MyEmployeeListModel*>(ui->employeesLV->model());
for (const QModelIndex & index : selectedRows){
Employee* item=currModel->getAtSelectedIndex(index);
if (item) {
// do something with the item
}
}
Now what I am willing to know is if this is the real best practice or not.
I'm using the following code with a QTreeView (ui->treeMessages), but this should work with a QListView as well:
QModelIndexList selectedRows;
QItemSelectionModel * selmodel = ui->treeMessages->selectionModel();
selectedRows = selmodel->selectedRows();
for (const QModelIndex & index : selectedRows)
{
const QModelIndex sourceIndex = m_sortFilterModel->mapToSource(index);
ItemData * item = sourceIndex.internalPointer();
if (item) {
// do something
}
}

Is there a method or a way to check whether a user has visited a certain tab page on a QTabWidget?

I have a QTabWidget on my application, so user can navigate through the tab pages by clicking on the title, I want to know when user open a tab page, whether he/she visited this page previously. In QWizard class there is a method hasVisitedPage() which does the exact same thing on a wizard, but I couldn't find a similar method in QTabWidget class. What I want to know is, whether there is a method to do this like in QWizard?
this is the similar method in QWizard class http://doc.qt.io/archives/qt-4.8/qwizard.html#hasVisitedPage
Currently I am using a QList to store the visited page indexes and each time when a user open a tabpage check whether QList contains the index of the opened page, I think it would be more easy if I had a method to check
What I want to know is, whether there is a method to do this like in QWizard?
Unfortunatelly, there is not.
Currently I am using a QList to store the visited page indexes and each time when a user open a tabpage check whether QList contains the index of the opened page
QWizard does the same, i.e. has a QList<int> history; attribute. So, in my opinion you are doing it the right way.
Take a look at the source code for more details. In particular, QWizardPrivate::switchToPage might be interesting to you, in order to give you an idea how it is done in QWizard, so you can check your own implementation against that and adapt it if necessary.
The history property is easy enough to add:
// https://github.com/KubaO/stackoverflown/tree/master/questions/tabwidget-history-52033092
#include <QtWidgets>
#include <array>
static const char kHistory[] = "history";
auto getHistory(const QTabWidget *w) {
return w->property(kHistory).value<QList<int>>();
}
void addHistory(QTabWidget *tabWidget) {
QObject::connect(tabWidget, &QTabWidget::currentChanged, [tabWidget](int index) {
if (index < 0) return;
auto history = getHistory(tabWidget);
history.removeAll(index);
history.append(index);
tabWidget->setProperty(kHistory, QVariant::fromValue(history));
});
if (tabWidget->currentIndex() >= 0)
tabWidget->setProperty(
kHistory, QVariant::fromValue(QList<int>() << tabWidget->currentIndex()));
}
bool hasVisitedPage(const QTabWidget *w, int index) {
return getHistory(w).contains(index);
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget ui;
QVBoxLayout layout{&ui};
QTabWidget tabWidget;
QLabel history;
layout.addWidget(&tabWidget);
layout.addWidget(&history);
std::array<QLabel, 5> tabs;
for (auto &l : tabs) {
auto const n = &l - &tabs[0] + 1;
l.setText(QStringLiteral("Label on Page #%1").arg(n));
tabWidget.addTab(&l, QStringLiteral("Page #%1").arg(n));
}
addHistory(&tabWidget);
auto showHistory = [&] {
auto text = QStringLiteral("History: ");
for (auto i : tabWidget.property("history").value<QList<int>>())
text.append(QString::number(i + 1));
history.setText(text);
};
showHistory();
QObject::connect(&tabWidget, &QTabWidget::currentChanged, showHistory);
tabWidget.currentChanged(tabWidget.currentIndex());
ui.show();
return app.exec();
}