mirror of
https://github.com/transmission/transmission.git
synced 2026-05-08 09:39:08 +01:00
#5608: Enter file renaming mode with keyboard only, not on mouse double-click (Qt client)
Since double-click is usually used to open things, don't use it to enter edit mode, use platform-specific keyboard shortcut instead (Return, F2, etc). Add context menu duplicating available actions, resembling that of Mac client. This includes new "only check selected" action. Speed up DND/priority change for large numbers of files at once. Make DND/priority toggling more natural: if both parent and its child are selected, only act on parent (with all its children following).
This commit is contained in:
@@ -585,6 +585,9 @@
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditKeyPressed</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -594,6 +597,9 @@
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
||||
@@ -304,21 +304,6 @@ FileTreeItem::setSubtreePriority (int i, QSet<int>& ids)
|
||||
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
|
||||
{
|
||||
@@ -358,13 +343,6 @@ FileTreeItem::setSubtreeWanted (bool b, QSet<int>& ids)
|
||||
child->setSubtreeWanted (b, ids);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeItem::twiddleWanted (QSet<int>& ids, bool& wanted)
|
||||
{
|
||||
wanted = isSubtreeWanted() != Qt::Checked;
|
||||
setSubtreeWanted (wanted, ids);
|
||||
}
|
||||
|
||||
QString
|
||||
FileTreeItem::path () const
|
||||
{
|
||||
|
||||
+12
-14
@@ -24,6 +24,14 @@ class FileTreeItem
|
||||
Q_DECLARE_TR_FUNCTIONS (FileTreeItem)
|
||||
Q_DISABLE_COPY (FileTreeItem)
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
LOW = (1 << 0),
|
||||
NORMAL = (1 << 1),
|
||||
HIGH = (1 << 2)
|
||||
};
|
||||
|
||||
public:
|
||||
FileTreeItem (const QString& name = QString (), int fileIndex = -1, uint64_t size = 0):
|
||||
myName (name),
|
||||
@@ -47,31 +55,21 @@ class FileTreeItem
|
||||
const QString& name () const { return myName; }
|
||||
QVariant data (int column, int role) const;
|
||||
std::pair<int, int> update (const QString& name, bool want, int priority, uint64_t have, bool updateFields);
|
||||
void twiddleWanted (QSet<int>& fileIds, bool&);
|
||||
void twiddlePriority (QSet<int>& fileIds, int&);
|
||||
void setSubtreeWanted (bool, QSet<int>& fileIds);
|
||||
void setSubtreePriority (int priority, QSet<int>& fileIds);
|
||||
int fileIndex () const { return myFileIndex; }
|
||||
uint64_t totalSize () const { return myTotalSize; }
|
||||
QString path () const;
|
||||
bool isComplete () const;
|
||||
int priority () const;
|
||||
int isSubtreeWanted () const;
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
LOW = (1 << 0),
|
||||
NORMAL = (1 << 1),
|
||||
HIGH = (1 << 2)
|
||||
};
|
||||
|
||||
private:
|
||||
void setSubtreePriority (int priority, QSet<int>& fileIds);
|
||||
void setSubtreeWanted (bool, QSet<int>& fileIds);
|
||||
QString priorityString () const;
|
||||
QString sizeString () const;
|
||||
void getSubtreeWantedSize (uint64_t& have, uint64_t& total) const;
|
||||
double progress () const;
|
||||
int priority () const;
|
||||
uint64_t size () const;
|
||||
int isSubtreeWanted () const;
|
||||
const QHash<QString,int>& getMyChildRows();
|
||||
|
||||
private:
|
||||
|
||||
+148
-49
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <libtransmission/transmission.h> // priorities
|
||||
|
||||
#include "FileTreeItem.h"
|
||||
#include "FileTreeModel.h"
|
||||
|
||||
@@ -108,9 +110,41 @@ FileTreeModel::setEditable (bool editable)
|
||||
FileTreeItem *
|
||||
FileTreeModel::itemFromIndex (const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return nullptr;
|
||||
|
||||
assert (index.model () == this);
|
||||
return static_cast<FileTreeItem*>(index.internalPointer());
|
||||
}
|
||||
|
||||
QModelIndexList
|
||||
FileTreeModel::getOrphanIndices (const QModelIndexList& indices) const
|
||||
{
|
||||
QModelIndexList orphanIndices = indices;
|
||||
|
||||
qSort (orphanIndices);
|
||||
|
||||
for (QMutableListIterator<QModelIndex> it (orphanIndices); it.hasNext ();)
|
||||
{
|
||||
QModelIndex walk = it.next ();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
walk = parent (walk, walk.column ());
|
||||
if (!walk.isValid ())
|
||||
break;
|
||||
|
||||
if (qBinaryFind (orphanIndices, walk) != orphanIndices.end ())
|
||||
{
|
||||
it.remove ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orphanIndices;
|
||||
}
|
||||
|
||||
QVariant
|
||||
FileTreeModel::data (const QModelIndex &index, int role) const
|
||||
{
|
||||
@@ -313,7 +347,7 @@ FileTreeModel::addFile (int fileIndex,
|
||||
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));
|
||||
emit dataChanged (indexOf (item, changed.first), indexOf (item, changed.second));
|
||||
if (!indexWithChangedParents.isValid () &&
|
||||
changed.first <= COL_PRIORITY && changed.second >= COL_SIZE)
|
||||
indexWithChangedParents = indexOf (item, 0);
|
||||
@@ -322,7 +356,7 @@ FileTreeModel::addFile (int fileIndex,
|
||||
}
|
||||
assert (item == myRootItem);
|
||||
if (indexWithChangedParents.isValid ())
|
||||
parentsChanged (indexWithChangedParents, COL_SIZE, COL_PRIORITY);
|
||||
emitParentsChanged (indexWithChangedParents, COL_SIZE, COL_PRIORITY);
|
||||
}
|
||||
else // we haven't build the FileTreeItems for these tokens yet
|
||||
{
|
||||
@@ -360,13 +394,13 @@ FileTreeModel::addFile (int fileIndex,
|
||||
|
||||
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));
|
||||
emit dataChanged (indexOf (item, changed.first), indexOf (item, changed.second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::parentsChanged (const QModelIndex& index, int firstColumn, int lastColumn)
|
||||
FileTreeModel::emitParentsChanged (const QModelIndex& index, int firstColumn, int lastColumn, QSet<QModelIndex> * visitedParentIndices)
|
||||
{
|
||||
assert (firstColumn <= lastColumn);
|
||||
|
||||
@@ -378,12 +412,19 @@ FileTreeModel::parentsChanged (const QModelIndex& index, int firstColumn, int la
|
||||
if (!walk.isValid ())
|
||||
break;
|
||||
|
||||
dataChanged (walk, walk.sibling (walk.row (), lastColumn));
|
||||
if (visitedParentIndices != nullptr)
|
||||
{
|
||||
if (visitedParentIndices->contains (walk))
|
||||
break;
|
||||
visitedParentIndices->insert (walk);
|
||||
}
|
||||
|
||||
emit dataChanged (walk, walk.sibling (walk.row (), lastColumn));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::subtreeChanged (const QModelIndex& index, int firstColumn, int lastColumn)
|
||||
FileTreeModel::emitSubtreeChanged (const QModelIndex& index, int firstColumn, int lastColumn)
|
||||
{
|
||||
assert (firstColumn <= lastColumn);
|
||||
|
||||
@@ -391,64 +432,122 @@ FileTreeModel::subtreeChanged (const QModelIndex& index, int firstColumn, int la
|
||||
if (!childCount)
|
||||
return;
|
||||
|
||||
// tell everyone that this tier changed
|
||||
dataChanged (index.child (0, firstColumn), index.child (childCount - 1, lastColumn));
|
||||
// tell everyone that this item changed
|
||||
emit dataChanged (index.child (0, firstColumn), index.child (childCount - 1, lastColumn));
|
||||
|
||||
// walk the subtiers
|
||||
// walk the subitems
|
||||
for (int i=0; i<childCount; ++i)
|
||||
subtreeChanged (index.child (i, 0), firstColumn, lastColumn);
|
||||
emitSubtreeChanged (index.child (i, 0), firstColumn, lastColumn);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::clicked (const QModelIndex& index)
|
||||
FileTreeModel::twiddleWanted (const QModelIndexList& indices)
|
||||
{
|
||||
const int column (index.column());
|
||||
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
if (column == COL_WANTED)
|
||||
QMap<bool, QModelIndexList> wantedIndices;
|
||||
for (const QModelIndex& i: getOrphanIndices (indices))
|
||||
{
|
||||
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);
|
||||
const FileTreeItem * const item = itemFromIndex (i);
|
||||
wantedIndices[item->isSubtreeWanted () != Qt::Checked] << i;
|
||||
}
|
||||
else if (column == COL_PRIORITY)
|
||||
|
||||
for (int i = 0; i <= 1; ++i)
|
||||
{
|
||||
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);
|
||||
if (wantedIndices.contains (i))
|
||||
setWanted (wantedIndices[i], i != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::doubleClicked (const QModelIndex& index)
|
||||
FileTreeModel::twiddlePriority (const QModelIndexList& indices)
|
||||
{
|
||||
if (!index.isValid())
|
||||
return;
|
||||
QMap<int, QModelIndexList> priorityIndices;
|
||||
for (const QModelIndex& i: getOrphanIndices (indices))
|
||||
{
|
||||
const FileTreeItem * const item = itemFromIndex (i);
|
||||
int priority = item->priority ();
|
||||
|
||||
const int column (index.column());
|
||||
if (column == COL_WANTED || column == COL_PRIORITY)
|
||||
return;
|
||||
// ... -> normal -> high -> low -> normal -> ...; mixed -> normal
|
||||
if (priority == FileTreeItem::NORMAL)
|
||||
priority = TR_PRI_HIGH;
|
||||
else if (priority == FileTreeItem::HIGH)
|
||||
priority = TR_PRI_LOW;
|
||||
else
|
||||
priority = TR_PRI_NORMAL;
|
||||
|
||||
FileTreeItem * item = itemFromIndex (index);
|
||||
priorityIndices[priority] << i;
|
||||
}
|
||||
|
||||
if (item->childCount () == 0 && item->isComplete ())
|
||||
emit openRequested (item->path ());
|
||||
for (int i = TR_PRI_LOW; i <= TR_PRI_HIGH; ++i)
|
||||
{
|
||||
if (priorityIndices.contains (i))
|
||||
setPriority (priorityIndices[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::setWanted (const QModelIndexList& indices, bool wanted)
|
||||
{
|
||||
if (indices.isEmpty ())
|
||||
return;
|
||||
|
||||
const QModelIndexList orphanIndices = getOrphanIndices (indices);
|
||||
|
||||
QSet<int> fileIds;
|
||||
for (const QModelIndex& i: orphanIndices)
|
||||
{
|
||||
FileTreeItem * const item = itemFromIndex (i);
|
||||
item->setSubtreeWanted (wanted, fileIds);
|
||||
|
||||
emit dataChanged (i, i);
|
||||
emitSubtreeChanged (i, COL_WANTED, COL_WANTED);
|
||||
}
|
||||
|
||||
// emit parent changes separately to avoid multiple updates for same items
|
||||
QSet<QModelIndex> parentIndices;
|
||||
for (const QModelIndex& i: orphanIndices)
|
||||
emitParentsChanged (i, COL_SIZE, COL_WANTED, &parentIndices);
|
||||
|
||||
if (!fileIds.isEmpty ())
|
||||
emit wantedChanged (fileIds, wanted);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeModel::setPriority (const QModelIndexList& indices, int priority)
|
||||
{
|
||||
if (indices.isEmpty ())
|
||||
return;
|
||||
|
||||
const QModelIndexList orphanIndices = getOrphanIndices (indices);
|
||||
|
||||
QSet<int> fileIds;
|
||||
for (const QModelIndex& i: orphanIndices)
|
||||
{
|
||||
FileTreeItem * const item = itemFromIndex (i);
|
||||
item->setSubtreePriority (priority, fileIds);
|
||||
|
||||
emit dataChanged (i, i);
|
||||
emitSubtreeChanged (i, COL_PRIORITY, COL_PRIORITY);
|
||||
}
|
||||
|
||||
// emit parent changes separately to avoid multiple updates for same items
|
||||
QSet<QModelIndex> parentIndices;
|
||||
for (const QModelIndex& i: orphanIndices)
|
||||
emitParentsChanged (i, COL_PRIORITY, COL_PRIORITY, &parentIndices);
|
||||
|
||||
if (!fileIds.isEmpty ())
|
||||
emit priorityChanged (fileIds, priority);
|
||||
}
|
||||
|
||||
bool
|
||||
FileTreeModel::openFile (const QModelIndex& index)
|
||||
{
|
||||
if (!index.isValid ())
|
||||
return false;
|
||||
|
||||
FileTreeItem * const item = itemFromIndex (index);
|
||||
if (item->childCount () != 0 || !item->isComplete ())
|
||||
return false;
|
||||
|
||||
emit openRequested (item->path ());
|
||||
return true;
|
||||
}
|
||||
|
||||
+11
-6
@@ -55,6 +55,14 @@ class FileTreeModel: public QAbstractItemModel
|
||||
uint64_t size, uint64_t have,
|
||||
bool torrentChanged);
|
||||
|
||||
bool openFile (const QModelIndex& index);
|
||||
|
||||
void twiddleWanted (const QModelIndexList& indices);
|
||||
void twiddlePriority (const QModelIndexList& indices);
|
||||
|
||||
void setWanted (const QModelIndexList& indices, bool wanted);
|
||||
void setPriority (const QModelIndexList& indices, int priority);
|
||||
|
||||
QModelIndex parent (const QModelIndex& child, int column) const;
|
||||
|
||||
// QAbstractItemModel
|
||||
@@ -67,10 +75,6 @@ class FileTreeModel: public QAbstractItemModel
|
||||
virtual int columnCount (const QModelIndex& parent = QModelIndex ()) const;
|
||||
virtual bool setData (const QModelIndex& index, const QVariant& value, int role = Qt::EditRole);
|
||||
|
||||
public slots:
|
||||
void clicked (const QModelIndex& index);
|
||||
void doubleClicked (const QModelIndex& index);
|
||||
|
||||
signals:
|
||||
void priorityChanged (const QSet<int>& fileIndices, int);
|
||||
void wantedChanged (const QSet<int>& fileIndices, bool);
|
||||
@@ -80,10 +84,11 @@ class FileTreeModel: public QAbstractItemModel
|
||||
private:
|
||||
void clearSubtree (const QModelIndex&);
|
||||
QModelIndex indexOf (FileTreeItem *, int column) const;
|
||||
void parentsChanged (const QModelIndex&, int firstColumn, int lastColumn);
|
||||
void subtreeChanged (const QModelIndex&, int firstColumn, int lastColumn);
|
||||
void emitParentsChanged (const QModelIndex&, int firstColumn, int lastColumn, QSet<QModelIndex> * visitedParentIndices = nullptr);
|
||||
void emitSubtreeChanged (const QModelIndex&, int firstColumn, int lastColumn);
|
||||
FileTreeItem * findItemForFileIndex (int fileIndex) const;
|
||||
FileTreeItem * itemFromIndex (const QModelIndex&) const;
|
||||
QModelIndexList getOrphanIndices (const QModelIndexList& indices) const;
|
||||
|
||||
private:
|
||||
bool myIsEditable;
|
||||
|
||||
+218
-39
@@ -8,11 +8,16 @@
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QQueue>
|
||||
#include <QResizeEvent>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include <libtransmission/transmission.h> // priorities
|
||||
|
||||
#include "FileTreeDelegate.h"
|
||||
#include "FileTreeItem.h"
|
||||
#include "FileTreeModel.h"
|
||||
@@ -20,6 +25,8 @@
|
||||
#include "Formatter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#define PRIORITY_KEY "priority"
|
||||
|
||||
FileTreeView::FileTreeView (QWidget * parent, bool isEditable):
|
||||
QTreeView (parent),
|
||||
myModel (new FileTreeModel (this, isEditable)),
|
||||
@@ -40,9 +47,6 @@ FileTreeView::FileTreeView (QWidget * parent, bool isEditable):
|
||||
connect (this, SIGNAL(clicked(QModelIndex)),
|
||||
this, SLOT(onClicked(QModelIndex)));
|
||||
|
||||
connect (this, SIGNAL(doubleClicked(QModelIndex)),
|
||||
this, SLOT(onDoubleClicked(QModelIndex)));
|
||||
|
||||
connect (myModel, SIGNAL(priorityChanged(QSet<int>, int)),
|
||||
this, SIGNAL(priorityChanged(QSet<int>, int)));
|
||||
|
||||
@@ -53,31 +57,17 @@ FileTreeView::FileTreeView (QWidget * parent, bool isEditable):
|
||||
this, SIGNAL(pathEdited(QString, QString)));
|
||||
|
||||
connect (myModel, SIGNAL (openRequested (QString)),
|
||||
this, SLOT (onOpenRequested (QString)),
|
||||
Qt::QueuedConnection);
|
||||
this, SIGNAL (openRequested (QString)));
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::onClicked (const QModelIndex& proxyIndex)
|
||||
{
|
||||
const QModelIndex modelIndex = myProxy->mapToSource (proxyIndex);
|
||||
myModel->clicked (modelIndex);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::onDoubleClicked (const QModelIndex& proxyIndex)
|
||||
{
|
||||
const QModelIndex modelIndex = myProxy->mapToSource (proxyIndex);
|
||||
myModel->doubleClicked (modelIndex);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::onOpenRequested (const QString& path)
|
||||
{
|
||||
if (state () == EditingState)
|
||||
return;
|
||||
|
||||
emit openRequested (path);
|
||||
if (modelIndex.column () == FileTreeModel::COL_WANTED)
|
||||
myModel->twiddleWanted (QModelIndexList () << modelIndex);
|
||||
else if (modelIndex.column () == FileTreeModel::COL_PRIORITY)
|
||||
myModel->twiddlePriority (QModelIndexList () << modelIndex);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -141,29 +131,55 @@ FileTreeView::resizeEvent (QResizeEvent * event)
|
||||
void
|
||||
FileTreeView::keyPressEvent (QKeyEvent * event)
|
||||
{
|
||||
if (state () != EditingState)
|
||||
{
|
||||
if (event->key () == Qt::Key_Space)
|
||||
{
|
||||
// handle using the keyboard to toggle the
|
||||
// wanted/unwanted state or the file priority
|
||||
|
||||
const Qt::KeyboardModifiers modifiers = event->modifiers ();
|
||||
|
||||
if (modifiers == Qt::NoModifier)
|
||||
{
|
||||
myModel->twiddleWanted (selectedSourceRows ());
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifiers == Qt::ShiftModifier)
|
||||
{
|
||||
myModel->twiddlePriority (selectedSourceRows ());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QTreeView::keyPressEvent (event);
|
||||
}
|
||||
|
||||
// handle using the keyboard to toggle the
|
||||
// wanted/unwanted state or the file priority
|
||||
|
||||
if (state () == EditingState)
|
||||
void
|
||||
FileTreeView::mouseDoubleClickEvent (QMouseEvent * event)
|
||||
{
|
||||
const QModelIndex index = currentIndex ();
|
||||
if (!index.isValid () || index.column () == FileTreeModel::COL_WANTED || index.column () == FileTreeModel::COL_PRIORITY)
|
||||
return;
|
||||
|
||||
if (event->key () == Qt::Key_Space)
|
||||
{
|
||||
int column;
|
||||
if (openSelectedItem ())
|
||||
return;
|
||||
|
||||
const Qt::KeyboardModifiers modifiers = event->modifiers ();
|
||||
if (modifiers == Qt::NoModifier)
|
||||
column = FileTreeModel::COL_WANTED;
|
||||
else if (modifiers == Qt::ShiftModifier)
|
||||
column = FileTreeModel::COL_PRIORITY;
|
||||
else
|
||||
return;
|
||||
QTreeView::mouseDoubleClickEvent (event);
|
||||
}
|
||||
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (column))
|
||||
clicked (i);
|
||||
}
|
||||
void
|
||||
FileTreeView::contextMenuEvent (QContextMenuEvent * event)
|
||||
{
|
||||
const QModelIndex rootIndex = myModel->index (0, 0);
|
||||
if (!rootIndex.isValid ())
|
||||
return;
|
||||
|
||||
if (myContextMenu == nullptr)
|
||||
initContextMenu ();
|
||||
myContextMenu->popup (event->globalPos ());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -213,3 +229,166 @@ FileTreeView::setEditable (bool editable)
|
||||
{
|
||||
myModel->setEditable (editable);
|
||||
}
|
||||
|
||||
bool
|
||||
FileTreeView::edit (const QModelIndex& index, EditTrigger trigger, QEvent * event)
|
||||
{
|
||||
if (selectionModel ()->selectedRows ().size () != 1)
|
||||
return false;
|
||||
|
||||
const QModelIndex nameIndex = index.sibling (index.row (), FileTreeModel::COL_NAME);
|
||||
if (editTriggers ().testFlag (trigger))
|
||||
selectionModel ()->setCurrentIndex (nameIndex, QItemSelectionModel::NoUpdate);
|
||||
|
||||
return QTreeView::edit (nameIndex, trigger, event);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::checkSelectedItems ()
|
||||
{
|
||||
myModel->setWanted (selectedSourceRows (), true);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::uncheckSelectedItems ()
|
||||
{
|
||||
myModel->setWanted (selectedSourceRows (), false);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::onlyCheckSelectedItems ()
|
||||
{
|
||||
const QModelIndex rootIndex = myModel->index (0, FileTreeModel::COL_WANTED);
|
||||
if (!rootIndex.isValid ())
|
||||
return;
|
||||
|
||||
QModelIndexList wantedIndices = selectedSourceRows (FileTreeModel::COL_WANTED);
|
||||
myModel->setWanted (wantedIndices, true);
|
||||
|
||||
qSort (wantedIndices);
|
||||
|
||||
QSet<QModelIndex> wantedIndicesParents;
|
||||
for (const QModelIndex& i: wantedIndices)
|
||||
{
|
||||
for (QModelIndex p = i.parent (); p.isValid (); p = p.parent ())
|
||||
wantedIndicesParents.insert (p.sibling (p.row (), FileTreeModel::COL_WANTED));
|
||||
}
|
||||
|
||||
QQueue<QModelIndex> parentsQueue;
|
||||
parentsQueue.enqueue (rootIndex);
|
||||
QModelIndexList unwantedIndices;
|
||||
while (!parentsQueue.isEmpty ())
|
||||
{
|
||||
const QModelIndex parentIndex = parentsQueue.dequeue ();
|
||||
if (qBinaryFind (wantedIndices, parentIndex) != wantedIndices.end ())
|
||||
continue;
|
||||
|
||||
for (int i = 0, count = myModel->rowCount (parentIndex); i < count; ++i)
|
||||
{
|
||||
const QModelIndex childIndex = parentIndex.child (i, FileTreeModel::COL_WANTED);
|
||||
const int childCheckState = childIndex.data ().toInt ();
|
||||
if (childCheckState == Qt::Unchecked || qBinaryFind (wantedIndices, childIndex) != wantedIndices.end ())
|
||||
continue;
|
||||
|
||||
if (childCheckState == Qt::Checked &&
|
||||
childIndex.sibling (childIndex.row (), FileTreeModel::COL_FILE_INDEX).data ().toInt () >= 0)
|
||||
{
|
||||
unwantedIndices << childIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wantedIndicesParents.contains (childIndex))
|
||||
unwantedIndices << childIndex;
|
||||
else
|
||||
parentsQueue.enqueue (childIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myModel->setWanted (unwantedIndices, false);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::setSelectedItemsPriority ()
|
||||
{
|
||||
QAction * action = qobject_cast<QAction *> (sender ());
|
||||
assert (action != nullptr);
|
||||
myModel->setPriority (selectedSourceRows (), action->property (PRIORITY_KEY).toInt ());
|
||||
}
|
||||
|
||||
bool
|
||||
FileTreeView::openSelectedItem ()
|
||||
{
|
||||
return myModel->openFile (myProxy->mapToSource (currentIndex ()));
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::renameSelectedItem ()
|
||||
{
|
||||
QTreeView::edit (currentIndex ());
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::refreshContextMenuActionsSensitivity ()
|
||||
{
|
||||
assert (myContextMenu != nullptr);
|
||||
|
||||
const QModelIndexList selectedRows = selectionModel ()->selectedRows (FileTreeModel::COL_WANTED);
|
||||
|
||||
QSet<int> checkStates;
|
||||
for (const QModelIndex& i: selectedRows)
|
||||
{
|
||||
checkStates.insert (i.data ().toInt ());
|
||||
if (checkStates.size() == 3)
|
||||
break;
|
||||
}
|
||||
|
||||
const bool haveSelection = !selectedRows.isEmpty ();
|
||||
const bool haveSingleSelection = selectedRows.size() == 1;
|
||||
const bool haveUnchecked = checkStates.contains (Qt::Unchecked) || checkStates.contains (Qt::PartiallyChecked);
|
||||
const bool haveChecked = checkStates.contains (Qt::Checked) || checkStates.contains (Qt::PartiallyChecked);
|
||||
|
||||
myCheckSelectedAction->setEnabled (haveUnchecked);
|
||||
myUncheckSelectedAction->setEnabled (haveChecked);
|
||||
myOnlyCheckSelectedAction->setEnabled (haveSelection);
|
||||
myPriorityMenu->setEnabled (haveSelection);
|
||||
myOpenAction->setEnabled (haveSingleSelection && myProxy->rowCount (selectedRows.first ()) == 0);
|
||||
myRenameAction->setEnabled (haveSingleSelection);
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::initContextMenu ()
|
||||
{
|
||||
myContextMenu = new QMenu (this);
|
||||
|
||||
myCheckSelectedAction = myContextMenu->addAction (tr ("Check selected"), this, SLOT (checkSelectedItems ()));
|
||||
myUncheckSelectedAction = myContextMenu->addAction (tr ("Uncheck selected"), this, SLOT (uncheckSelectedItems ()));
|
||||
myOnlyCheckSelectedAction = myContextMenu->addAction (tr ("Only check selected"), this, SLOT (onlyCheckSelectedItems ()));
|
||||
|
||||
myContextMenu->addSeparator ();
|
||||
|
||||
myPriorityMenu = myContextMenu->addMenu (tr ("Priority"));
|
||||
myHighPriorityAction = myPriorityMenu->addAction (FileTreeItem::tr ("High"), this, SLOT (setSelectedItemsPriority ()));
|
||||
myNormalPriorityAction = myPriorityMenu->addAction (FileTreeItem::tr ("Normal"), this, SLOT (setSelectedItemsPriority ()));
|
||||
myLowPriorityAction = myPriorityMenu->addAction (FileTreeItem::tr ("Low"), this, SLOT (setSelectedItemsPriority ()));
|
||||
|
||||
myHighPriorityAction->setProperty (PRIORITY_KEY, TR_PRI_HIGH);
|
||||
myNormalPriorityAction->setProperty (PRIORITY_KEY, TR_PRI_NORMAL);
|
||||
myLowPriorityAction->setProperty (PRIORITY_KEY, TR_PRI_LOW);
|
||||
|
||||
myContextMenu->addSeparator ();
|
||||
|
||||
myOpenAction = myContextMenu->addAction (tr ("Open"), this, SLOT (openSelectedItem ()));
|
||||
myRenameAction = myContextMenu->addAction (tr ("Rename..."), this, SLOT (renameSelectedItem ()));
|
||||
|
||||
connect (myContextMenu, SIGNAL (aboutToShow ()), SLOT (refreshContextMenuActionsSensitivity ()));
|
||||
}
|
||||
|
||||
QModelIndexList
|
||||
FileTreeView::selectedSourceRows (int column) const
|
||||
{
|
||||
QModelIndexList indices;
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (column))
|
||||
indices << myProxy->mapToSource (i);
|
||||
return indices;
|
||||
}
|
||||
|
||||
+34
-5
@@ -15,6 +15,8 @@
|
||||
|
||||
#include "Torrent.h" // FileList
|
||||
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class FileTreeDelegate;
|
||||
@@ -32,11 +34,6 @@ class FileTreeView: public QTreeView
|
||||
|
||||
void setEditable (bool editable);
|
||||
|
||||
public slots:
|
||||
void onClicked (const QModelIndex& index);
|
||||
void onDoubleClicked (const QModelIndex& index);
|
||||
void onOpenRequested (const QString& path);
|
||||
|
||||
signals:
|
||||
void priorityChanged (const QSet<int>& fileIndices, int priority);
|
||||
void wantedChanged (const QSet<int>& fileIndices, bool wanted);
|
||||
@@ -47,11 +44,43 @@ class FileTreeView: public QTreeView
|
||||
// QWidget
|
||||
virtual void resizeEvent (QResizeEvent * event);
|
||||
virtual void keyPressEvent (QKeyEvent * event);
|
||||
virtual void mouseDoubleClickEvent (QMouseEvent * event);
|
||||
virtual void contextMenuEvent (QContextMenuEvent * event);
|
||||
|
||||
// QAbstractItemView
|
||||
virtual bool edit (const QModelIndex& index, EditTrigger trigger, QEvent * event);
|
||||
|
||||
private slots:
|
||||
void onClicked (const QModelIndex& index);
|
||||
|
||||
void checkSelectedItems ();
|
||||
void uncheckSelectedItems ();
|
||||
void onlyCheckSelectedItems ();
|
||||
void setSelectedItemsPriority ();
|
||||
bool openSelectedItem ();
|
||||
void renameSelectedItem ();
|
||||
|
||||
void refreshContextMenuActionsSensitivity ();
|
||||
QModelIndexList selectedSourceRows (int column = 0) const;
|
||||
|
||||
private:
|
||||
void initContextMenu ();
|
||||
|
||||
private:
|
||||
FileTreeModel * myModel;
|
||||
QSortFilterProxyModel * myProxy;
|
||||
FileTreeDelegate * myDelegate;
|
||||
|
||||
QMenu * myContextMenu = nullptr;
|
||||
QMenu * myPriorityMenu = nullptr;
|
||||
QAction * myCheckSelectedAction = nullptr;
|
||||
QAction * myUncheckSelectedAction = nullptr;
|
||||
QAction * myOnlyCheckSelectedAction = nullptr;
|
||||
QAction * myHighPriorityAction = nullptr;
|
||||
QAction * myNormalPriorityAction = nullptr;
|
||||
QAction * myLowPriorityAction = nullptr;
|
||||
QAction * myOpenAction = nullptr;
|
||||
QAction * myRenameAction = nullptr;
|
||||
};
|
||||
|
||||
#endif // QTR_FILE_TREE_VIEW_H
|
||||
|
||||
Reference in New Issue
Block a user