source: trunk/qt/TorrentDelegate.cc @ 14537

Last change on this file since 14537 was 14537, checked in by mikedld, 6 years ago

Use PascalCase? for Qt client filenames

Split FileTree?.{h,cc} and FilterBar?.{h,cc} files so that each class
is in its own file.

This breaks translations (some classes got renamed => context changed),
to be fixed by next commit (along with Tx sync).

  • Property svn:keywords set to Date Rev Author Id
File size: 18.1 KB
Line 
1/*
2 * This file Copyright (C) 2009-2015 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: TorrentDelegate.cc 14537 2015-06-10 21:27:11Z mikedld $
8 */
9
10
11#include <iostream>
12
13#include <QApplication>
14#include <QFont>
15#include <QFontMetrics>
16#include <QIcon>
17#include <QModelIndex>
18#include <QPainter>
19#include <QPixmap>
20#include <QPixmapCache>
21#include <QStyleOptionProgressBar>
22
23#include "Formatter.h"
24#include "Torrent.h"
25#include "TorrentDelegate.h"
26#include "TorrentModel.h"
27#include "Utils.h"
28
29enum
30{
31  GUI_PAD = 6,
32  BAR_HEIGHT = 12
33};
34
35QColor TorrentDelegate::greenBrush;
36QColor TorrentDelegate::blueBrush;
37QColor TorrentDelegate::silverBrush;
38QColor TorrentDelegate::greenBack;
39QColor TorrentDelegate::blueBack;
40QColor TorrentDelegate::silverBack;
41
42namespace
43{
44  class ItemLayout
45  {
46    private:
47      QString myNameText;
48      QString myStatusText;
49      QString myProgressText;
50
51    public:
52      QFont nameFont;
53      QFont statusFont;
54      QFont progressFont;
55
56      QRect iconRect;
57      QRect emblemRect;
58      QRect nameRect;
59      QRect statusRect;
60      QRect barRect;
61      QRect progressRect;
62
63    public:
64      ItemLayout(const QString& nameText, const QString& statusText, const QString& progressText,
65                 const QIcon& emblemIcon, const QFont& baseFont, Qt::LayoutDirection direction,
66                 const QPoint& topLeft, int width);
67
68      QSize size () const
69      {
70        return (iconRect | nameRect | statusRect | barRect | progressRect).size ();
71      }
72
73      QString nameText () const { return elidedText (nameFont, myNameText, nameRect.width ()); }
74      QString statusText () const { return elidedText (statusFont, myStatusText, statusRect.width ()); }
75      QString progressText () const  { return elidedText (progressFont, myProgressText, progressRect.width ()); }
76
77    private:
78      QString elidedText (const QFont& font, const QString& text, int width) const
79      {
80        return QFontMetrics (font).elidedText (text, Qt::ElideRight, width);
81      }
82  };
83
84  ItemLayout::ItemLayout(const QString& nameText, const QString& statusText, const QString& progressText,
85                         const QIcon& emblemIcon, const QFont& baseFont, Qt::LayoutDirection direction,
86                         const QPoint& topLeft, int width):
87    myNameText (nameText),
88    myStatusText (statusText),
89    myProgressText (progressText),
90    nameFont (baseFont),
91    statusFont (baseFont),
92    progressFont (baseFont)
93  {
94    const QStyle * style (qApp->style ());
95    const int iconSize (style->pixelMetric (QStyle::PM_LargeIconSize));
96
97    nameFont.setWeight (QFont::Bold);
98    const QFontMetrics nameFM (nameFont);
99    const QSize nameSize (nameFM.size (0, myNameText));
100
101    statusFont.setPointSize (static_cast<int> (statusFont.pointSize () * 0.9));
102    const QFontMetrics statusFM (statusFont);
103    const QSize statusSize (statusFM.size (0, myStatusText));
104
105    progressFont.setPointSize (static_cast<int> (progressFont.pointSize () * 0.9));
106    const QFontMetrics progressFM (progressFont);
107    const QSize progressSize (progressFM.size (0, myProgressText));
108
109    QRect baseRect (topLeft, QSize (width, 0));
110    Utils::narrowRect (baseRect, iconSize + GUI_PAD, 0, direction);
111
112    nameRect = baseRect.adjusted(0, 0, 0, nameSize.height ());
113    statusRect = nameRect.adjusted(0, nameRect.height () + 1, 0, statusSize.height () + 1);
114    barRect = statusRect.adjusted(0, statusRect.height () + 1, 0, BAR_HEIGHT + 1);
115    progressRect = barRect.adjusted (0, barRect.height () + 1, 0, progressSize.height () + 1);
116    iconRect = style->alignedRect (direction, Qt::AlignLeft | Qt::AlignVCenter,
117                                   QSize (iconSize, iconSize),
118                                   QRect (topLeft, QSize (width, progressRect.bottom () - nameRect.top ())));
119    emblemRect = style->alignedRect (direction, Qt::AlignRight | Qt::AlignBottom,
120                                     emblemIcon.actualSize (iconRect.size () / 2, QIcon::Normal, QIcon::On),
121                                     iconRect);
122  }
123}
124
125TorrentDelegate::TorrentDelegate (QObject * parent):
126  QStyledItemDelegate (parent),
127  myProgressBarStyle (new QStyleOptionProgressBar)
128{
129  myProgressBarStyle->minimum = 0;
130  myProgressBarStyle->maximum = 1000;
131
132  greenBrush = QColor ("forestgreen");
133  greenBack = QColor ("darkseagreen");
134
135  blueBrush = QColor ("steelblue");
136  blueBack = QColor ("lightgrey");
137
138  silverBrush = QColor ("silver");
139  silverBack = QColor ("grey");
140}
141
142TorrentDelegate::~TorrentDelegate ()
143{
144  delete myProgressBarStyle;
145}
146
147/***
148****
149***/
150
151QSize
152TorrentDelegate::margin (const QStyle& style) const
153{
154  Q_UNUSED (style);
155
156  return QSize (4, 4);
157}
158
159QString
160TorrentDelegate::progressString (const Torrent& tor) const
161{
162  const bool isMagnet (!tor.hasMetadata());
163  const bool isDone (tor.isDone ());
164  const bool isSeed (tor.isSeed ());
165  const uint64_t haveTotal (tor.haveTotal());
166  QString str;
167  double seedRatio;
168  const bool hasSeedRatio (tor.getSeedRatio (seedRatio));
169
170  if (isMagnet) // magnet link with no metadata
171    {
172      //: First part of torrent progress string;
173      //: %1 is the percentage of torrent metadata downloaded
174      str = tr ("Magnetized transfer - retrieving metadata (%1%)")
175            .arg (Formatter::percentToString (tor.metadataPercentDone() * 100.0));
176    }
177  else if (!isDone) // downloading
178    {
179      //: First part of torrent progress string;
180      //: %1 is how much we've got,
181      //: %2 is how much we'll have when done,
182      //: %3 is a percentage of the two
183      str = tr ("%1 of %2 (%3%)")
184            .arg (Formatter::sizeToString (haveTotal))
185            .arg (Formatter::sizeToString (tor.sizeWhenDone()))
186            .arg (Formatter::percentToString (tor.percentDone() * 100.0));
187    }
188  else if (!isSeed) // partial seed
189    {
190      if (hasSeedRatio)
191        {
192          //: First part of torrent progress string;
193          //: %1 is how much we've got,
194          //: %2 is the torrent's total size,
195          //: %3 is a percentage of the two,
196          //: %4 is how much we've uploaded,
197          //: %5 is our upload-to-download ratio,
198          //: %6 is the ratio we want to reach before we stop uploading
199          str = tr ("%1 of %2 (%3%), uploaded %4 (Ratio: %5 Goal: %6)")
200                .arg (Formatter::sizeToString (haveTotal))
201                .arg (Formatter::sizeToString (tor.totalSize()))
202                .arg (Formatter::percentToString (tor.percentComplete() * 100.0))
203                .arg (Formatter::sizeToString (tor.uploadedEver()))
204                .arg (Formatter::ratioToString (tor.ratio()))
205                .arg (Formatter::ratioToString (seedRatio));
206        }
207        else
208        {
209            //: First part of torrent progress string;
210            //: %1 is how much we've got,
211            //: %2 is the torrent's total size,
212            //: %3 is a percentage of the two,
213            //: %4 is how much we've uploaded,
214            //: %5 is our upload-to-download ratio
215            str = tr ("%1 of %2 (%3%), uploaded %4 (Ratio: %5)")
216                  .arg (Formatter::sizeToString (haveTotal))
217                  .arg (Formatter::sizeToString (tor.totalSize()))
218                  .arg (Formatter::percentToString (tor.percentComplete() * 100.0))
219                  .arg (Formatter::sizeToString (tor.uploadedEver()))
220                  .arg (Formatter::ratioToString (tor.ratio()));
221        }
222    }
223  else // seeding
224    {
225      if (hasSeedRatio)
226        {
227          //: First part of torrent progress string;
228          //: %1 is the torrent's total size,
229          //: %2 is how much we've uploaded,
230          //: %3 is our upload-to-download ratio,
231          //: %4 is the ratio we want to reach before we stop uploading
232          str = tr ("%1, uploaded %2 (Ratio: %3 Goal: %4)")
233                .arg (Formatter::sizeToString (haveTotal))
234                .arg (Formatter::sizeToString (tor.uploadedEver()))
235                .arg (Formatter::ratioToString (tor.ratio()))
236                .arg (Formatter::ratioToString (seedRatio));
237        }
238      else // seeding w/o a ratio
239        {
240          //: First part of torrent progress string;
241          //: %1 is the torrent's total size,
242          //: %2 is how much we've uploaded,
243          //: %3 is our upload-to-download ratio
244          str = tr ("%1, uploaded %2 (Ratio: %3)")
245                .arg (Formatter::sizeToString (haveTotal))
246                .arg (Formatter::sizeToString (tor.uploadedEver()))
247                .arg (Formatter::ratioToString (tor.ratio()));
248        }
249    }
250
251  // add time when downloading
252  if ((hasSeedRatio && tor.isSeeding()) || tor.isDownloading())
253    {
254      if (tor.hasETA ())
255        //: Second (optional) part of torrent progress string;
256        //: %1 is duration;
257        //: notice that leading space (before the dash) is included here
258        str += tr (" - %1 left").arg (Formatter::timeToString (tor.getETA ()));
259      else
260        //: Second (optional) part of torrent progress string;
261        //: notice that leading space (before the dash) is included here
262        str += tr (" - Remaining time unknown");
263    }
264
265    return str.trimmed ();
266}
267
268QString
269TorrentDelegate::shortTransferString (const Torrent& tor) const
270{
271  QString str;
272  const bool haveMeta (tor.hasMetadata());
273  const bool haveDown (haveMeta && ((tor.webseedsWeAreDownloadingFrom()>0) || (tor.peersWeAreDownloadingFrom()>0)));
274  const bool haveUp (haveMeta && tor.peersWeAreUploadingTo()>0);
275
276  if (haveDown)
277    str = Formatter::downloadSpeedToString(tor.downloadSpeed()) +
278          QLatin1String ("   ") +
279          Formatter::uploadSpeedToString(tor.uploadSpeed());
280
281  else if (haveUp)
282    str = Formatter::uploadSpeedToString(tor.uploadSpeed());
283
284  return str.trimmed ();
285}
286
287QString
288TorrentDelegate::shortStatusString (const Torrent& tor) const
289{
290  QString str;
291  static const QChar ratioSymbol (0x262F);
292
293  switch (tor.getActivity ())
294    {
295      case TR_STATUS_CHECK:
296        str = tr ("Verifying local data (%1% tested)").arg (Formatter::percentToString (tor.getVerifyProgress()*100.0));
297        break;
298
299      case TR_STATUS_DOWNLOAD:
300      case TR_STATUS_SEED:
301        str = shortTransferString(tor) +
302              QLatin1String ("    ") +
303              tr("Ratio: %1").arg(Formatter::ratioToString(tor.ratio()));
304        break;
305
306      default:
307        str = tor.activityString ();
308        break;
309    }
310
311  return str.trimmed ();
312}
313
314QString
315TorrentDelegate::statusString (const Torrent& tor) const
316{
317  QString str;
318
319  if (tor.hasError ())
320    {
321      str = tor.getError ();
322    }
323  else switch (tor.getActivity ())
324    {
325      case TR_STATUS_STOPPED:
326      case TR_STATUS_CHECK_WAIT:
327      case TR_STATUS_CHECK:
328      case TR_STATUS_DOWNLOAD_WAIT:
329      case TR_STATUS_SEED_WAIT:
330        str = shortStatusString (tor);
331        break;
332
333      case TR_STATUS_DOWNLOAD:
334        if (!tor.hasMetadata())
335          {
336            str = tr ("Downloading metadata from %Ln peer(s) (%1% done)", 0, tor.peersWeAreDownloadingFrom ())
337                  .arg (Formatter::percentToString (100.0 * tor.metadataPercentDone ()));
338          }
339        else
340          {
341            /* it would be nicer for translation if this was all one string, but I don't see how to do multiple %n's in tr() */
342            if (tor.connectedPeersAndWebseeds () == 0)
343              //: First part of phrase "Downloading from ... peer(s) and ... web seed(s)"
344              str = tr ("Downloading from %Ln peer(s)", 0, tor.peersWeAreDownloadingFrom ());
345            else
346              //: First part of phrase "Downloading from ... of ... connected peer(s) and ... web seed(s)"
347              str = tr ("Downloading from %1 of %Ln connected peer(s)", 0, tor.connectedPeersAndWebseeds ())
348                    .arg (tor.peersWeAreDownloadingFrom ());
349
350            if (tor.webseedsWeAreDownloadingFrom())
351              //: Second (optional) part of phrase "Downloading from ... of ... connected peer(s) and ... web seed(s)";
352              //: notice that leading space (before "and") is included here
353              str += tr(" and %Ln web seed(s)", 0, tor.webseedsWeAreDownloadingFrom());
354          }
355        break;
356
357      case TR_STATUS_SEED:
358        if (tor.connectedPeers () == 0)
359          str = tr ("Seeding to %Ln peer(s)", 0, tor.peersWeAreUploadingTo ());
360        else
361          str = tr ("Seeding to %1 of %Ln connected peer(s)", 0, tor.connectedPeers ())
362                .arg (tor.peersWeAreUploadingTo ());
363        break;
364
365      default:
366        str = tr ("Error");
367        break;
368    }
369
370  if (tor.isReadyToTransfer ())
371    {
372      QString s = shortTransferString (tor);
373      if (!s.isEmpty ())
374        str += tr (" - ") + s;
375    }
376
377  return str.trimmed ();
378}
379
380/***
381****
382***/
383
384QSize
385TorrentDelegate::sizeHint (const QStyleOptionViewItem& option, const Torrent& tor) const
386{
387  const QSize m (margin (*qApp->style ()));
388  const ItemLayout layout (tor.name (), progressString (tor), statusString (tor), QIcon (),
389                           option.font, option.direction, QPoint (0, 0), option.rect.width () - m.width () * 2);
390  return layout.size () + m * 2;
391}
392
393QSize
394TorrentDelegate::sizeHint (const QStyleOptionViewItem  & option,
395                           const QModelIndex           & index) const
396{
397  const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*>());
398  return sizeHint (option, *tor);
399}
400
401void
402TorrentDelegate::paint (QPainter                    * painter,
403                        const QStyleOptionViewItem  & option,
404                        const QModelIndex           & index) const
405{
406  const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*>());
407  painter->save ();
408  painter->setClipRect (option.rect);
409  drawTorrent (painter, option, *tor);
410  painter->restore ();
411}
412
413void
414TorrentDelegate::setProgressBarPercentDone (const QStyleOptionViewItem & option,
415                                            const Torrent              & tor) const
416{
417  double seedRatioLimit;
418  if (tor.isSeeding() && tor.getSeedRatio(seedRatioLimit))
419    {
420      const double seedRateRatio = tor.ratio() / seedRatioLimit;
421      const int scaledProgress = seedRateRatio * (myProgressBarStyle->maximum - myProgressBarStyle->minimum);
422      myProgressBarStyle->progress = myProgressBarStyle->minimum + scaledProgress;
423    }
424  else
425    {
426      const bool isMagnet (!tor.hasMetadata ());
427      myProgressBarStyle->direction = option.direction;
428      myProgressBarStyle->progress = static_cast<int> (myProgressBarStyle->minimum + (((isMagnet ? tor.metadataPercentDone() : tor.percentDone()) * (myProgressBarStyle->maximum - myProgressBarStyle->minimum))));
429    }
430}
431
432void
433TorrentDelegate::drawTorrent (QPainter                   * painter,
434                              const QStyleOptionViewItem & option,
435                              const Torrent              & tor) const
436{
437  const QStyle * style (qApp->style ());
438
439  const bool isPaused (tor.isPaused ());
440
441  const bool isItemSelected ((option.state & QStyle::State_Selected) != 0);
442  const bool isItemEnabled ((option.state & QStyle::State_Enabled) != 0);
443  const bool isItemActive ((option.state & QStyle::State_Active) != 0);
444
445  painter->save ();
446
447  if (isItemSelected)
448    {
449      QPalette::ColorGroup cg = isItemEnabled ? QPalette::Normal : QPalette::Disabled;
450      if (cg == QPalette::Normal && !isItemActive)
451        cg = QPalette::Inactive;
452
453      painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight));
454    }
455
456  QIcon::Mode im;
457  if (isPaused || !isItemEnabled)
458    im = QIcon::Disabled;
459  else if (isItemSelected)
460    im = QIcon::Selected;
461  else
462    im = QIcon::Normal;
463
464  QIcon::State qs;
465  if (isPaused)
466    qs = QIcon::Off;
467  else
468    qs = QIcon::On;
469
470  QPalette::ColorGroup cg = QPalette::Normal;
471  if (isPaused || !isItemEnabled)
472    cg = QPalette::Disabled;
473  if (cg == QPalette::Normal && !isItemActive)
474    cg = QPalette::Inactive;
475
476  QPalette::ColorRole cr;
477  if (isItemSelected)
478    cr = QPalette::HighlightedText;
479  else
480    cr = QPalette::Text;
481
482  QStyle::State progressBarState (option.state);
483  if (isPaused)
484    progressBarState = QStyle::State_None;
485  progressBarState |= QStyle::State_Small;
486
487  const QIcon::Mode emblemIm = isItemSelected ? QIcon::Selected : QIcon::Normal;
488  const QIcon emblemIcon = tor.hasError () ? QIcon::fromTheme (QLatin1String ("emblem-important"), style->standardIcon (QStyle::SP_MessageBoxWarning)) : QIcon ();
489
490  // layout
491  const QSize m (margin (*style));
492  const QRect contentRect (option.rect.adjusted (m.width(), m.height(), -m.width(), -m.height()));
493  const ItemLayout layout (tor.name (), progressString (tor), statusString (tor), emblemIcon,
494                           option.font, option.direction, contentRect.topLeft (), contentRect.width ());
495
496  // render
497  if (tor.hasError () && !isItemSelected)
498    painter->setPen (QColor ("red"));
499  else
500    painter->setPen (option.palette.color (cg, cr));
501  tor.getMimeTypeIcon().paint (painter, layout.iconRect, Qt::AlignCenter, im, qs);
502  if (!emblemIcon.isNull ())
503    emblemIcon.paint (painter, layout.emblemRect, Qt::AlignCenter, emblemIm, qs);
504  painter->setFont (layout.nameFont);
505  painter->drawText (layout.nameRect, Qt::AlignLeft | Qt::AlignVCenter, layout.nameText ());
506  painter->setFont (layout.statusFont);
507  painter->drawText (layout.statusRect, Qt::AlignLeft | Qt::AlignVCenter, layout.statusText ());
508  painter->setFont (layout.progressFont);
509  painter->drawText (layout.progressRect, Qt::AlignLeft | Qt::AlignVCenter, layout.progressText ());
510  myProgressBarStyle->rect = layout.barRect;
511  if (tor.isDownloading())
512    {
513      myProgressBarStyle->palette.setBrush (QPalette::Highlight, blueBrush);
514      myProgressBarStyle->palette.setColor (QPalette::Base, blueBack);
515      myProgressBarStyle->palette.setColor (QPalette::Window, blueBack);
516    }
517  else if (tor.isSeeding())
518    {
519      myProgressBarStyle->palette.setBrush (QPalette::Highlight, greenBrush);
520      myProgressBarStyle->palette.setColor (QPalette::Base, greenBack);
521      myProgressBarStyle->palette.setColor (QPalette::Window, greenBack);
522    }
523  else
524    {
525      myProgressBarStyle->palette.setBrush (QPalette::Highlight, silverBrush);
526      myProgressBarStyle->palette.setColor (QPalette::Base, silverBack);
527      myProgressBarStyle->palette.setColor (QPalette::Window, silverBack);
528    }
529  myProgressBarStyle->state = progressBarState;
530  setProgressBarPercentDone (option, tor);
531
532  style->drawControl (QStyle::CE_ProgressBar, myProgressBarStyle, painter);
533
534  painter->restore ();
535}
Note: See TracBrowser for help on using the repository browser.