mirror of
https://github.com/transmission/transmission.git
synced 2025-12-24 12:28:52 +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,7 +107,7 @@ 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)
|
||||
{
|
||||
@@ -116,7 +116,10 @@ FileTreeItem::data (int column, int role) const
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_SIZE:
|
||||
value.setValue (sizeString() + QLatin1String (" "));
|
||||
if (role == Qt::DisplayRole)
|
||||
value.setValue (sizeString());
|
||||
else
|
||||
value.setValue (size ());
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_PROGRESS:
|
||||
@@ -128,7 +131,10 @@ FileTreeItem::data (int column, int role) const
|
||||
break;
|
||||
|
||||
case FileTreeModel::COL_PRIORITY:
|
||||
if (role == Qt::DisplayRole)
|
||||
value.setValue (priorityString());
|
||||
else
|
||||
value.setValue (priority ());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
return myTotalSize;
|
||||
|
||||
uint64_t have = 0;
|
||||
uint64_t total = 0;
|
||||
getSubtreeWantedSize (have, total);
|
||||
str = Formatter::sizeToString (total);
|
||||
}
|
||||
|
||||
return str;
|
||||
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,28 +26,17 @@ 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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
left -= 20; // not sure why this is necessary. it works in different themes + font sizes though...
|
||||
setColumnWidth(FileTreeModel::COL_NAME, std::max(left,0));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
switch (static_cast<QKeyEvent*> (event)->key ())
|
||||
{
|
||||
case Qt::Key_Space:
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (FileTreeModel::COL_WANTED))
|
||||
clicked (i);
|
||||
break;
|
||||
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
for (const QModelIndex& i: selectionModel ()->selectedRows (FileTreeModel::COL_PRIORITY))
|
||||
clicked (i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state () == EditingState)
|
||||
return;
|
||||
|
||||
return false;
|
||||
if (event->key () == Qt::Key_Space)
|
||||
{
|
||||
int column;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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