QFileSystemModel and QTreeView - c++

I show a QFileSystemModel through a QTreeView.
Whenever the user clicks on a directory (expanded or not expanded) I want to get a list of the files inside this directory.
void MyModel::selectionChanged(const QItemSelection& selected,const QItemSelection& deselected) {
for (auto const & it : selected.indexes()) {
for (int i=0;i<rowCount(it);i++) {
auto child = it.child(i, it.column());
qDebug() << fileName(child);
}
}
}
The problem with the above code is that this only seems to work once that particular directory has been expanded. As long as the directory has never been expanded (since program start) rowCount is 0.
How can I force the model to populate the children of the given model index? Without necessarily showing the children in the view? One level of children indexes would be enough in this case.

Related

Find nth file/folder in QFileSystemModel

I am using a QFileSystemModel and a QTreeView and I am trying to make the TreeView select the first folder/file by default. To do that I need to get the index of the first folder/file but I can't find a method that does that in QFileSystemModel.
Could you please help me?
Thank you in advance.
I have tried setCurrentIndex(_model->index(x, y)) but it didn't work. Here's the code I have and the tree shown:
void CodeView::finished_loading(QString file) {
qDebug()<<"Currently selected : " << _model->fileName( ui->treeView->currentIndex());
qDebug()<<"(0,0) "<< _model->fileName(_model->index(0,0));
qDebug()<<"(1,0) "<< _model->fileName(_model->index(1,0));
qDebug()<<"(2,0) "<< _model->fileName(_model->index(2,0));
qDebug()<<"(3,0) "<< _model->fileName(_model->index(3,0));
qDebug()<<"(0,0) "<< _model->fileName(_model->index(0,0));
qDebug()<<"(1,1) "<< _model->fileName(_model->index(1,1));
qDebug()<<"(2,1) "<< _model->fileName(_model->index(2,1));
qDebug()<<"(3,1) "<< _model->fileName(_model->index(3,1));
ui->treeView->setCurrentIndex(_model.index(1,0));
qDebug()<<"New selected : " << _model->fileName( ui->treeView->currentIndex());
}
Output :
Currently selected : "Wassim Gharbi"
(0,0) "/"
(1,0) ""
(2,0) ""
(3,0) ""
(0,0) "/"
(1,1) ""
(2,1) ""
(3,1) ""
New selected : "Wassim Gharbi"
The method is not in the model, but in the view.
QTreeView::setCurrentIndex
from the docs:
QAbstractItemView::setCurrentIndex(const QModelIndex &index) Sets the
current item to be the item at index.
Unless the current selection mode is NoSelection, the item is also
selected. Note that this function also updates the starting position
for any new selections the user performs.
To set an item as the current item without selecting it, call
selectionModel()->setCurrentIndex(index,
QItemSelectionModel::NoUpdate);
See also currentIndex(), currentChanged(), and selectionMode.
The code for the first folder is not really easy as first glance but you need to remember that the data abstractions on the models makes it powerfull, and cumberstone at the same time, so any item could potentially be a folder or a file, and we need to check for that:
if (model->rowCount()) // has at least one file or folder
{
QModelIndex current = model->index(0,0);
if (model->rowCount(current) == 0); // it's a file.
return current;
else {
// walk the tree trying to find the first file on the folders.
while(model->rowCount(current) > 0) {
current = model->index(0,0,current);
}
if (index.isValid())
return index; // our file inside folders
else
return QModelIndex(); // no file inside folders.
}
}
In order to get the index at a specific location in the model, use QModelIndex::child(row, column)
QFileSystemModel *model = new QFileSystemModel();
model->setRootPath("C:/Qt");//your path
ui->treeView->setModel(model);
ui->treeView->setCurrentIndex(model->index(0, 0).child(0, 0));
I can tell from your question that you don't understand how a treeview's row and column system works. Please read the documentation

How to access childWidgets in a QTreeView using QModelIndex?

