/* * This file Copyright (C) 2009-2015 Mnemosyne LLC * * It may be used under the GNU GPL versions 2 or 3 * or any future license endorsed by Mnemosyne LLC. * * $Id$ */ #include #include #include "FileTreeItem.h" #include "FileTreeModel.h" FileTreeModel::FileTreeModel (QObject * parent, bool isEditable): QAbstractItemModel(parent), myIsEditable (isEditable), myRootItem (new FileTreeItem), myIndexCache () { } FileTreeModel::~FileTreeModel() { clear(); delete myRootItem; } void FileTreeModel::setEditable (bool editable) { myIsEditable = editable; } FileTreeItem * FileTreeModel::itemFromIndex (const QModelIndex& index) const { return static_cast(index.internalPointer()); } QVariant FileTreeModel::data (const QModelIndex &index, int role) const { QVariant value; if (index.isValid()) value = itemFromIndex(index)->data (index.column(), role); return value; } Qt::ItemFlags FileTreeModel::flags (const QModelIndex& index) const { int i(Qt::ItemIsSelectable | Qt::ItemIsEnabled); if(myIsEditable && (index.column() == COL_NAME)) i |= Qt::ItemIsEditable; if(index.column() == COL_WANTED) i |= Qt::ItemIsUserCheckable | Qt::ItemIsTristate; return (Qt::ItemFlags)i; } bool FileTreeModel::setData (const QModelIndex& index, const QVariant& newname, int role) { if (role == Qt::EditRole) { FileTreeItem * item = itemFromIndex (index); emit pathEdited (item->path (), newname.toString ()); } return false; // don't update the view until the session confirms the change } QVariant FileTreeModel::headerData (int column, Qt::Orientation orientation, int role) const { QVariant data; if (orientation==Qt::Horizontal && role==Qt::DisplayRole) { switch (column) { case COL_NAME: data.setValue (tr("File")); break; case COL_SIZE: data.setValue (tr("Size")); break; case COL_PROGRESS: data.setValue (tr("Progress")); break; case COL_WANTED: data.setValue (tr("Download")); break; case COL_PRIORITY: data.setValue (tr("Priority")); break; default: break; } } return data; } QModelIndex FileTreeModel::index (int row, int column, const QModelIndex& parent) const { QModelIndex i; if (hasIndex (row, column, parent)) { FileTreeItem * parentItem; if (!parent.isValid ()) parentItem = myRootItem; else parentItem = itemFromIndex (parent); FileTreeItem * childItem = parentItem->child (row); if (childItem) i = createIndex (row, column, childItem); } return i; } QModelIndex FileTreeModel::parent (const QModelIndex& child) const { return parent (child, 0); // QAbstractItemModel::parent() wants col 0 } QModelIndex FileTreeModel::parent (const QModelIndex& child, int column) const { QModelIndex parent; if (child.isValid()) parent = indexOf (itemFromIndex(child)->parent(), column); return parent; } int FileTreeModel::rowCount (const QModelIndex& parent) const { FileTreeItem * parentItem; if (parent.isValid()) parentItem = itemFromIndex (parent); else parentItem = myRootItem; return parentItem->childCount(); } int FileTreeModel::columnCount (const QModelIndex& parent) const { Q_UNUSED(parent); return NUM_COLUMNS; } QModelIndex FileTreeModel::indexOf (FileTreeItem * item, int column) const { if (!item || item==myRootItem) return QModelIndex(); return createIndex(item->row(), column, item); } void FileTreeModel::clearSubtree (const QModelIndex& top) { size_t i = rowCount (top); while (i > 0) clearSubtree(index(--i, 0, top)); FileTreeItem * const item = itemFromIndex (top); if (item == 0) return; if (item->fileIndex () != -1) myIndexCache.remove (item->fileIndex ()); delete item; } void FileTreeModel::clear () { beginResetModel (); clearSubtree (QModelIndex()); endResetModel (); assert (myIndexCache.isEmpty ()); } FileTreeItem * FileTreeModel::findItemForFileIndex (int fileIndex) const { return myIndexCache.value (fileIndex, 0); } void FileTreeModel::addFile (int fileIndex, const QString & filename, bool wanted, int priority, uint64_t totalSize, uint64_t have, QList & rowsAdded, bool updateFields) { FileTreeItem * item; QStringList tokens = filename.split (QChar::fromLatin1('/')); item = findItemForFileIndex (fileIndex); if (item) // this file is already in the tree, we've added this { QModelIndex indexWithChangedParents; while (!tokens.isEmpty()) { const QString token = tokens.takeLast(); const std::pair changed = item->update (token, wanted, priority, have, updateFields); if (changed.first >= 0) { dataChanged (indexOf (item, changed.first), indexOf (item, changed.second)); if (!indexWithChangedParents.isValid () && changed.first <= COL_PRIORITY && changed.second >= COL_SIZE) indexWithChangedParents = indexOf (item, 0); } item = item->parent(); } assert (item == myRootItem); if (indexWithChangedParents.isValid ()) parentsChanged (indexWithChangedParents, COL_SIZE, COL_PRIORITY); } else // we haven't build the FileTreeItems for these tokens yet { bool added = false; item = myRootItem; while (!tokens.isEmpty()) { const QString token = tokens.takeFirst(); FileTreeItem * child(item->child(token)); if (!child) { added = true; QModelIndex parentIndex (indexOf(item, 0)); const int n (item->childCount()); beginInsertRows (parentIndex, n, n); if (tokens.isEmpty()) child = new FileTreeItem (token, fileIndex, totalSize); else child = new FileTreeItem (token); item->appendChild (child); endInsertRows (); rowsAdded.append (indexOf(child, 0)); } item = child; } if (item != myRootItem) { assert (item->fileIndex() == fileIndex); assert (item->totalSize() == totalSize); myIndexCache[fileIndex] = item; const std::pair changed = item->update (item->name(), wanted, priority, have, added || updateFields); if (changed.first >= 0) dataChanged (indexOf (item, changed.first), indexOf (item, changed.second)); } } } void FileTreeModel::parentsChanged (const QModelIndex& index, int firstColumn, int lastColumn) { assert (firstColumn <= lastColumn); QModelIndex walk = index; for (;;) { walk = parent (walk, firstColumn); if (!walk.isValid ()) break; dataChanged (walk, walk.sibling (walk.row (), lastColumn)); } } void FileTreeModel::subtreeChanged (const QModelIndex& index, int firstColumn, int lastColumn) { assert (firstColumn <= lastColumn); const int childCount = rowCount (index); if (!childCount) return; // tell everyone that this tier changed dataChanged (index.child (0, firstColumn), index.child (childCount - 1, lastColumn)); // walk the subtiers for (int i=0; i file_ids; FileTreeItem * item; item = itemFromIndex (index); item->twiddleWanted (file_ids, want); emit wantedChanged (file_ids, want); dataChanged (index, index); parentsChanged (index, COL_SIZE, COL_WANTED); subtreeChanged (index, COL_WANTED, COL_WANTED); } else if (column == COL_PRIORITY) { int priority; QSet file_ids; FileTreeItem * item; item = itemFromIndex (index); item->twiddlePriority (file_ids, priority); emit priorityChanged (file_ids, priority); dataChanged (index, index); parentsChanged (index, column, column); subtreeChanged (index, column, column); } } void FileTreeModel::doubleClicked (const QModelIndex& index) { if (!index.isValid()) return; const int column (index.column()); if (column == COL_WANTED || column == COL_PRIORITY) return; FileTreeItem * item = itemFromIndex (index); if (item->childCount () == 0 && item->isComplete ()) emit openRequested (item->path ()); }