mirror of
https://github.com/transmission/transmission
synced 2024-12-23 08:13:27 +00:00
58312e6c16
Simplify DND checkboxes drawing, this also fixes incorrect drawing on Mac when file tree widget is inactive. Do better job calculating column widths for file tree to avoid ellipsis. Fix file tree sorting order for size and priority columns. Change key to toggle priorities to Shift+Space instead of Enter/Return to avoid conflicts with name editing and default button handling. Fix selected tracker item background drawing in certain cases.
390 lines
7.7 KiB
C++
390 lines
7.7 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 <algorithm>
|
|
#include <cassert>
|
|
|
|
#include <QApplication>
|
|
#include <QStyle>
|
|
|
|
#include <libtransmission/transmission.h> // priorities
|
|
|
|
#include "FileTreeItem.h"
|
|
#include "FileTreeModel.h"
|
|
#include "Formatter.h"
|
|
#include "Utils.h" // mime icons
|
|
|
|
const QHash<QString,int>&
|
|
FileTreeItem::getMyChildRows ()
|
|
{
|
|
const size_t n = childCount();
|
|
|
|
// ensure that all the rows are hashed
|
|
while (myFirstUnhashedRow < n)
|
|
{
|
|
myChildRows.insert (myChildren[myFirstUnhashedRow]->name(),
|
|
myFirstUnhashedRow);
|
|
++myFirstUnhashedRow;
|
|
}
|
|
|
|
return myChildRows;
|
|
}
|
|
|
|
|
|
FileTreeItem::~FileTreeItem ()
|
|
{
|
|
assert(myChildren.isEmpty());
|
|
|
|
if (myParent != 0)
|
|
{
|
|
const int pos = row();
|
|
assert ((pos>=0) && "couldn't find child in parent's lookup");
|
|
myParent->myChildren.removeAt(pos);
|
|
myParent->myChildRows.remove(name());
|
|
myParent->myFirstUnhashedRow = pos;
|
|
}
|
|
}
|
|
|
|
void
|
|
FileTreeItem::appendChild (FileTreeItem * child)
|
|
{
|
|
const size_t n = childCount();
|
|
child->myParent = this;
|
|
myChildren.append (child);
|
|
myFirstUnhashedRow = n;
|
|
}
|
|
|
|
FileTreeItem *
|
|
FileTreeItem::child (const QString& filename)
|
|
{
|
|
FileTreeItem * item(0);
|
|
|
|
const int row = getMyChildRows().value (filename, -1);
|
|
if (row != -1)
|
|
{
|
|
item = child (row);
|
|
assert (filename == item->name());
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
int
|
|
FileTreeItem::row () const
|
|
{
|
|
int i(-1);
|
|
|
|
if(myParent)
|
|
{
|
|
i = myParent->getMyChildRows().value (name(), -1);
|
|
assert (this == myParent->myChildren[i]);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
QVariant
|
|
FileTreeItem::data (int column, int role) const
|
|
{
|
|
QVariant value;
|
|
|
|
if (column == FileTreeModel::COL_FILE_INDEX)
|
|
{
|
|
value.setValue (myFileIndex);
|
|
}
|
|
else if (role == Qt::EditRole)
|
|
{
|
|
if (column == 0)
|
|
value.setValue (name());
|
|
}
|
|
else if ((role == Qt::TextAlignmentRole) && column == FileTreeModel::COL_SIZE)
|
|
{
|
|
value = Qt::AlignRight + Qt::AlignVCenter;
|
|
}
|
|
else if (role == Qt::DisplayRole || role == FileTreeModel::SortRole)
|
|
{
|
|
switch(column)
|
|
{
|
|
case FileTreeModel::COL_NAME:
|
|
value.setValue (name());
|
|
break;
|
|
|
|
case FileTreeModel::COL_SIZE:
|
|
if (role == Qt::DisplayRole)
|
|
value.setValue (sizeString());
|
|
else
|
|
value.setValue (size ());
|
|
break;
|
|
|
|
case FileTreeModel::COL_PROGRESS:
|
|
value.setValue (progress());
|
|
break;
|
|
|
|
case FileTreeModel::COL_WANTED:
|
|
value.setValue (isSubtreeWanted());
|
|
break;
|
|
|
|
case FileTreeModel::COL_PRIORITY:
|
|
if (role == Qt::DisplayRole)
|
|
value.setValue (priorityString());
|
|
else
|
|
value.setValue (priority ());
|
|
break;
|
|
}
|
|
}
|
|
else if (role == Qt::DecorationRole && column == FileTreeModel::COL_NAME)
|
|
{
|
|
if (childCount () > 0)
|
|
value = qApp->style ()->standardIcon (QStyle::SP_DirOpenIcon);
|
|
else
|
|
value = Utils::guessMimeIcon (name ());
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void
|
|
FileTreeItem::getSubtreeWantedSize (uint64_t& have, uint64_t& total) const
|
|
{
|
|
if (myIsWanted)
|
|
{
|
|
have += myHaveSize;
|
|
total += myTotalSize;
|
|
}
|
|
|
|
for (const FileTreeItem * const i: myChildren)
|
|
i->getSubtreeWantedSize(have, total);
|
|
}
|
|
|
|
double
|
|
FileTreeItem::progress () const
|
|
{
|
|
double d(0);
|
|
uint64_t have(0), total(0);
|
|
|
|
getSubtreeWantedSize (have, total);
|
|
if (total)
|
|
d = have / (double)total;
|
|
|
|
return d;
|
|
}
|
|
|
|
QString
|
|
FileTreeItem::sizeString () const
|
|
{
|
|
return Formatter::sizeToString (size ());
|
|
}
|
|
|
|
uint64_t
|
|
FileTreeItem::size () const
|
|
{
|
|
if (myChildren.isEmpty())
|
|
return myTotalSize;
|
|
|
|
uint64_t have = 0;
|
|
uint64_t total = 0;
|
|
getSubtreeWantedSize (have, total);
|
|
return total;
|
|
}
|
|
|
|
std::pair<int,int>
|
|
FileTreeItem::update (const QString& name,
|
|
bool wanted,
|
|
int priority,
|
|
uint64_t haveSize,
|
|
bool updateFields)
|
|
{
|
|
int changed_count = 0;
|
|
int changed_columns[4];
|
|
|
|
if (myName != name)
|
|
{
|
|
if (myParent)
|
|
myParent->myFirstUnhashedRow = row();
|
|
|
|
myName = name;
|
|
changed_columns[changed_count++] = FileTreeModel::COL_NAME;
|
|
}
|
|
|
|
if (fileIndex () != -1)
|
|
{
|
|
if (myHaveSize != haveSize)
|
|
{
|
|
myHaveSize = haveSize;
|
|
changed_columns[changed_count++] = FileTreeModel::COL_PROGRESS;
|
|
}
|
|
|
|
if (updateFields)
|
|
{
|
|
if (myIsWanted != wanted)
|
|
{
|
|
myIsWanted = wanted;
|
|
changed_columns[changed_count++] = FileTreeModel::COL_WANTED;
|
|
}
|
|
|
|
if (myPriority != priority)
|
|
{
|
|
myPriority = priority;
|
|
changed_columns[changed_count++] = FileTreeModel::COL_PRIORITY;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<int,int> changed (-1, -1);
|
|
if (changed_count > 0)
|
|
{
|
|
std::sort (changed_columns, changed_columns+changed_count);
|
|
changed.first = changed_columns[0];
|
|
changed.second = changed_columns[changed_count-1];
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
QString
|
|
FileTreeItem::priorityString () const
|
|
{
|
|
const int i = priority();
|
|
|
|
switch (i)
|
|
{
|
|
case LOW: return tr("Low");
|
|
case HIGH: return tr("High");
|
|
case NORMAL: return tr("Normal");
|
|
default: return tr("Mixed");
|
|
}
|
|
}
|
|
|
|
int
|
|
FileTreeItem::priority () const
|
|
{
|
|
int i(0);
|
|
|
|
if (myChildren.isEmpty())
|
|
{
|
|
switch (myPriority)
|
|
{
|
|
case TR_PRI_LOW:
|
|
i |= LOW;
|
|
break;
|
|
|
|
case TR_PRI_HIGH:
|
|
i |= HIGH;
|
|
break;
|
|
|
|
default:
|
|
i |= NORMAL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (const FileTreeItem * const child: myChildren)
|
|
i |= child->priority();
|
|
|
|
return i;
|
|
}
|
|
|
|
void
|
|
FileTreeItem::setSubtreePriority (int i, QSet<int>& ids)
|
|
{
|
|
if (myPriority != i)
|
|
{
|
|
myPriority = i;
|
|
|
|
if (myFileIndex >= 0)
|
|
ids.insert (myFileIndex);
|
|
}
|
|
|
|
for (FileTreeItem * const child: myChildren)
|
|
child->setSubtreePriority (i, ids);
|
|
}
|
|
|
|
void
|
|
FileTreeItem::twiddlePriority (QSet<int>& ids, int& p)
|
|
{
|
|
const int old(priority());
|
|
|
|
if (old & LOW)
|
|
p = TR_PRI_NORMAL;
|
|
else if (old & NORMAL)
|
|
p = TR_PRI_HIGH;
|
|
else
|
|
p = TR_PRI_LOW;
|
|
|
|
setSubtreePriority (p, ids);
|
|
}
|
|
|
|
int
|
|
FileTreeItem::isSubtreeWanted () const
|
|
{
|
|
if(myChildren.isEmpty())
|
|
return myIsWanted ? Qt::Checked : Qt::Unchecked;
|
|
|
|
int wanted(-1);
|
|
for (const FileTreeItem * const child: myChildren)
|
|
{
|
|
const int childWanted = child->isSubtreeWanted();
|
|
|
|
if (wanted == -1)
|
|
wanted = childWanted;
|
|
|
|
if (wanted != childWanted)
|
|
wanted = Qt::PartiallyChecked;
|
|
|
|
if (wanted == Qt::PartiallyChecked)
|
|
return wanted;
|
|
}
|
|
|
|
return wanted;
|
|
}
|
|
|
|
void
|
|
FileTreeItem::setSubtreeWanted (bool b, QSet<int>& ids)
|
|
{
|
|
if (myIsWanted != b)
|
|
{
|
|
myIsWanted = b;
|
|
|
|
if (myFileIndex >= 0)
|
|
ids.insert(myFileIndex);
|
|
}
|
|
|
|
for (FileTreeItem * const child: myChildren)
|
|
child->setSubtreeWanted (b, ids);
|
|
}
|
|
|
|
void
|
|
FileTreeItem::twiddleWanted (QSet<int>& ids, bool& wanted)
|
|
{
|
|
wanted = isSubtreeWanted() != Qt::Checked;
|
|
setSubtreeWanted (wanted, ids);
|
|
}
|
|
|
|
QString
|
|
FileTreeItem::path () const
|
|
{
|
|
QString itemPath;
|
|
const FileTreeItem * item = this;
|
|
|
|
while (item != NULL && !item->name().isEmpty())
|
|
{
|
|
if (itemPath.isEmpty())
|
|
itemPath = item->name();
|
|
else
|
|
itemPath = item->name() + QLatin1Char ('/') + itemPath;
|
|
item = item->parent ();
|
|
}
|
|
|
|
return itemPath;
|
|
}
|
|
|
|
bool
|
|
FileTreeItem::isComplete () const
|
|
{
|
|
return myHaveSize == totalSize ();
|
|
}
|