I'm working on an application using the Qt library (version 4.8).
I have a QTreeView with a QStandardItemModel. My widget looks like that:
Item1
subitem11
subitem12
Item2
subitem21
subitem22
Item3
subitem31
subitem32
Here is how I add the items to my QTreeView:
model->setItem(0, 0, item1);
item1->setChild(0, 0, subitem12);
I want to take an action only when the user double cliked an item (and do nothing when he clicked a subitem). So I use the doubleClicked(const QModelIndex & index) signal.
I want to process the information about the item/subitem which was double cliked by the user. So I get the row of my item/subitem:
index.row();
But every time I try to reference to the item/subitem to display its name or check if it has children, I can only access the items:
index.model()->item(row)->text();
My question is: how can I acces the subitems (vbetween items abd subitems) in my slot? Or how can I prevent them from emitting the signal? I can't disable them - it would be too confusing for the user.
Edit: The problem is that every time I click on an item or subitem and execute:
index.model()->item(row)->hasChildren();
or:
index.model()->item(row)->parent() == 0;
I get true as result. So I can reference only the items.
My question is: What is the correct way to reference to subitems?
When you are trying to access model items by row index, the model returns top-level item at that row. Use itemFromIndex instead:
auto item = index.model()->itemFromIndex(index);
if (item && item->hasChildren()){
// item is not a leaf
}
EDIT index.model() returns QAbstractItemModel*, so a cast is also necessary here (or, better, storing a pointer to the standard model somehwere in the code).
I would do it like this:
// Define your custom role to store item type.
enum MyRoles
{
ItemTypeRole = Qt::UserRole + 1
};
// Define item types.
enum ItemType
{
Primary,
Secondary
};
Than set the proper item type for all your items:
QStandardItem* item = new QStandardItem("Item1");
item->setData(Primary, ItemTypeRole);
QStandardItem* subItem = new QStandardItem("SubItem1");
subItem->setData(Secondary, ItemTypeRole);
And in your slot connected to the doubleClicked signal acces the type like this:
ItemType type = static_cast<ItemType>(index.data(ItemTypeRole).toInt());
if (type == Primary)
std::cout << "It's a Primary item!" << std::endl;
else if (type == Secondary)
std::cout << "It's a Secondary item!" << std::endl;
You can't disable the signal for the subitems. However, you can check if an item has a parent. It it doesn't, it is an item and if it does, it's a subitem.
if (item->parent() != 0)
.. //subitem
else
.. //item
An alternative would be to use the data() function to set some special value to distinguish between the two.
item1->setData(QVariant("item"));
subitem1->setData(QVariant("subitem"));
Then query the value in your doubleclick handler:
QVariant var = item->data();
if (var.toString() == "item")
...
else if (var.toString() == "subitem")
...

QListView: how to automatically scroll the view and keep current selection on correct items in view when removing items from the top

I have a list view with a custom model. The model allows me to add text to the bottom of the list (using 'addText(const QString&)') and to remove items from the top of the list (using 'removeItemsFromTop(int _iCount)').
What is the best way to add text to the view and keep the model size under some maximum (lets say 'MAX_LIST_SIZE'), while always maintaining the view (i.e. current selection and items in view should not change when items are removed).
The solution should preferably be a function that I can use wherever I'm using my custom model.
I have looked at indexAt(...), scrollTo(...), currentIndex(...) and setCurrentIndex(...) methods on QListView, but I can't figure out how to put all of this together.
So far I have (for auto scrolling the view)
// add items here ...
// cleanup
QModelIndex indexViewTop = listView->indexAt(QPoint(8, 8));
if (listModel->rowCount() > MAX_SIZE)
{
int iRemoveCount = (listModel->rowCount() - MAX_SIZE) + MAX_SIZE/10;
listModel->clearTextFromFront(iRemoveCount);
listView->scrollTo(indexViewTop.sibling(indexViewTop.row() - iRemoveCount, 0), QAbstractItemView::PositionAtTop);
}
This is supposed to scroll the list view as items are removed to keep the view consistent, but indexAt(...) always returns an invalid index.
For keeping the selection consistent I tried:
// add items her ...
// cleanup
if (listModel->rowCount() > MAX_SIZE)
{
int iCurrentViewIndex = listView->currentIndex().row();
int iRemoveCount = (listModel->rowCount() - MAX_SIZE) + MAX_SIZE/10;
listModel->clearTextFromFront(iRemoveCount);
listView->setCurrentIndex(listModel->index(iCurrentViewIndex - iRemoveCount, 0));
}
This seems to work, but I'm still stuck on the auto scrolling.
I did a queue-like table model implementation. I think it is similar for QAbstractItemModel. Best way would be to use QQueue to store data.
Now, this is a snipped for QAbstractTableModel (which is subclass of QAbstractItemModel so it should work; mEvents is QQueue):
// custom table for inserting events
void EventPreviewTableModel::insertEvent(const DeviceEvent &event) {
beginInsertRows(QModelIndex(), 0, 0);
mEvents.enqueue(event);
endInsertRows();
if (mEvents.size() > SIZE) {
beginRemoveRows(QModelIndex(), mEvents.size(), mEvents.size());
mEvents.dequeue();
endRemoveRows();
}
}
And also override data() and rowCount() to serve correct data.
For second part using ItemIsSelected flag for items you want to have selected is done through: Qt::ItemFlags QAbstractItemModel::flags(const QModelIndex & index)
This is my current approach and it seems to work well:
void addTitlesToList(Model *model, QListView *view, std::vector<Object*> &items)
{
QScrollBar *pVerticalScrollBar = view->verticalScrollBar();
bool bScrolledToBottom = pVerticalScrollBar->value() == pVerticalScrollBar->maximum();
QModelIndex indexViewTop = view->indexAt(QPoint(8, 8));
// add to model
model->pushItems(items);
// cleanup if model gets too big
if (model->rowCount() > model->maxListSize())
{
int iCurrentViewIndex = view->currentIndex().row();
int iRemoveCount = (int)(model->rowCount() - model->maxListSize()) + (int)model->maxListSize()/10;
model->removeItemsFromFront(iRemoveCount);
// scrolls to maintain view on items
if (bScrolledToBottom == false)
{
_pView->scrollTo(indexViewTop.sibling(indexViewTop.row() - iRemoveCount, 0), QAbstractItemView::PositionAtTop);
}
// maintain selection
if (iCurrentViewIndex >= iRemoveCount)
{
view->setCurrentIndex(_pModel->index(iCurrentViewIndex - iRemoveCount, 0));
}
else
{
view->setCurrentIndex(QModelIndex());
}
}
// move scroll bar to keep new items in view (if scrolled to the bottom)
if (bScrolledToBottom == true)
{
view->scrollToBottom();
}
}
One issue I had with indexAt(QPoint(...)) is that I was calling it after adding the items to the list, which seems to cause it to always return an invalid index. Calling indexAt before anything is added to the model seems to work.
I also added automatic 'scroll to bottom' if already there (i.e. the view either stays fixed on specific items or sticks to the latest items if scrolled all the way to the bottom).

