mirror of
https://github.com/transmission/transmission
synced 2025-02-22 14:10:34 +00:00
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.
345 lines
12 KiB
C++
345 lines
12 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 <iostream>
|
|
|
|
#include <QAbstractTextDocumentLayout>
|
|
#include <QApplication>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QTextDocument>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h>
|
|
|
|
#include "FaviconCache.h"
|
|
#include "Formatter.h"
|
|
#include "Torrent.h"
|
|
#include "TrackerDelegate.h"
|
|
#include "TrackerModel.h"
|
|
#include "Utils.h"
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
namespace
|
|
{
|
|
const int mySpacing = 6;
|
|
const QSize myMargin (10, 10);
|
|
|
|
class ItemLayout
|
|
{
|
|
private:
|
|
QTextDocument myTextDocument;
|
|
|
|
public:
|
|
QRect iconRect;
|
|
QRect textRect;
|
|
|
|
public:
|
|
ItemLayout(const QString& text, bool suppressColors, Qt::LayoutDirection direction,
|
|
const QPoint& topLeft, int width);
|
|
|
|
QSize size () const
|
|
{
|
|
return (iconRect | textRect).size ();
|
|
}
|
|
|
|
QAbstractTextDocumentLayout * textLayout () const
|
|
{
|
|
return myTextDocument.documentLayout ();
|
|
}
|
|
};
|
|
|
|
ItemLayout::ItemLayout(const QString& text, bool suppressColors, Qt::LayoutDirection direction,
|
|
const QPoint& topLeft, int width)
|
|
{
|
|
const QStyle * style (qApp->style ());
|
|
const QSize iconSize = FaviconCache::getIconSize ();
|
|
|
|
QRect baseRect (topLeft, QSize (width, 0));
|
|
|
|
iconRect = style->alignedRect (direction, Qt::AlignLeft | Qt::AlignTop, iconSize, baseRect);
|
|
Utils::narrowRect (baseRect, iconSize.width () + mySpacing, 0, direction);
|
|
|
|
myTextDocument.setDocumentMargin (0);
|
|
myTextDocument.setTextWidth (baseRect.width ());
|
|
QTextOption textOption;
|
|
textOption.setTextDirection (direction);
|
|
if (suppressColors)
|
|
textOption.setFlags (QTextOption::SuppressColors);
|
|
myTextDocument.setDefaultTextOption (textOption);
|
|
myTextDocument.setHtml (text);
|
|
|
|
textRect = baseRect;
|
|
textRect.setSize (myTextDocument.size ().toSize ());
|
|
}
|
|
}
|
|
|
|
QSize
|
|
TrackerDelegate::margin (const QStyle& style) const
|
|
{
|
|
Q_UNUSED (style);
|
|
|
|
return myMargin;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
QSize
|
|
TrackerDelegate::sizeHint (const QStyleOptionViewItem & option,
|
|
const TrackerInfo & info) const
|
|
{
|
|
const ItemLayout layout (getText (info), true, option.direction, QPoint (0, 0), option.rect.width () - myMargin.width () * 2);
|
|
return layout.size () + myMargin * 2;
|
|
}
|
|
|
|
QSize
|
|
TrackerDelegate::sizeHint (const QStyleOptionViewItem & option,
|
|
const QModelIndex & index) const
|
|
{
|
|
const TrackerInfo trackerInfo = index.data (TrackerModel::TrackerRole).value<TrackerInfo>();
|
|
return sizeHint (option, trackerInfo);
|
|
}
|
|
|
|
void
|
|
TrackerDelegate::paint (QPainter * painter,
|
|
const QStyleOptionViewItem & option,
|
|
const QModelIndex & index) const
|
|
{
|
|
const TrackerInfo trackerInfo = index.data (TrackerModel::TrackerRole).value<TrackerInfo>();
|
|
painter->save();
|
|
painter->setClipRect (option.rect);
|
|
drawBackground (painter, option, index);
|
|
drawTracker (painter, option, trackerInfo);
|
|
drawFocus(painter, option, option.rect);
|
|
painter->restore();
|
|
}
|
|
|
|
void
|
|
TrackerDelegate::drawTracker (QPainter * painter,
|
|
const QStyleOptionViewItem & option,
|
|
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());
|
|
|
|
const QRect contentRect (option.rect.adjusted (myMargin.width (), myMargin.height (), -myMargin.width (), -myMargin.height ()));
|
|
const ItemLayout layout (getText (inf), isItemSelected, option.direction, contentRect.topLeft (), contentRect.width ());
|
|
|
|
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;
|
|
paintContext.clip = layout.textRect.translated (-layout.textRect.topLeft ());
|
|
paintContext.palette.setColor (QPalette::Text, option.palette.color (isItemSelected ? QPalette::HighlightedText : QPalette::Text));
|
|
painter->translate (layout.textRect.topLeft());
|
|
layout.textLayout ()->draw (painter, paintContext);
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
void
|
|
TrackerDelegate::setShowMore (bool b)
|
|
{
|
|
myShowMore = b;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
QString timeToStringRounded (int seconds)
|
|
{
|
|
if (seconds > 60)
|
|
seconds -= (seconds % 60);
|
|
|
|
return Formatter::timeToString (seconds);
|
|
}
|
|
}
|
|
|
|
QString
|
|
TrackerDelegate::getText (const TrackerInfo& inf) const
|
|
{
|
|
QString key;
|
|
QString str;
|
|
const time_t now (time (0));
|
|
const QString err_markup_begin = QLatin1String ("<span style=\"color:red\">");
|
|
const QString err_markup_end = QLatin1String ("</span>");
|
|
const QString timeout_markup_begin = QLatin1String ("<span style=\"color:#224466\">");
|
|
const QString timeout_markup_end = QLatin1String ("</span>");
|
|
const QString success_markup_begin = QLatin1String ("<span style=\"color:#008B00\">");
|
|
const QString success_markup_end = QLatin1String ("</span>");
|
|
|
|
// hostname
|
|
str += inf.st.isBackup ? QLatin1String ("<i>") : QLatin1String ("<b>");
|
|
char * host = NULL;
|
|
int port = 0;
|
|
tr_urlParse (inf.st.announce.toUtf8().constData(), -1, NULL, &host, &port, NULL);
|
|
str += QString::fromLatin1 ("%1:%2").arg (QString::fromUtf8 (host)).arg (port);
|
|
tr_free (host);
|
|
if (!key.isEmpty()) str += QLatin1String (" - ") + key;
|
|
str += inf.st.isBackup ? QLatin1String ("</i>") : QLatin1String ("</b>");
|
|
|
|
// announce & scrape info
|
|
if (!inf.st.isBackup)
|
|
{
|
|
if (inf.st.hasAnnounced && inf.st.announceState != TR_TRACKER_INACTIVE)
|
|
{
|
|
const QString tstr (timeToStringRounded (now - inf.st.lastAnnounceTime));
|
|
str += QLatin1String ("<br/>\n");
|
|
if (inf.st.lastAnnounceSucceeded)
|
|
{
|
|
//: %1 and %2 are replaced with HTML markup, %3 is duration
|
|
str += tr ("Got a list of%1 %Ln peer(s)%2 %3 ago", 0, inf.st.lastAnnouncePeerCount)
|
|
.arg (success_markup_begin)
|
|
.arg (success_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
else if (inf.st.lastAnnounceTimedOut)
|
|
{
|
|
//: %1 and %2 are replaced with HTML markup, %3 is duration
|
|
str += tr ("Peer list request %1timed out%2 %3 ago; will retry")
|
|
.arg (timeout_markup_begin)
|
|
.arg (timeout_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
else
|
|
{
|
|
//: %1 and %3 are replaced with HTML markup, %2 is error message, %4 is duration
|
|
str += tr ("Got an error %1\"%2\"%3 %4 ago")
|
|
.arg (err_markup_begin)
|
|
.arg (inf.st.lastAnnounceResult)
|
|
.arg (err_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
}
|
|
|
|
switch (inf.st.announceState)
|
|
{
|
|
case TR_TRACKER_INACTIVE:
|
|
str += QLatin1String ("<br/>\n");
|
|
str += tr ("No updates scheduled");
|
|
break;
|
|
|
|
case TR_TRACKER_WAITING:
|
|
{
|
|
const QString tstr (timeToStringRounded (inf.st.nextAnnounceTime - now));
|
|
str += QLatin1String ("<br/>\n");
|
|
//: %1 is duration
|
|
str += tr ("Asking for more peers in %1").arg (tstr);
|
|
break;
|
|
}
|
|
|
|
case TR_TRACKER_QUEUED:
|
|
str += QLatin1String ("<br/>\n");
|
|
str += tr ("Queued to ask for more peers");
|
|
break;
|
|
|
|
case TR_TRACKER_ACTIVE: {
|
|
const QString tstr (timeToStringRounded (now - inf.st.lastAnnounceStartTime));
|
|
str += QLatin1String ("<br/>\n");
|
|
//: %1 is duration
|
|
str += tr ("Asking for more peers now... <small>%1</small>").arg (tstr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (myShowMore)
|
|
{
|
|
if (inf.st.hasScraped)
|
|
{
|
|
str += QLatin1String ("<br/>\n");
|
|
const QString tstr (timeToStringRounded (now - inf.st.lastScrapeTime));
|
|
if (inf.st.lastScrapeSucceeded)
|
|
{
|
|
if (inf.st.seederCount >= 0 && inf.st.leecherCount >= 0)
|
|
{
|
|
//: First part of phrase "Tracker had ... seeder(s) and ... leecher(s) ... ago";
|
|
//: %1 and %2 are replaced with HTML markup
|
|
str += tr ("Tracker had%1 %Ln seeder(s)%2", 0, inf.st.seederCount)
|
|
.arg (success_markup_begin)
|
|
.arg (success_markup_end);
|
|
//: Second part of phrase "Tracker had ... seeder(s) and ... leecher(s) ... ago";
|
|
//: %1 and %2 are replaced with HTML markup, %3 is duration;
|
|
//: notice that leading space (before "and") is included here
|
|
str += tr (" and%1 %Ln leecher(s)%2 %3 ago", 0, inf.st.leecherCount)
|
|
.arg (success_markup_begin)
|
|
.arg (success_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
else
|
|
{
|
|
//: %1 and %2 are replaced with HTML markup, %3 is duration
|
|
str += tr ("Tracker had %1no information%2 on peer counts %3 ago")
|
|
.arg (success_markup_begin)
|
|
.arg (success_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//: %1 and %3 are replaced with HTML markup, %2 is error message, %4 is duration
|
|
str += tr ("Got a scrape error %1\"%2\"%3 %4 ago")
|
|
.arg (err_markup_begin)
|
|
.arg (inf.st.lastScrapeResult)
|
|
.arg (err_markup_end)
|
|
.arg (tstr);
|
|
}
|
|
}
|
|
|
|
switch (inf.st.scrapeState)
|
|
{
|
|
case TR_TRACKER_INACTIVE:
|
|
break;
|
|
|
|
case TR_TRACKER_WAITING:
|
|
{
|
|
str += QLatin1String ("<br/>\n");
|
|
const QString tstr (timeToStringRounded (inf.st.nextScrapeTime - now));
|
|
//: %1 is duration
|
|
str += tr ("Asking for peer counts in %1").arg (tstr);
|
|
break;
|
|
}
|
|
|
|
case TR_TRACKER_QUEUED:
|
|
{
|
|
str += QLatin1String ("<br/>\n");
|
|
str += tr ("Queued to ask for peer counts");
|
|
break;
|
|
}
|
|
|
|
case TR_TRACKER_ACTIVE:
|
|
{
|
|
str += QLatin1String ("<br/>\n");
|
|
const QString tstr (timeToStringRounded (now - inf.st.lastScrapeStartTime));
|
|
//: %1 is duration
|
|
str += tr ("Asking for peer counts now... <small>%1</small>").arg (tstr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return str;
|
|
}
|