mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
Torrent properties dialog improvements
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.
This commit is contained in:
@@ -71,13 +71,14 @@ namespace
|
||||
};
|
||||
|
||||
int
|
||||
measureViewItem (QAbstractItemView * view, const QString& text)
|
||||
measureViewItem (QTreeWidget * view, int column, const QString& text)
|
||||
{
|
||||
QStyleOptionViewItemV4 option;
|
||||
option.features = QStyleOptionViewItemV2::HasDisplay;
|
||||
option.text = text;
|
||||
return view->style ()->sizeFromContents (QStyle::CT_ItemViewItem, &option,
|
||||
QSize (QWIDGETSIZE_MAX, QWIDGETSIZE_MAX), view).width ();
|
||||
const QTreeWidgetItem * headerItem = view->headerItem ();
|
||||
|
||||
const int itemWidth = Utils::measureViewItem (view, text);
|
||||
const int headerWidth = Utils::measureHeaderItem (view->header (), headerItem->text (column));
|
||||
|
||||
return std::max (itemWidth, headerWidth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1245,11 +1246,11 @@ DetailsDialog::initPeersTab ()
|
||||
ui.peersView->sortByColumn (COL_ADDRESS, Qt::AscendingOrder);
|
||||
|
||||
ui.peersView->setColumnWidth (COL_LOCK, 20);
|
||||
ui.peersView->setColumnWidth (COL_UP, measureViewItem (ui.peersView, QLatin1String ("1024 MiB/s")));
|
||||
ui.peersView->setColumnWidth (COL_DOWN, measureViewItem (ui.peersView, QLatin1String ("1024 MiB/s")));
|
||||
ui.peersView->setColumnWidth (COL_PERCENT, measureViewItem (ui.peersView, QLatin1String ("100%")));
|
||||
ui.peersView->setColumnWidth (COL_STATUS, measureViewItem (ui.peersView, QLatin1String ("ODUK?EXI")));
|
||||
ui.peersView->setColumnWidth (COL_ADDRESS, measureViewItem (ui.peersView, QLatin1String ("888.888.888.888")));
|
||||
ui.peersView->setColumnWidth (COL_UP, measureViewItem (ui.peersView, COL_UP, QLatin1String ("1024 MiB/s")));
|
||||
ui.peersView->setColumnWidth (COL_DOWN, measureViewItem (ui.peersView, COL_DOWN, QLatin1String ("1024 MiB/s")));
|
||||
ui.peersView->setColumnWidth (COL_PERCENT, measureViewItem (ui.peersView, COL_PERCENT, QLatin1String ("100%")));
|
||||
ui.peersView->setColumnWidth (COL_STATUS, measureViewItem (ui.peersView, COL_STATUS, QLatin1String ("ODUK?EXI")));
|
||||
ui.peersView->setColumnWidth (COL_ADDRESS, measureViewItem (ui.peersView, COL_ADDRESS, QLatin1String ("888.888.888.888")));
|
||||
}
|
||||
|
||||
/***
|
||||
|
||||
@@ -582,9 +582,21 @@
|
||||
<layout class="QVBoxLayout" name="filesTabLayout">
|
||||
<item>
|
||||
<widget class="FileTreeView" name="filesView">
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="verticalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -57,7 +57,7 @@ FileTreeDelegate::paint (QPainter * painter,
|
||||
p.state = option.state | QStyle::State_Small;
|
||||
p.direction = qApp->layoutDirection();
|
||||
p.rect = option.rect;
|
||||
p.rect.setSize (QSize(option.rect.width()-2, option.rect.height()-8));
|
||||
p.rect.setSize (QSize(option.rect.width() - 4, option.rect.height() - 8));
|
||||
p.rect.moveCenter (option.rect.center());
|
||||
p.fontMetrics = qApp->fontMetrics();
|
||||
p.minimum = 0;
|
||||
@@ -70,19 +70,11 @@ FileTreeDelegate::paint (QPainter * painter,
|
||||
}
|
||||
else if(column == FileTreeModel::COL_WANTED)
|
||||
{
|
||||
QStyleOptionButton o;
|
||||
o.state = option.state;
|
||||
o.direction = qApp->layoutDirection();
|
||||
o.rect.setSize (QSize(20, option.rect.height()));
|
||||
o.rect.moveCenter (option.rect.center());
|
||||
o.fontMetrics = qApp->fontMetrics();
|
||||
switch (index.data().toInt())
|
||||
{
|
||||
case Qt::Unchecked: o.state |= QStyle::State_Off; break;
|
||||
case Qt::Checked: o.state |= QStyle::State_On; break;
|
||||
default: o.state |= QStyle::State_NoChange;break;
|
||||
}
|
||||
style->drawControl (QStyle::CE_CheckBox, &o, painter);
|
||||
QStyleOptionViewItemV4 vi (option);
|
||||
vi.features |= QStyleOptionViewItemV4::HasCheckIndicator;
|
||||
QRect checkRect = style->subElementRect (QStyle::SE_ItemViewItemCheckIndicator, &vi, nullptr);
|
||||
checkRect.moveCenter (option.rect.center ());
|
||||
drawCheck (painter, vi, checkRect, static_cast<Qt::CheckState> (index.data ().toInt ()));
|
||||
}
|
||||
|
||||
QItemDelegate::drawFocus (painter, option, option.rect);
|
||||
|
||||
@@ -107,29 +107,35 @@ FileTreeItem::data (int column, int role) const
|
||||
{
|
||||
value = Qt::AlignRight + Qt::AlignVCenter;
|
||||
}
|
||||
else if (role == Qt::DisplayRole)
|
||||
else if (role == Qt::DisplayRole || role == FileTreeModel::SortRole)
|
||||
{
|
||||
switch(column)
|
||||
{
|
||||
case FileTreeModel::COL_NAME:
|
||||
value.setValue (name());
|
||||
break;
|
||||
{
|
||||
case FileTreeModel::COL_NAME:
|
||||
value.setValue (name());
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_SIZE:
|
||||
value.setValue (sizeString() + QLatin1String (" "));
|
||||
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_PROGRESS:
|
||||
value.setValue (progress());
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_WANTED:
|
||||
value.setValue (isSubtreeWanted());
|
||||
break;
|
||||
case FileTreeModel::COL_WANTED:
|
||||
value.setValue (isSubtreeWanted());
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_PRIORITY:
|
||||
value.setValue (priorityString());
|
||||
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)
|
||||
@@ -172,21 +178,19 @@ FileTreeItem::progress () const
|
||||
QString
|
||||
FileTreeItem::sizeString () const
|
||||
{
|
||||
QString str;
|
||||
return Formatter::sizeToString (size ());
|
||||
}
|
||||
|
||||
uint64_t
|
||||
FileTreeItem::size () const
|
||||
{
|
||||
if (myChildren.isEmpty())
|
||||
{
|
||||
str = Formatter::sizeToString (myTotalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t have = 0;
|
||||
uint64_t total = 0;
|
||||
getSubtreeWantedSize (have, total);
|
||||
str = Formatter::sizeToString (total);
|
||||
}
|
||||
return myTotalSize;
|
||||
|
||||
return str;
|
||||
uint64_t have = 0;
|
||||
uint64_t total = 0;
|
||||
getSubtreeWantedSize (have, total);
|
||||
return total;
|
||||
}
|
||||
|
||||
std::pair<int,int>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
class FileTreeItem
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS (FileTreeItem)
|
||||
Q_DISABLE_COPY (FileTreeItem)
|
||||
|
||||
public:
|
||||
FileTreeItem (const QString& name = QString (), int fileIndex = -1, uint64_t size = 0):
|
||||
@@ -69,6 +70,7 @@ class FileTreeItem
|
||||
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();
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ class FileTreeModel: public QAbstractItemModel
|
||||
NUM_COLUMNS
|
||||
};
|
||||
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
|
||||
public:
|
||||
FileTreeModel (QObject * parent = nullptr, bool isEditable = true);
|
||||
virtual ~FileTreeModel ();
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "FileTreeDelegate.h"
|
||||
#include "FileTreeItem.h"
|
||||
#include "FileTreeModel.h"
|
||||
#include "FileTreeView.h"
|
||||
#include "Formatter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
FileTreeView::FileTreeView (QWidget * parent, bool isEditable):
|
||||
QTreeView (parent),
|
||||
@@ -23,27 +26,16 @@ FileTreeView::FileTreeView (QWidget * parent, bool isEditable):
|
||||
myProxy (new QSortFilterProxyModel (this)),
|
||||
myDelegate (new FileTreeDelegate (this))
|
||||
{
|
||||
setSortingEnabled (true);
|
||||
setAlternatingRowColors (true);
|
||||
setSelectionBehavior (QAbstractItemView::SelectRows);
|
||||
setSelectionMode (QAbstractItemView::ExtendedSelection);
|
||||
myProxy->setSourceModel (myModel);
|
||||
myProxy->setSortRole (FileTreeModel::SortRole);
|
||||
myProxy->setSortCaseSensitivity (Qt::CaseInsensitive);
|
||||
|
||||
setModel (myProxy);
|
||||
setItemDelegate (myDelegate);
|
||||
setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
|
||||
sortByColumn (FileTreeModel::COL_NAME, Qt::AscendingOrder);
|
||||
installEventFilter (this);
|
||||
|
||||
for (int i=0; i<FileTreeModel::NUM_COLUMNS; ++i)
|
||||
{
|
||||
setColumnHidden (i, (i<FileTreeModel::FIRST_VISIBLE_COLUMN) || (FileTreeModel::LAST_VISIBLE_COLUMN<i));
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
header()->setResizeMode(i, QHeaderView::Interactive);
|
||||
#else
|
||||
header()->setSectionResizeMode(i, QHeaderView::Interactive);
|
||||
#endif
|
||||
}
|
||||
setColumnHidden (i, (i<FileTreeModel::FIRST_VISIBLE_COLUMN) || (FileTreeModel::LAST_VISIBLE_COLUMN<i));
|
||||
|
||||
connect (this, SIGNAL(clicked(QModelIndex)),
|
||||
this, SLOT(onClicked(QModelIndex)));
|
||||
@@ -88,58 +80,90 @@ FileTreeView::onOpenRequested (const QString& path)
|
||||
emit openRequested (path);
|
||||
}
|
||||
|
||||
bool
|
||||
FileTreeView::eventFilter (QObject * o, QEvent * event)
|
||||
void
|
||||
FileTreeView::resizeEvent (QResizeEvent * event)
|
||||
{
|
||||
// this is kind of a hack to get the last three columns be the
|
||||
QTreeView::resizeEvent (event);
|
||||
|
||||
// this is kind of a hack to get the last four columns be the
|
||||
// right size, and to have the filename column use whatever
|
||||
// space is left over...
|
||||
if ((o == this) && (event->type() == QEvent::Resize))
|
||||
{
|
||||
QResizeEvent * r = static_cast<QResizeEvent*> (event);
|
||||
int left = r->size().width();
|
||||
const QFontMetrics fontMetrics(font());
|
||||
for (int column=FileTreeModel::FIRST_VISIBLE_COLUMN; column<=FileTreeModel::LAST_VISIBLE_COLUMN; ++column)
|
||||
{
|
||||
if (column == FileTreeModel::COL_NAME)
|
||||
continue;
|
||||
if (isColumnHidden (column))
|
||||
continue;
|
||||
|
||||
QString header;
|
||||
if (column == FileTreeModel::COL_SIZE)
|
||||
header = QLatin1String ("999.9 KiB");
|
||||
else
|
||||
header = myModel->headerData (column, Qt::Horizontal).toString();
|
||||
header += QLatin1String (" ");
|
||||
const int width = fontMetrics.size (0, header).width();
|
||||
setColumnWidth (column, width);
|
||||
left -= width;
|
||||
int left = event->size ().width () - 1;
|
||||
for (int column = FileTreeModel::FIRST_VISIBLE_COLUMN; column <= FileTreeModel::LAST_VISIBLE_COLUMN; ++column)
|
||||
{
|
||||
if (column == FileTreeModel::COL_NAME)
|
||||
continue;
|
||||
if (isColumnHidden (column))
|
||||
continue;
|
||||
|
||||
int minWidth = 0;
|
||||
|
||||
QStringList itemTexts;
|
||||
switch (column)
|
||||
{
|
||||
case FileTreeModel::COL_SIZE:
|
||||
for (int s = Formatter::B; s <= Formatter::TB; ++s)
|
||||
itemTexts << QLatin1String ("999.9 ") +
|
||||
Formatter::unitStr (Formatter::MEM, static_cast<Formatter::Size> (s));
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_PROGRESS:
|
||||
itemTexts << QLatin1String (" 100% ");
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_WANTED:
|
||||
minWidth = 20;
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_PRIORITY:
|
||||
itemTexts << FileTreeItem::tr ("Low") << FileTreeItem::tr ("Normal") <<
|
||||
FileTreeItem::tr ("High") << FileTreeItem::tr ("Mixed");
|
||||
break;
|
||||
}
|
||||
left -= 20; // not sure why this is necessary. it works in different themes + font sizes though...
|
||||
setColumnWidth(FileTreeModel::COL_NAME, std::max(left,0));
|
||||
|
||||
int itemWidth = 0;
|
||||
for (const QString& itemText: itemTexts)
|
||||
itemWidth = std::max (itemWidth, Utils::measureViewItem (this, itemText));
|
||||
|
||||
const QString headerText = myModel->headerData (column, Qt::Horizontal).toString ();
|
||||
int headerWidth = Utils::measureHeaderItem (this->header (), headerText);
|
||||
|
||||
const int width = std::max (minWidth, std::max (itemWidth, headerWidth));
|
||||
setColumnWidth (column, width);
|
||||
|
||||
left -= width;
|
||||
}
|
||||
|
||||
setColumnWidth (FileTreeModel::COL_NAME, std::max (left, 0));
|
||||
}
|
||||
|
||||
void
|
||||
FileTreeView::keyPressEvent (QKeyEvent * event)
|
||||
{
|
||||
QTreeView::keyPressEvent (event);
|
||||
|
||||
// handle using the keyboard to toggle the
|
||||
// wanted/unwanted state or the file priority
|
||||
else if (event->type () == QEvent::KeyPress && state () != EditingState)
|
||||
|
||||
if (state () == EditingState)
|
||||
return;
|
||||
|
||||
if (event->key () == Qt::Key_Space)
|
||||
{
|
||||
switch (static_cast<QKeyEvent*> (event)->key ())
|
||||
{
|
||||
case Qt::Key_Space:
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (FileTreeModel::COL_WANTED))
|
||||
clicked (i);
|
||||
break;
|
||||
int column;
|
||||
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (FileTreeModel::COL_PRIORITY))
|
||||
clicked (i);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (column))
|
||||
clicked (i);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -44,8 +44,9 @@ class FileTreeView: public QTreeView
|
||||
void openRequested (const QString& path);
|
||||
|
||||
protected:
|
||||
// QObject
|
||||
bool eventFilter (QObject *, QEvent *);
|
||||
// QWidget
|
||||
virtual void resizeEvent (QResizeEvent * event);
|
||||
virtual void keyPressEvent (QKeyEvent * event);
|
||||
|
||||
private:
|
||||
FileTreeModel * myModel;
|
||||
|
||||
@@ -131,6 +131,8 @@ TrackerDelegate::drawTracker (QPainter * painter,
|
||||
const TrackerInfo & inf) const
|
||||
{
|
||||
const bool isItemSelected ((option.state & QStyle::State_Selected) != 0);
|
||||
const bool isItemEnabled ((option.state & QStyle::State_Enabled) != 0);
|
||||
const bool isItemActive ((option.state & QStyle::State_Active) != 0);
|
||||
|
||||
QIcon trackerIcon (inf.st.getFavicon());
|
||||
|
||||
@@ -139,6 +141,15 @@ TrackerDelegate::drawTracker (QPainter * painter,
|
||||
|
||||
painter->save();
|
||||
|
||||
if (isItemSelected)
|
||||
{
|
||||
QPalette::ColorGroup cg = isItemEnabled ? QPalette::Normal : QPalette::Disabled;
|
||||
if (cg == QPalette::Normal && !isItemActive)
|
||||
cg = QPalette::Inactive;
|
||||
|
||||
painter->fillRect (option.rect, option.palette.brush (cg, QPalette::Highlight));
|
||||
}
|
||||
|
||||
trackerIcon.paint (painter, layout.iconRect, Qt::AlignCenter, isItemSelected ? QIcon::Selected : QIcon::Normal, QIcon::On);
|
||||
|
||||
QAbstractTextDocumentLayout::PaintContext paintContext;
|
||||
|
||||
29
qt/Utils.cc
29
qt/Utils.cc
@@ -12,12 +12,14 @@
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QColor>
|
||||
#include <QDataStream>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QIcon>
|
||||
#include <QInputDialog>
|
||||
#include <QObject>
|
||||
@@ -204,6 +206,33 @@ Utils::removeTrailingDirSeparator (const QString& path)
|
||||
return pathInfo.fileName ().isEmpty () ? pathInfo.absolutePath () : pathInfo.absoluteFilePath ();
|
||||
}
|
||||
|
||||
int
|
||||
Utils::measureViewItem (QAbstractItemView * view, const QString& text)
|
||||
{
|
||||
QStyleOptionViewItemV4 option;
|
||||
option.initFrom (view);
|
||||
option.features = QStyleOptionViewItemV2::HasDisplay;
|
||||
option.text = text;
|
||||
option.textElideMode = Qt::ElideNone;
|
||||
option.font = view->font ();
|
||||
|
||||
return view->style ()->sizeFromContents (QStyle::CT_ItemViewItem, &option,
|
||||
QSize (QWIDGETSIZE_MAX, QWIDGETSIZE_MAX), view).width ();
|
||||
}
|
||||
|
||||
int
|
||||
Utils::measureHeaderItem (QHeaderView * view, const QString& text)
|
||||
{
|
||||
QStyleOptionHeader option;
|
||||
option.initFrom (view);
|
||||
option.text = text;
|
||||
option.sortIndicator = view->isSortIndicatorShown () ? QStyleOptionHeader::SortDown :
|
||||
QStyleOptionHeader::None;
|
||||
|
||||
return view->style ()->sizeFromContents (QStyle::CT_HeaderSection, &option,
|
||||
QSize (QWIDGETSIZE_MAX, QWIDGETSIZE_MAX), view).width ();
|
||||
}
|
||||
|
||||
QColor
|
||||
Utils::getFadedColor (const QColor& color)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
class QAbstractItemView;
|
||||
class QColor;
|
||||
class QHeaderView;
|
||||
class QIcon;
|
||||
|
||||
class Utils
|
||||
@@ -34,6 +36,9 @@ class Utils
|
||||
rect.adjust (dx1, 0, -dx2, 0);
|
||||
}
|
||||
|
||||
static int measureViewItem (QAbstractItemView * view, const QString& text);
|
||||
static int measureHeaderItem (QHeaderView * view, const QString& text);
|
||||
|
||||
static QColor getFadedColor (const QColor& color);
|
||||
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user