Hide the checkbox from a QListView item

I wave a QListView that is backed by a QStandardItemModel. Under certain circonstances, the QStandardItem are made checkable. A checkbox gets displayed besides the item's display. At some point, I want to remove hide the QStandardItem checkbox. I set its checkable state to false but it doesn't hide the checkbox (though it cannot be checked anymore).
The only way I have found of hiding the checkbox is to replace the item with a new one. This doesn't seem the proper way to preceed.
This is the code:
MyModel::MyModel(QObject *parent):QStandardItemModel(parent){}
void MyModel::createItem(int row, const QString &text)
{
setItem(row, new QStandardItem(text));
}
void MyModel::setCheckable(int row)
{
item(row)->setCheckState(Qt::Unchecked);
item(row)->setCheckable(true); // A checkbox appears besides the text
}
void MyModel::hideCheckBox(int row)
{
item(row)->setCheckState(Qt::Unchecked);
item(row)->setCheckable(false); // does not work
// I need to completely replace the item for the checkbox to disapear.
// This doesn't seem the proper way to proceed
setItem(row, new QStandardItem(item(row)->data(Qt::DisplayRole).toString()));
}
Is there better way to proceed?
When you call setCheckState or setCheckable, the qt will update the data of list item by adding or setting a Qt::CheckStateRole data. If the Qt::CheckStateRole data is existed, the check icon will be shown. So you need remove it from the data map of the list item.
Finally, the code of hideCheckBox should be:
void MyModel::hideCheckBox(int row)
{
// check the item pointer
QStandardItem* pitem = item(row);
if (pitem == NULL) return;
// find and delete the Qt::CheckStateRole data
QMap<int, QVariant> mdata = itemData(pitem->index());
if (mdata.remove(Qt::CheckStateRole))
{
setItemData(pitem->index(), mdata);
}
}
Hope it useful. :)
I think the presence of the check boxes in items defined by item flags, so that I would write the function in the following way:
void MyModel::hideCheckBox(int row)
{
// Does not set the Qt::ItemIsUserCheckable flag.
item(row)->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
}

QFileSystemModel - Incremental update/pre-emptive update

From the Qt documentation:
QFileSystemModel will not fetch any files or directories until setRootPath() is called. This will prevent any unnecessary querying on the file system until that point such as listing the drives on Windows.
Unlike QDirModel(obsolete), QFileSystemModel uses a separate thread to populate itself so it will not cause the main thread to hang as the file system is being queried. Calls to rowCount() will return 0 until the model populates a directory. QFileSystemModel keeps a cache with file information. The cache is automatically kept up to date using the QFileSystemWatcher.
I'm using QTreeView together with a sub-classed QFileSystemModel which uses checkable boxes.
If I call QFileSystemModel::rowCount(index) before an item has been expanded in the tree I will receive '0', regardless of whether there are any subdirectories or files. However, once it has been expanded the correct row count will then be given when called again.
I think if you call QFileSystemModel::setRootPath() this will fetch the data from the specified file path, but it seems that it does not 'execute fast enough' (the cache is not updated) before I call QFileSystemModel::rowCount in my code below.
// Whenever a checkbox in the TreeView is clicked
bool MyModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole)
{
if (value == Qt::Checked)
{
setRootPath(this->filePath(index));
checklist.insert(index);
set_children(index);
}
else
{
checklist.remove(index);
unchecklist->insert(index);
}
emit dataChanged(index, index);
return true;
}
return QFileSystemModel::setData(index, value, role);
}
// Counts how many items/children the node has (i.e. file/folders)
void MyModel::set_children(const QModelIndex& index)
{
int row = this->rowCount(index);
qDebug() << QString::number(row);
}
Is there a way I can pre-emptively gather the sub-folder information before I try to count how many items are contained in that folder?
Thanks
QFileSystemModel emits directoryLoaded(const QString &path) signal, when gather thread finished loading directory.