387 lines
8.9 KiB
C++
387 lines
8.9 KiB
C++
/*
|
|
* 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 <cassert>
|
|
|
|
#include <QStringList>
|
|
|
|
#include "FileTreeItem.h"
|
|
#include "FileTreeModel.h"
|
|
|
|
FileTreeModel::FileTreeModel (QObject * parent, bool isEditable):
|
|
QAbstractItemModel(parent),
|
|
myRootItem (new FileTreeItem),
|
|
myIndexCache (),
|
|
myIsEditable (isEditable)
|
|
{
|
|
}
|
|
|
|
FileTreeModel::~FileTreeModel()
|
|
{
|
|
clear();
|
|
|
|
delete myRootItem;
|
|
}
|
|
|
|
void
|
|
FileTreeModel::setEditable (bool editable)
|
|
{
|
|
myIsEditable = editable;
|
|
}
|
|
|
|
FileTreeItem *
|
|
FileTreeModel::itemFromIndex (const QModelIndex& index) const
|
|
{
|
|
return static_cast<FileTreeItem*>(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<QModelIndex> & 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<int,int> 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<int,int> 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<childCount; ++i)
|
|
subtreeChanged (index.child (i, 0), firstColumn, lastColumn);
|
|
}
|
|
|
|
void
|
|
FileTreeModel::clicked (const QModelIndex& index)
|
|
{
|
|
const int column (index.column());
|
|
|
|
if (!index.isValid())
|
|
return;
|
|
|
|
if (column == COL_WANTED)
|
|
{
|
|
bool want;
|
|
QSet<int> 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<int> 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 ());
|
|
}
|