You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libfm-qt-packaging/src/dirtreemodelitem.cpp

338 lines
11 KiB

/*
* Copyright (C) 2014 - 2015 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "dirtreemodelitem.h"
#include "dirtreemodel.h"
#include "icontheme.h"
#include <QDebug>
namespace Fm {
DirTreeModelItem::DirTreeModelItem():
fileInfo_(nullptr),
folder_(nullptr),
expanded_(false),
loaded_(false),
parent_(nullptr),
placeHolderChild_(nullptr),
model_(nullptr) {
}
DirTreeModelItem::DirTreeModelItem(FmFileInfo* info, DirTreeModel* model, DirTreeModelItem* parent):
fileInfo_(fm_file_info_ref(info)),
folder_(nullptr),
displayName_(QString::fromUtf8(fm_file_info_get_disp_name(info))),
icon_(IconTheme::icon(fm_file_info_get_icon(info))),
expanded_(false),
loaded_(false),
parent_(parent),
placeHolderChild_(nullptr),
model_(model) {
if(info)
addPlaceHolderChild();
}
DirTreeModelItem::~DirTreeModelItem() {
if(fileInfo_)
fm_file_info_unref(fileInfo_);
if(folder_)
freeFolder();
// delete child items if needed
if(!children_.isEmpty()) {
Q_FOREACH(DirTreeModelItem* item, children_) {
delete item;
}
}
if(!hiddenChildren_.isEmpty()) {
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
delete item;
}
}
}
void DirTreeModelItem::addPlaceHolderChild() {
placeHolderChild_ = new DirTreeModelItem();
placeHolderChild_->parent_ = this;
placeHolderChild_->model_ = model_;
placeHolderChild_->displayName_ = DirTreeModel::tr("Loading...");
children_.append(placeHolderChild_);
}
void DirTreeModelItem::freeFolder() {
if(folder_) {
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFinishLoading), this);
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesAdded), this);
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesRemoved), this);
g_signal_handlers_disconnect_by_func(folder_, gpointer(onFolderFilesChanged), this);
g_object_unref(folder_);
folder_ = nullptr;
}
}
void DirTreeModelItem::loadFolder() {
if(!expanded_) {
/* dynamically load content of the folder. */
folder_ = fm_folder_from_path(fm_file_info_get_path(fileInfo_));
/* g_debug("fm_dir_tree_model_load_row()"); */
/* associate the data with loaded handler */
g_signal_connect(folder_, "finish-loading", G_CALLBACK(onFolderFinishLoading), this);
g_signal_connect(folder_, "files-added", G_CALLBACK(onFolderFilesAdded), this);
g_signal_connect(folder_, "files-removed", G_CALLBACK(onFolderFilesRemoved), this);
g_signal_connect(folder_, "files-changed", G_CALLBACK(onFolderFilesChanged), this);
/* set 'expanded' flag beforehand as callback may check it */
expanded_ = true;
/* if the folder is already loaded, call "loaded" handler ourselves */
if(fm_folder_is_loaded(folder_)) { // already loaded
GList* file_l;
FmFileInfoList* files = fm_folder_get_files(folder_);
for(file_l = fm_file_info_list_peek_head_link(files); file_l; file_l = file_l->next) {
FmFileInfo* fi = FM_FILE_INFO(file_l->data);
if(fm_file_info_is_dir(fi)) {
insertFileInfo(fi);
}
}
onFolderFinishLoading(folder_, this);
}
}
}
void DirTreeModelItem::unloadFolder() {
if(expanded_) { /* do some cleanup */
/* remove all children, and replace them with a dummy child
* item to keep expander in the tree view around. */
// delete all visible child items
model_->beginRemoveRows(index(), 0, children_.count() - 1);
if(!children_.isEmpty()) {
Q_FOREACH(DirTreeModelItem* item, children_) {
delete item;
}
children_.clear();
}
model_->endRemoveRows();
// remove hidden children
if(!hiddenChildren_.isEmpty()) {
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
delete item;
}
hiddenChildren_.clear();
}
/* now, we have no child since all child items are removed.
* So we add a place holder child item to keep the expander around. */
addPlaceHolderChild();
/* deactivate folder since it will be reactivated on expand */
freeFolder();
expanded_ = false;
loaded_ = false;
}
}
QModelIndex DirTreeModelItem::index() {
Q_ASSERT(model_);
return model_->indexFromItem(this);
}
/* Add file info to parent node to proper position.
* GtkTreePath tp is the tree path of parent node. */
DirTreeModelItem* DirTreeModelItem::insertFileInfo(FmFileInfo* fi) {
// qDebug() << "insertFileInfo: " << fm_file_info_get_disp_name(fi);
DirTreeModelItem* item = new DirTreeModelItem(fi, model_);
insertItem(item);
return item;
}
// find a good position to insert the new item
int DirTreeModelItem::insertItem(DirTreeModelItem* newItem) {
if(model_->showHidden() || !newItem->fileInfo_ || !fm_file_info_is_hidden(newItem->fileInfo_)) {
const char* new_key = fm_file_info_get_collate_key(newItem->fileInfo_);
int pos = 0;
QList<DirTreeModelItem*>::iterator it;
for(it = children_.begin(); it != children_.end(); ++it) {
DirTreeModelItem* child = *it;
if(G_UNLIKELY(!child->fileInfo_))
continue;
const char* key = fm_file_info_get_collate_key(child->fileInfo_);
if(strcmp(new_key, key) <= 0)
break;
++pos;
}
// inform the world that we're about to insert the item
model_->beginInsertRows(index(), pos, pos);
newItem->parent_ = this;
children_.insert(it, newItem);
model_->endInsertRows();
return pos;
}
else { // hidden folder
hiddenChildren_.append(newItem);
}
return -1;
}
// FmFolder signal handlers
// static
void DirTreeModelItem::onFolderFinishLoading(FmFolder* folder, gpointer user_data) {
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
DirTreeModel* model = _this->model_;
/* set 'loaded' flag beforehand as callback may check it */
_this->loaded_ = true;
QModelIndex index = _this->index();
qDebug() << "folder loaded";
// remove the placeholder child if needed
if(_this->children_.count() == 1) { // we have no other child other than the place holder item, leave it
_this->placeHolderChild_->displayName_ = DirTreeModel::tr("<No sub folders>");
QModelIndex placeHolderIndex = _this->placeHolderChild_->index();
// qDebug() << "placeHolderIndex: "<<placeHolderIndex;
Q_EMIT model->dataChanged(placeHolderIndex, placeHolderIndex);
}
else {
int pos = _this->children_.indexOf(_this->placeHolderChild_);
model->beginRemoveRows(index, pos, pos);
_this->children_.removeAt(pos);
delete _this->placeHolderChild_;
model->endRemoveRows();
_this->placeHolderChild_ = nullptr;
}
Q_EMIT model->rowLoaded(index);
}
// static
void DirTreeModelItem::onFolderFilesAdded(FmFolder* folder, GSList* files, gpointer user_data) {
GSList* l;
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
for(l = files; l; l = l->next) {
FmFileInfo* fi = FM_FILE_INFO(l->data);
if(fm_file_info_is_dir(fi)) { /* FIXME: maybe adding files can be allowed later */
/* Ideally FmFolder should not emit files-added signals for files that
* already exists. So there is no need to check for duplication here. */
_this->insertFileInfo(fi);
}
}
}
// static
void DirTreeModelItem::onFolderFilesRemoved(FmFolder* folder, GSList* files, gpointer user_data) {
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
DirTreeModel* model = _this->model_;
for(GSList* l = files; l; l = l->next) {
FmFileInfo* fi = FM_FILE_INFO(l->data);
int pos;
DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(fi), &pos);
if(child) {
model->beginRemoveRows(_this->index(), pos, pos);
_this->children_.removeAt(pos);
delete child;
model->endRemoveRows();
}
}
}
// static
void DirTreeModelItem::onFolderFilesChanged(FmFolder* folder, GSList* files, gpointer user_data) {
DirTreeModelItem* _this = (DirTreeModelItem*)user_data;
DirTreeModel* model = _this->model_;
for(GSList* l = files; l; l = l->next) {
FmFileInfo* changedFile = FM_FILE_INFO(l->data);
int pos;
DirTreeModelItem* child = _this->childFromName(fm_file_info_get_name(changedFile), &pos);
if(child) {
QModelIndex childIndex = child->index();
Q_EMIT model->dataChanged(childIndex, childIndex);
}
}
}
DirTreeModelItem* DirTreeModelItem::childFromName(const char* utf8_name, int* pos) {
int i = 0;
for (const auto item : children_) {
if(item->fileInfo_ && strcmp(fm_file_info_get_name(item->fileInfo_), utf8_name) == 0) {
if(pos)
*pos = i;
return item;
}
++i;
}
return nullptr;
}
DirTreeModelItem* DirTreeModelItem::childFromPath(FmPath* path, bool recursive) const {
Q_ASSERT(path != nullptr);
Q_FOREACH(DirTreeModelItem* item, children_) {
// if(item->fileInfo_)
// qDebug() << "child: " << QString::fromUtf8(fm_file_info_get_disp_name(item->fileInfo_));
if(item->fileInfo_ && fm_path_equal(fm_file_info_get_path(item->fileInfo_), path)) {
return item;
} else if(recursive) {
DirTreeModelItem* child = item->childFromPath(path, true);
if(child)
return child;
}
}
return nullptr;
}
void DirTreeModelItem::setShowHidden(bool show) {
if(show) {
// move all hidden children to visible list
Q_FOREACH(DirTreeModelItem* item, hiddenChildren_) {
insertItem(item);
}
hiddenChildren_.clear();
}
else { // hide hidden folders
QModelIndex _index = index();
QList<DirTreeModelItem*>::iterator it, next;
int pos = 0;
for(it = children_.begin(); it != children_.end(); ++pos) {
DirTreeModelItem* item = *it;
next = it + 1;
if(item->fileInfo_) {
if(fm_file_info_is_hidden(item->fileInfo_)) { // hidden folder
// remove from the model and add to the hiddenChildren_ list
model_->beginRemoveRows(_index, pos, pos);
children_.erase(it);
hiddenChildren_.append(item);
model_->endRemoveRows();
}
else { // visible folder, recursively filter its children
item->setShowHidden(show);
}
}
it = next;
}
}
}
} // namespace Fm