source: trunk/qt/mainwin.cc @ 14386

Last change on this file since 14386 was 14386, checked in by mikedld, 8 years ago

Do not use printf-style formatting in C++ code

  • Property svn:keywords set to Date Rev Author Id
File size: 47.2 KB
Line 
1/*
2 * This file Copyright (C) 2009-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU Public License v2 or v3 licenses,
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 * $Id: mainwin.cc 14386 2014-12-14 15:34:31Z mikedld $
8 */
9
10#include <cassert>
11#include <iostream>
12
13#include <QtGui>
14#include <QCheckBox>
15#include <QIcon>
16#include <QProxyStyle>
17#include <QLabel>
18#include <QFileDialog>
19#include <QMessageBox>
20
21#include <libtransmission/transmission.h>
22#include <libtransmission/version.h>
23
24#include "about.h"
25#include "add-data.h"
26#include "app.h"
27#include "details.h"
28#include "filterbar.h"
29#include "filters.h"
30#include "formatter.h"
31#include "hig.h"
32#include "mainwin.h"
33#include "make-dialog.h"
34#include "options.h"
35#include "prefs.h"
36#include "prefs-dialog.h"
37#include "relocate.h"
38#include "session.h"
39#include "session-dialog.h"
40#include "speed.h"
41#include "stats-dialog.h"
42#include "torrent-delegate.h"
43#include "torrent-delegate-min.h"
44#include "torrent-filter.h"
45#include "torrent-model.h"
46#include "triconpushbutton.h"
47#include "ui_mainwin.h"
48
49#define PREFS_KEY "prefs-key";
50
51
52/**
53 * This is a proxy-style for that forces it to be always disabled.
54 * We use this to make our torrent list view behave consistently on
55 * both GTK and Qt implementations.
56 */
57class ListViewProxyStyle: public QProxyStyle
58{
59  public:
60
61    int styleHint (StyleHint            hint,
62                   const QStyleOption * option = 0,
63                   const QWidget      * widget = 0,
64                   QStyleHintReturn   * returnData = 0) const
65    {
66      if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
67        return 0;
68      return QProxyStyle::styleHint (hint, option, widget, returnData);
69    }
70};
71
72
73QIcon
74TrMainWindow::getStockIcon (const QString& name, int fallback)
75{
76  QIcon icon = QIcon::fromTheme (name);
77
78  if (icon.isNull () && (fallback >= 0))
79    icon = style ()->standardIcon (QStyle::StandardPixmap (fallback), 0, this);
80
81  return icon;
82}
83
84TrMainWindow::TrMainWindow (Session& session, Prefs& prefs, TorrentModel& model, bool minimized):
85  myLastFullUpdateTime (0),
86  mySessionDialog (new SessionDialog (session, prefs, this)),
87  myPrefsDialog (0),
88  myAboutDialog (new AboutDialog (this)),
89  myStatsDialog (new StatsDialog (session, this)),
90  myDetailsDialog (0),
91  myFilterModel (prefs),
92  myTorrentDelegate (new TorrentDelegate (this)),
93  myTorrentDelegateMin (new TorrentDelegateMin (this)),
94  mySession (session),
95  myPrefs (prefs),
96  myModel (model),
97  mySpeedModeOffIcon (":/icons/alt-limit-off.png"),
98  mySpeedModeOnIcon (":/icons/alt-limit-on.png"),
99  myLastSendTime (0),
100  myLastReadTime (0),
101  myNetworkTimer (this),
102  myNetworkError (false),
103  myRefreshTrayIconTimer (this),
104  myRefreshActionSensitivityTimer (this)
105{
106  setAcceptDrops (true);
107
108  QAction * sep = new QAction (this);
109  sep->setSeparator (true);
110
111  ui.setupUi (this);
112
113  QStyle * style = this->style ();
114
115  int i = style->pixelMetric (QStyle::PM_SmallIconSize, 0, this);
116  const QSize smallIconSize (i, i);
117
118  ui.listView->setStyle (new ListViewProxyStyle);
119
120  // icons
121  ui.action_OpenFile->setIcon (getStockIcon ("folder-open", QStyle::SP_DialogOpenButton));
122  ui.action_New->setIcon (getStockIcon ("document-new", QStyle::SP_DesktopIcon));
123  ui.action_Properties->setIcon (getStockIcon ("document-properties", QStyle::SP_DesktopIcon));
124  ui.action_OpenFolder->setIcon (getStockIcon ("folder-open", QStyle::SP_DirOpenIcon));
125  ui.action_Start->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
126  ui.action_StartNow->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
127  ui.action_Announce->setIcon (getStockIcon ("network-transmit-receive"));
128  ui.action_Pause->setIcon (getStockIcon ("media-playback-pause", QStyle::SP_MediaPause));
129  ui.action_Remove->setIcon (getStockIcon ("list-remove", QStyle::SP_TrashIcon));
130  ui.action_Delete->setIcon (getStockIcon ("edit-delete", QStyle::SP_TrashIcon));
131  ui.action_StartAll->setIcon (getStockIcon ("media-playback-start", QStyle::SP_MediaPlay));
132  ui.action_PauseAll->setIcon (getStockIcon ("media-playback-pause", QStyle::SP_MediaPause));
133  ui.action_Quit->setIcon (getStockIcon ("application-exit"));
134  ui.action_SelectAll->setIcon (getStockIcon ("edit-select-all"));
135  ui.action_ReverseSortOrder->setIcon (getStockIcon ("view-sort-ascending", QStyle::SP_ArrowDown));
136  ui.action_Preferences->setIcon (getStockIcon ("preferences-system"));
137  ui.action_Contents->setIcon (getStockIcon ("help-contents", QStyle::SP_DialogHelpButton));
138  ui.action_About->setIcon (getStockIcon ("help-about"));
139  ui.action_QueueMoveTop->setIcon (getStockIcon ("go-top"));
140  ui.action_QueueMoveUp->setIcon (getStockIcon ("go-up", QStyle::SP_ArrowUp));
141  ui.action_QueueMoveDown->setIcon (getStockIcon ("go-down", QStyle::SP_ArrowDown));
142  ui.action_QueueMoveBottom->setIcon (getStockIcon ("go-bottom"));
143
144  // ui signals
145  connect (ui.action_Toolbar, SIGNAL (toggled (bool)), this, SLOT (setToolbarVisible (bool)));
146  connect (ui.action_Filterbar, SIGNAL (toggled (bool)), this, SLOT (setFilterbarVisible (bool)));
147  connect (ui.action_Statusbar, SIGNAL (toggled (bool)), this, SLOT (setStatusbarVisible (bool)));
148  connect (ui.action_CompactView, SIGNAL (toggled (bool)), this, SLOT (setCompactView (bool)));
149  connect (ui.action_SortByActivity, SIGNAL (toggled (bool)), this, SLOT (onSortByActivityToggled (bool)));
150  connect (ui.action_SortByAge,      SIGNAL (toggled (bool)), this, SLOT (onSortByAgeToggled (bool)));
151  connect (ui.action_SortByETA,      SIGNAL (toggled (bool)), this, SLOT (onSortByETAToggled (bool)));
152  connect (ui.action_SortByName,     SIGNAL (toggled (bool)), this, SLOT (onSortByNameToggled (bool)));
153  connect (ui.action_SortByProgress, SIGNAL (toggled (bool)), this, SLOT (onSortByProgressToggled (bool)));
154  connect (ui.action_SortByQueue,    SIGNAL (toggled (bool)), this, SLOT (onSortByQueueToggled (bool)));
155  connect (ui.action_SortByRatio,    SIGNAL (toggled (bool)), this, SLOT (onSortByRatioToggled (bool)));
156  connect (ui.action_SortBySize,     SIGNAL (toggled (bool)), this, SLOT (onSortBySizeToggled (bool)));
157  connect (ui.action_SortByState,    SIGNAL (toggled (bool)), this, SLOT (onSortByStateToggled (bool)));
158  connect (ui.action_ReverseSortOrder, SIGNAL (toggled (bool)), this, SLOT (setSortAscendingPref (bool)));
159  connect (ui.action_Start, SIGNAL (triggered ()), this, SLOT (startSelected ()));
160  connect (ui.action_QueueMoveTop,    SIGNAL (triggered ()), this, SLOT (queueMoveTop ()));
161  connect (ui.action_QueueMoveUp,     SIGNAL (triggered ()), this, SLOT (queueMoveUp ()));
162  connect (ui.action_QueueMoveDown,   SIGNAL (triggered ()), this, SLOT (queueMoveDown ()));
163  connect (ui.action_QueueMoveBottom, SIGNAL (triggered ()), this, SLOT (queueMoveBottom ()));
164  connect (ui.action_StartNow, SIGNAL (triggered ()), this, SLOT (startSelectedNow ()));
165  connect (ui.action_Pause, SIGNAL (triggered ()), this, SLOT (pauseSelected ()));
166  connect (ui.action_Remove, SIGNAL (triggered ()), this, SLOT (removeSelected ()));
167  connect (ui.action_Delete, SIGNAL (triggered ()), this, SLOT (deleteSelected ()));
168  connect (ui.action_Verify, SIGNAL (triggered ()), this, SLOT (verifySelected ()));
169  connect (ui.action_Announce, SIGNAL (triggered ()), this, SLOT (reannounceSelected ()));
170  connect (ui.action_StartAll, SIGNAL (triggered ()), this, SLOT (startAll ()));
171  connect (ui.action_PauseAll, SIGNAL (triggered ()), this, SLOT (pauseAll ()));
172  connect (ui.action_OpenFile, SIGNAL (triggered ()), this, SLOT (openTorrent ()));
173  connect (ui.action_AddURL, SIGNAL (triggered ()), this, SLOT (openURL ()));
174  connect (ui.action_New, SIGNAL (triggered ()), this, SLOT (newTorrent ()));
175  connect (ui.action_Preferences, SIGNAL (triggered ()), this, SLOT (openPreferences ()));
176  connect (ui.action_Statistics, SIGNAL (triggered ()), myStatsDialog, SLOT (show ()));
177  connect (ui.action_Donate, SIGNAL (triggered ()), this, SLOT (openDonate ()));
178  connect (ui.action_About, SIGNAL (triggered ()), myAboutDialog, SLOT (show ()));
179  connect (ui.action_Contents, SIGNAL (triggered ()), this, SLOT (openHelp ()));
180  connect (ui.action_OpenFolder, SIGNAL (triggered ()), this, SLOT (openFolder ()));
181  connect (ui.action_CopyMagnetToClipboard, SIGNAL (triggered ()), this, SLOT (copyMagnetLinkToClipboard ()));
182  connect (ui.action_SetLocation, SIGNAL (triggered ()), this, SLOT (setLocation ()));
183  connect (ui.action_Properties, SIGNAL (triggered ()), this, SLOT (openProperties ()));
184  connect (ui.action_SessionDialog, SIGNAL (triggered ()), mySessionDialog, SLOT (show ()));
185
186  connect (ui.listView, SIGNAL (activated (QModelIndex)), ui.action_Properties, SLOT (trigger ()));
187
188  // signals
189  connect (ui.action_SelectAll, SIGNAL (triggered ()), ui.listView, SLOT (selectAll ()));
190  connect (ui.action_DeselectAll, SIGNAL (triggered ()), ui.listView, SLOT (clearSelection ()));
191
192  connect (&myFilterModel, SIGNAL (rowsInserted (QModelIndex, int, int)), this, SLOT (refreshActionSensitivitySoon ()));
193  connect (&myFilterModel, SIGNAL (rowsRemoved (QModelIndex, int, int)), this, SLOT (refreshActionSensitivitySoon ()));
194
195  connect (ui.action_Quit, SIGNAL (triggered ()), QCoreApplication::instance (), SLOT (quit ()));
196
197  // torrent view
198  myFilterModel.setSourceModel (&myModel);
199  connect (&myModel, SIGNAL (modelReset ()), this, SLOT (onModelReset ()));
200  connect (&myModel, SIGNAL (rowsRemoved (QModelIndex, int, int)), this, SLOT (onModelReset ()));
201  connect (&myModel, SIGNAL (rowsInserted (QModelIndex, int, int)), this, SLOT (onModelReset ()));
202  connect (&myModel, SIGNAL (dataChanged (QModelIndex, QModelIndex)), this, SLOT (refreshTrayIconSoon ()));
203
204  ui.listView->setModel (&myFilterModel);
205  connect (ui.listView->selectionModel (), SIGNAL (selectionChanged (QItemSelection, QItemSelection)), this, SLOT (refreshActionSensitivitySoon ()));
206
207  QActionGroup * actionGroup = new QActionGroup (this);
208  actionGroup->addAction (ui.action_SortByActivity);
209  actionGroup->addAction (ui.action_SortByAge);
210  actionGroup->addAction (ui.action_SortByETA);
211  actionGroup->addAction (ui.action_SortByName);
212  actionGroup->addAction (ui.action_SortByProgress);
213  actionGroup->addAction (ui.action_SortByQueue);
214  actionGroup->addAction (ui.action_SortByRatio);
215  actionGroup->addAction (ui.action_SortBySize);
216  actionGroup->addAction (ui.action_SortByState);
217
218  myAltSpeedAction = new QAction (tr ("Speed Limits"), this);
219  myAltSpeedAction->setIcon (myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon);
220  connect (myAltSpeedAction, SIGNAL (triggered ()), this, SLOT (toggleSpeedMode ()));
221
222  QMenu * menu = new QMenu (this);
223  menu->addAction (ui.action_OpenFile);
224  menu->addAction (ui.action_AddURL);
225  menu->addSeparator ();
226  menu->addAction (ui.action_ShowMainWindow);
227  menu->addAction (ui.action_ShowMessageLog);
228  menu->addAction (ui.action_About);
229  menu->addSeparator ();
230  menu->addAction (ui.action_StartAll);
231  menu->addAction (ui.action_PauseAll);
232  menu->addAction (myAltSpeedAction);
233  menu->addSeparator ();
234  menu->addAction (ui.action_Quit);
235  myTrayIcon.setContextMenu (menu);
236  myTrayIcon.setIcon (QIcon::fromTheme ("transmission-tray-icon", QApplication::windowIcon ()));
237
238  connect (&myPrefs, SIGNAL (changed (int)), this, SLOT (refreshPref (int)));
239  connect (ui.action_ShowMainWindow, SIGNAL (triggered (bool)), this, SLOT (toggleWindows (bool)));
240  connect (&myTrayIcon, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
241           this, SLOT (trayActivated (QSystemTrayIcon::ActivationReason)));
242
243  toggleWindows (!minimized);
244  ui.action_TrayIcon->setChecked (minimized || prefs.getBool (Prefs::SHOW_TRAY_ICON));
245
246  ui.verticalLayout->addWidget (createStatusBar ());
247  ui.verticalLayout->insertWidget (0, myFilterBar = new FilterBar (myPrefs, myModel, myFilterModel));
248
249  QList<int> initKeys;
250  initKeys << Prefs::MAIN_WINDOW_X
251           << Prefs::SHOW_TRAY_ICON
252           << Prefs::SORT_REVERSED
253           << Prefs::SORT_MODE
254           << Prefs::FILTERBAR
255           << Prefs::STATUSBAR
256           << Prefs::STATUSBAR_STATS
257           << Prefs::TOOLBAR
258           << Prefs::ALT_SPEED_LIMIT_ENABLED
259           << Prefs::COMPACT_VIEW
260           << Prefs::DSPEED
261           << Prefs::DSPEED_ENABLED
262           << Prefs::USPEED
263           << Prefs::USPEED_ENABLED
264           << Prefs::RATIO
265           << Prefs::RATIO_ENABLED;
266  foreach (int key, initKeys)
267    refreshPref (key);
268
269  connect (&mySession, SIGNAL (sourceChanged ()), this, SLOT (onSessionSourceChanged ()));
270  connect (&mySession, SIGNAL (statsUpdated ()), this, SLOT (refreshStatusBar ()));
271  connect (&mySession, SIGNAL (dataReadProgress ()), this, SLOT (dataReadProgress ()));
272  connect (&mySession, SIGNAL (dataSendProgress ()), this, SLOT (dataSendProgress ()));
273  connect (&mySession, SIGNAL (httpAuthenticationRequired ()), this, SLOT (wrongAuthentication ()));
274  connect (&mySession, SIGNAL (error (QNetworkReply::NetworkError)), this, SLOT (onError (QNetworkReply::NetworkError)));
275  connect (&mySession, SIGNAL (errorMessage (QString)), this, SLOT (errorMessage(QString)));
276
277  if (mySession.isServer ())
278    {
279      myNetworkLabel->hide ();
280    }
281  else
282    {
283      connect (&myNetworkTimer, SIGNAL (timeout ()), this, SLOT (onNetworkTimer ()));
284      myNetworkTimer.start (1000);
285    }
286
287  connect (&myRefreshTrayIconTimer, SIGNAL (timeout ()), this, SLOT (refreshTrayIcon ()));
288  connect (&myRefreshActionSensitivityTimer, SIGNAL (timeout ()), this, SLOT (refreshActionSensitivity ()));
289
290
291  refreshActionSensitivitySoon ();
292  refreshTrayIconSoon ();
293  refreshStatusBar ();
294  refreshTitle ();
295}
296
297TrMainWindow::~TrMainWindow ()
298{
299}
300
301/****
302*****
303****/
304
305void
306TrMainWindow::onSessionSourceChanged ()
307{
308  myModel.clear ();
309}
310
311void
312TrMainWindow::onModelReset ()
313{
314  refreshTitle ();
315  refreshActionSensitivitySoon ();
316  refreshStatusBar ();
317  refreshTrayIconSoon ();
318}
319
320/****
321*****
322****/
323
324#define PREF_VARIANTS_KEY "pref-variants-list"
325
326void
327TrMainWindow::onSetPrefs ()
328{
329  const QVariantList p = sender ()->property (PREF_VARIANTS_KEY).toList ();
330  assert ( (p.size () % 2) == 0);
331  for (int i=0, n=p.size (); i<n; i+=2)
332    myPrefs.set (p[i].toInt (), p[i+1]);
333}
334
335void
336TrMainWindow::onSetPrefs (bool isChecked)
337{
338  if (isChecked)
339    onSetPrefs ();
340}
341
342#define SHOW_KEY "show-mode"
343
344QWidget *
345TrMainWindow::createStatusBar ()
346{
347  QMenu * m;
348  QLabel * l;
349  QHBoxLayout * h;
350  QPushButton * p;
351  QActionGroup * a;
352  const int i = style ()->pixelMetric (QStyle::PM_SmallIconSize, 0, this);
353  const QSize smallIconSize (i, i);
354
355  QWidget * top = myStatusBar = new QWidget;
356  h = new QHBoxLayout (top);
357  h->setContentsMargins (HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL);
358  h->setSpacing (HIG::PAD_SMALL);
359
360    p = myOptionsButton = new TrIconPushButton (this);
361    p->setIcon (QIcon (":/icons/utilities.png"));
362    p->setIconSize (QPixmap (":/icons/utilities.png").size ());
363    p->setFlat (true);
364    p->setMenu (createOptionsMenu ());
365    h->addWidget (p);
366
367    p = myAltSpeedButton = new QPushButton (this);
368    p->setIcon (myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon);
369    p->setIconSize (QPixmap (":/icons/alt-limit-on.png").size ());
370    p->setCheckable (true);
371    p->setFixedWidth (p->height ());
372    p->setFlat (true);
373    h->addWidget (p);
374    connect (p, SIGNAL (clicked ()), this, SLOT (toggleSpeedMode ()));
375
376    l = myNetworkLabel = new QLabel;
377    h->addWidget (l);
378
379  h->addStretch (1);
380
381    l = myDownloadSpeedLabel = new QLabel (this);
382    const int minimumSpeedWidth = l->fontMetrics ().width (Formatter::uploadSpeedToString (Speed::fromKBps (999.99)));
383    l->setMinimumWidth (minimumSpeedWidth);
384    l->setAlignment (Qt::AlignRight|Qt::AlignVCenter);
385    h->addWidget (l);
386
387  h->addSpacing (HIG::PAD);
388
389    l = myUploadSpeedLabel = new QLabel;
390    l->setMinimumWidth (minimumSpeedWidth);
391    l->setAlignment (Qt::AlignRight|Qt::AlignVCenter);
392    h->addWidget (l);
393
394  h->addSpacing (HIG::PAD);
395
396    l = myStatsLabel = new QLabel (this);
397    h->addWidget (l);
398    a = new QActionGroup (this);
399    a->addAction (ui.action_TotalRatio);
400    a->addAction (ui.action_TotalTransfer);
401    a->addAction (ui.action_SessionRatio);
402    a->addAction (ui.action_SessionTransfer);
403    m = new QMenu (this);
404    m->addAction (ui.action_TotalRatio);
405    m->addAction (ui.action_TotalTransfer);
406    m->addAction (ui.action_SessionRatio);
407    m->addAction (ui.action_SessionTransfer);
408    connect (ui.action_TotalRatio, SIGNAL (triggered ()), this, SLOT (showTotalRatio ()));
409    connect (ui.action_TotalTransfer, SIGNAL (triggered ()), this, SLOT (showTotalTransfer ()));
410    connect (ui.action_SessionRatio, SIGNAL (triggered ()), this, SLOT (showSessionRatio ()));
411    connect (ui.action_SessionTransfer, SIGNAL (triggered ()), this, SLOT (showSessionTransfer ()));
412    p = myStatsModeButton = new TrIconPushButton (this);
413    p->setIcon (QIcon (":/icons/ratio.png"));
414    p->setIconSize (QPixmap (":/icons/ratio.png").size ());
415    p->setFlat (true);
416    p->setMenu (m);
417    h->addWidget (p);
418
419  return top;
420}
421
422QMenu *
423TrMainWindow::createOptionsMenu ()
424{
425  QMenu * menu;
426  QMenu * sub;
427  QAction * a;
428  QActionGroup * g;
429
430  QList<int> stockSpeeds;
431  stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
432  QList<double> stockRatios;
433  stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
434
435  menu = new QMenu (this);
436  sub = menu->addMenu (tr ("Limit Download Speed"));
437
438    int currentVal = myPrefs.get<int> (Prefs::DSPEED);
439    g = new QActionGroup (this);
440    a = myDlimitOffAction = sub->addAction (tr ("Unlimited"));
441    a->setCheckable (true);
442    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED_ENABLED << false);
443    g->addAction (a);
444    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
445    a = myDlimitOnAction = sub->addAction (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (currentVal))));
446    a->setCheckable (true);
447    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true);
448    g->addAction (a);
449    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
450    sub->addSeparator ();
451    foreach (int i, stockSpeeds)
452      {
453        a = sub->addAction (Formatter::speedToString (Speed::fromKBps (i)));
454        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true);
455        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
456      }
457
458  sub = menu->addMenu (tr ("Limit Upload Speed"));
459
460    currentVal = myPrefs.get<int> (Prefs::USPEED);
461    g = new QActionGroup (this);
462    a = myUlimitOffAction = sub->addAction (tr ("Unlimited"));
463    a->setCheckable (true);
464    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED_ENABLED << false);
465    g->addAction (a);
466    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
467    a = myUlimitOnAction = sub->addAction (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (currentVal))));
468    a->setCheckable (true);
469    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true);
470    g->addAction (a);
471    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
472    sub->addSeparator ();
473    foreach (int i, stockSpeeds)
474      {
475        a = sub->addAction (Formatter::speedToString (Speed::fromKBps (i)));
476        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true);
477        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
478      }
479
480  menu->addSeparator ();
481  sub = menu->addMenu (tr ("Stop Seeding at Ratio"));
482
483    double d = myPrefs.get<double> (Prefs::RATIO);
484    g = new QActionGroup (this);
485    a = myRatioOffAction = sub->addAction (tr ("Seed Forever"));
486    a->setCheckable (true);
487    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO_ENABLED << false);
488    g->addAction (a);
489    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
490    a = myRatioOnAction = sub->addAction (tr ("Stop at Ratio (%1)").arg (Formatter::ratioToString (d)));
491    a->setCheckable (true);
492    a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true);
493    g->addAction (a);
494    connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs (bool)));
495    sub->addSeparator ();
496    foreach (double i, stockRatios)
497      {
498        a = sub->addAction (Formatter::ratioToString (i));
499        a->setProperty (PREF_VARIANTS_KEY, QVariantList () << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true);
500        connect (a, SIGNAL (triggered (bool)), this, SLOT (onSetPrefs ()));
501      }
502
503  return menu;
504}
505
506/****
507*****
508****/
509
510void
511TrMainWindow::setSortPref (int i)
512{
513  myPrefs.set (Prefs::SORT_MODE, SortMode (i));
514}
515void TrMainWindow::onSortByActivityToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_ACTIVITY); }
516void TrMainWindow::onSortByAgeToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_AGE); }
517void TrMainWindow::onSortByETAToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_ETA); }
518void TrMainWindow::onSortByNameToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_NAME); }
519void TrMainWindow::onSortByProgressToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_PROGRESS); }
520void TrMainWindow::onSortByQueueToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_QUEUE); }
521void TrMainWindow::onSortByRatioToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_RATIO); }
522void TrMainWindow::onSortBySizeToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_SIZE); }
523void TrMainWindow::onSortByStateToggled (bool b) { if (b) setSortPref (SortMode::SORT_BY_STATE); }
524
525void
526TrMainWindow::setSortAscendingPref (bool b)
527{
528  myPrefs.set (Prefs::SORT_REVERSED, b);
529}
530
531/****
532*****
533****/
534
535void
536TrMainWindow::showEvent (QShowEvent * event)
537{
538  Q_UNUSED (event);
539
540  ui.action_ShowMainWindow->setChecked (true);
541}
542
543/****
544*****
545****/
546
547void
548TrMainWindow::hideEvent (QHideEvent * event)
549{
550  Q_UNUSED (event);
551
552  if (!isVisible ())
553    ui.action_ShowMainWindow->setChecked (false);
554}
555
556/****
557*****
558****/
559
560void
561TrMainWindow::onPrefsDestroyed ()
562{
563  myPrefsDialog = 0;
564}
565
566void
567TrMainWindow::openPreferences ()
568{
569  if (myPrefsDialog == 0)
570    {
571      myPrefsDialog = new PrefsDialog (mySession, myPrefs, this);
572      connect (myPrefsDialog, SIGNAL (destroyed (QObject*)), this, SLOT (onPrefsDestroyed ()));
573    }
574
575  myPrefsDialog->show ();
576}
577
578void
579TrMainWindow::onDetailsDestroyed ()
580{
581  myDetailsDialog = 0;
582}
583
584void
585TrMainWindow::openProperties ()
586{
587  if (myDetailsDialog == 0)
588    {
589      myDetailsDialog = new Details (mySession, myPrefs, myModel, this);
590      connect (myDetailsDialog, SIGNAL (destroyed (QObject*)), this, SLOT (onDetailsDestroyed ()));
591    }
592
593  myDetailsDialog->setIds (getSelectedTorrents ());
594  myDetailsDialog->show ();
595}
596
597void
598TrMainWindow::setLocation ()
599{
600  QDialog * d = new RelocateDialog (mySession, myModel, getSelectedTorrents (), this);
601  d->show ();
602}
603
604// Open Folder & select torrent's file or top folder
605#undef HAVE_OPEN_SELECT
606#if defined (Q_OS_WIN)
607# define HAVE_OPEN_SELECT
608static
609void openSelect (const QString& path)
610{
611  const QString explorer = "explorer";
612  QString param;
613  if (!QFileInfo (path).isDir ())
614    param = QLatin1String ("/select,");
615  param += QDir::toNativeSeparators (path);
616  QProcess::startDetached (explorer, QStringList (param));
617}
618#elif defined (Q_OS_MAC)
619# define HAVE_OPEN_SELECT
620static
621void openSelect (const QString& path)
622{
623  QStringList scriptArgs;
624  scriptArgs << QLatin1String ("-e")
625             << QString::fromLatin1 ("tell application \"Finder\" to reveal POSIX file \"%1\"").arg (path);
626  QProcess::execute (QLatin1String ("/usr/bin/osascript"), scriptArgs);
627
628  scriptArgs.clear ();
629  scriptArgs << QLatin1String ("-e")
630             << QLatin1String ("tell application \"Finder\" to activate");
631  QProcess::execute ("/usr/bin/osascript", scriptArgs);
632}
633#endif
634
635void
636TrMainWindow::openFolder ()
637{
638  const int torrentId (*getSelectedTorrents ().begin ());
639  const Torrent * tor (myModel.getTorrentFromId (torrentId));
640  QString path (tor->getPath ());
641  const FileList files = tor->files ();
642  const QString firstfile = files.at (0).filename;
643  int slashIndex = firstfile.indexOf ('/');
644  if (slashIndex > -1)
645    {
646      path = path + "/" + firstfile.left (slashIndex);
647    }
648#ifdef HAVE_OPEN_SELECT
649  else
650    {
651      openSelect (path + "/" + firstfile);
652      return;
653    }
654#endif
655  QDesktopServices::openUrl (QUrl::fromLocalFile (path));
656}
657
658void
659TrMainWindow::copyMagnetLinkToClipboard ()
660{
661  const int id (*getSelectedTorrents ().begin ());
662  mySession.copyMagnetLinkToClipboard (id);
663}
664
665void
666TrMainWindow::openDonate ()
667{
668  QDesktopServices::openUrl (QUrl ("http://www.transmissionbt.com/donate.php"));
669}
670
671void
672TrMainWindow::openHelp ()
673{
674  QDesktopServices::openUrl (QUrl (QString::fromLatin1 ("http://www.transmissionbt.com/help/gtk/%1.%2x").
675    arg (MAJOR_VERSION).arg (MINOR_VERSION / 10)));
676}
677
678void
679TrMainWindow::refreshTitle ()
680{
681  QString title ("Transmission");
682  const QUrl url (mySession.getRemoteUrl ());
683  if (!url.isEmpty ())
684    title += tr (" - %1:%2").arg (url.host ()).arg (url.port ());
685  setWindowTitle (title);
686}
687
688void
689TrMainWindow::refreshTrayIconSoon ()
690{
691  if (!myRefreshTrayIconTimer.isActive ())
692    {
693      myRefreshTrayIconTimer.setSingleShot (true);
694      myRefreshTrayIconTimer.start (100);
695    }
696}
697void
698TrMainWindow::refreshTrayIcon ()
699{
700  Speed upSpeed, downSpeed;
701  size_t upCount, downCount;
702  QString tip;
703
704  myModel.getTransferSpeed (upSpeed, upCount, downSpeed, downCount);
705
706  if (myNetworkError)
707    {
708      tip  = tr ("Network Error");
709    }
710  else if (!upCount && !downCount)
711    {
712      tip = tr ("Idle");
713    }
714  else if (downCount)
715    {
716      tip  = tr( "%1   %2" ).arg(Formatter::downloadSpeedToString(downSpeed))
717                            .arg(Formatter::uploadSpeedToString(upSpeed));
718    }
719  else if (upCount)
720    {
721      tip = Formatter::uploadSpeedToString(upSpeed);
722    }
723
724  myTrayIcon.setToolTip (tip);
725}
726
727void
728TrMainWindow::refreshStatusBar ()
729{
730  Speed upSpeed, downSpeed;
731  size_t upCount, downCount;
732  myModel.getTransferSpeed (upSpeed, upCount, downSpeed, downCount);
733
734  myUploadSpeedLabel->setText (Formatter::uploadSpeedToString(upSpeed));
735  myUploadSpeedLabel->setVisible (downCount || upCount);
736  myDownloadSpeedLabel->setText (Formatter::downloadSpeedToString(downSpeed));
737  myDownloadSpeedLabel->setVisible (downCount);
738
739  myNetworkLabel->setVisible (!mySession.isServer ());
740
741  const QString mode (myPrefs.getString (Prefs::STATUSBAR_STATS));
742  QString str;
743
744  if (mode == "session-ratio")
745    {
746      str = tr ("Ratio: %1").arg (Formatter:: ratioToString (mySession.getStats ().ratio));
747    }
748  else if (mode == "session-transfer")
749    {
750      const tr_session_stats& stats (mySession.getStats ());
751      str = tr ("Down: %1, Up: %2").arg (Formatter:: sizeToString (stats.downloadedBytes))
752                                      .arg (Formatter:: sizeToString (stats.uploadedBytes));
753    }
754  else if (mode == "total-transfer")
755    {
756      const tr_session_stats& stats (mySession.getCumulativeStats ());
757      str = tr ("Down: %1, Up: %2").arg (Formatter:: sizeToString (stats.downloadedBytes))
758                                   .arg (Formatter:: sizeToString (stats.uploadedBytes));
759    }
760  else // default is "total-ratio"
761    {
762      str = tr ("Ratio: %1").arg (Formatter:: ratioToString (mySession.getCumulativeStats ().ratio));
763    }
764
765  myStatsLabel->setText (str);
766}
767
768
769
770void
771TrMainWindow::refreshActionSensitivitySoon ()
772{
773  if (!myRefreshActionSensitivityTimer.isActive ())
774    {
775      myRefreshActionSensitivityTimer.setSingleShot (true);
776      myRefreshActionSensitivityTimer.start (100);
777    }
778}
779void
780TrMainWindow::refreshActionSensitivity ()
781{
782  int selected (0);
783  int paused (0);
784  int queued (0);
785  int selectedAndPaused (0);
786  int selectedAndQueued (0);
787  int canAnnounce (0);
788  const QAbstractItemModel * model (ui.listView->model ());
789  const QItemSelectionModel * selectionModel (ui.listView->selectionModel ());
790  const int rowCount (model->rowCount ());
791
792  // count how many torrents are selected, paused, etc
793  for (int row=0; row<rowCount; ++row)
794    {
795      const QModelIndex modelIndex (model->index (row, 0));
796      assert (model == modelIndex.model ());
797      const Torrent * tor (model->data (modelIndex, TorrentModel::TorrentRole).value<const Torrent*> ());
798      if (tor)
799        {
800          const bool isSelected (selectionModel->isSelected (modelIndex));
801          const bool isPaused (tor->isPaused ());
802          const bool isQueued (tor->isQueued ());
803          if (isSelected) ++selected;
804          if (isQueued) ++queued;
805          if (isPaused) ++ paused;
806          if (isSelected && isPaused) ++selectedAndPaused;
807          if (isSelected && isQueued) ++selectedAndQueued;
808          if (tor->canManualAnnounce ()) ++canAnnounce;
809        }
810    }
811
812  const bool haveSelection (selected > 0);
813  ui.action_Verify->setEnabled (haveSelection);
814  ui.action_Remove->setEnabled (haveSelection);
815  ui.action_Delete->setEnabled (haveSelection);
816  ui.action_Properties->setEnabled (haveSelection);
817  ui.action_DeselectAll->setEnabled (haveSelection);
818  ui.action_SetLocation->setEnabled (haveSelection);
819
820  const bool oneSelection (selected == 1);
821  ui.action_OpenFolder->setEnabled (oneSelection && mySession.isLocal ());
822  ui.action_CopyMagnetToClipboard->setEnabled (oneSelection);
823
824  ui.action_SelectAll->setEnabled (selected < rowCount);
825  ui.action_StartAll->setEnabled (paused > 0);
826  ui.action_PauseAll->setEnabled (paused < rowCount);
827  ui.action_Start->setEnabled (selectedAndPaused > 0);
828  ui.action_StartNow->setEnabled (selectedAndPaused + selectedAndQueued > 0);
829  ui.action_Pause->setEnabled (selectedAndPaused < selected);
830  ui.action_Announce->setEnabled (selected > 0 && (canAnnounce == selected));
831
832  ui.action_QueueMoveTop->setEnabled (haveSelection);
833  ui.action_QueueMoveUp->setEnabled (haveSelection);
834  ui.action_QueueMoveDown->setEnabled (haveSelection);
835  ui.action_QueueMoveBottom->setEnabled (haveSelection);
836
837  if (myDetailsDialog)
838    myDetailsDialog->setIds (getSelectedTorrents ());
839}
840
841/**
842***
843**/
844
845void
846TrMainWindow::clearSelection ()
847{
848  ui.action_DeselectAll->trigger ();
849}
850
851QSet<int>
852TrMainWindow::getSelectedTorrents () const
853{
854  QSet<int> ids;
855
856  foreach (QModelIndex index, ui.listView->selectionModel ()->selectedRows ())
857    {
858      const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*> ());
859      ids.insert (tor->id ());
860    }
861
862  return ids;
863}
864
865void
866TrMainWindow::startSelected ()
867{
868  mySession.startTorrents (getSelectedTorrents ());
869}
870void
871TrMainWindow::startSelectedNow ()
872{
873  mySession.startTorrentsNow (getSelectedTorrents ());
874}
875void
876TrMainWindow::pauseSelected ()
877{
878  mySession.pauseTorrents (getSelectedTorrents ());
879}
880void
881TrMainWindow::queueMoveTop ()
882{
883  mySession.queueMoveTop (getSelectedTorrents ());
884}
885void
886TrMainWindow::queueMoveUp ()
887{
888  mySession.queueMoveUp (getSelectedTorrents ());
889}
890void
891TrMainWindow::queueMoveDown ()
892{
893  mySession.queueMoveDown (getSelectedTorrents ());
894}
895void
896TrMainWindow::queueMoveBottom ()
897{
898  mySession.queueMoveBottom (getSelectedTorrents ());
899}
900void
901TrMainWindow::startAll ()
902{
903  mySession.startTorrents ();
904}
905void
906TrMainWindow::pauseAll ()
907{
908  mySession.pauseTorrents ();
909}
910void
911TrMainWindow::removeSelected ()
912{
913  removeTorrents (false);
914}
915void
916TrMainWindow::deleteSelected ()
917{
918  removeTorrents (true);
919}
920void
921TrMainWindow::verifySelected ()
922{
923  mySession.verifyTorrents (getSelectedTorrents ());
924}
925void
926TrMainWindow::reannounceSelected ()
927{
928  mySession.reannounceTorrents (getSelectedTorrents ());
929}
930
931/**
932***
933**/
934
935void TrMainWindow::showTotalRatio () { myPrefs.set (Prefs::STATUSBAR_STATS, "total-ratio"); }
936void TrMainWindow::showTotalTransfer () { myPrefs.set (Prefs::STATUSBAR_STATS, "total-transfer"); }
937void TrMainWindow::showSessionRatio () { myPrefs.set (Prefs::STATUSBAR_STATS, "session-ratio"); }
938void TrMainWindow::showSessionTransfer () { myPrefs.set (Prefs::STATUSBAR_STATS, "session-transfer"); }
939
940/**
941***
942**/
943
944void
945TrMainWindow::setCompactView (bool visible)
946{
947  myPrefs.set (Prefs::COMPACT_VIEW, visible);
948}
949void
950TrMainWindow::toggleSpeedMode ()
951{
952  myPrefs.toggleBool (Prefs::ALT_SPEED_LIMIT_ENABLED);
953  const bool mode = myPrefs.get<bool> (Prefs::ALT_SPEED_LIMIT_ENABLED);
954  myAltSpeedAction->setIcon (mode ? mySpeedModeOnIcon : mySpeedModeOffIcon);
955}
956void
957TrMainWindow::setToolbarVisible (bool visible)
958{
959  myPrefs.set (Prefs::TOOLBAR, visible);
960}
961void
962TrMainWindow::setFilterbarVisible (bool visible)
963{
964  myPrefs.set (Prefs::FILTERBAR, visible);
965}
966void
967TrMainWindow::setStatusbarVisible (bool visible)
968{
969  myPrefs.set (Prefs::STATUSBAR, visible);
970}
971
972/**
973***
974**/
975
976void
977TrMainWindow::toggleWindows (bool doShow)
978{
979  if (!doShow)
980    {
981      hide ();
982    }
983  else
984    {
985      if (!isVisible ()) show ();
986      if (isMinimized ()) showNormal ();
987      //activateWindow ();
988      raise ();
989      QApplication::setActiveWindow (this);
990    }
991}
992
993void
994TrMainWindow::trayActivated (QSystemTrayIcon::ActivationReason reason)
995{
996  if ((reason == QSystemTrayIcon::Trigger) ||
997      (reason == QSystemTrayIcon::DoubleClick))
998    {
999      if (isMinimized ())
1000        toggleWindows (true);
1001      else
1002        toggleWindows (!isVisible ());
1003    }
1004}
1005
1006
1007void
1008TrMainWindow::refreshPref (int key)
1009{
1010  bool b;
1011  int i;
1012  QString str;
1013
1014  switch (key)
1015    {
1016      case Prefs::STATUSBAR_STATS:
1017        str = myPrefs.getString (key);
1018        ui.action_TotalRatio->setChecked (str == "total-ratio");
1019        ui.action_TotalTransfer->setChecked (str == "total-transfer");
1020        ui.action_SessionRatio->setChecked (str == "session-ratio");
1021        ui.action_SessionTransfer->setChecked (str == "session-transfer");
1022        refreshStatusBar ();
1023        break;
1024
1025      case Prefs::SORT_REVERSED:
1026        ui.action_ReverseSortOrder->setChecked (myPrefs.getBool (key));
1027        break;
1028
1029      case Prefs::SORT_MODE:
1030        i = myPrefs.get<SortMode> (key).mode ();
1031        ui.action_SortByActivity->setChecked (i == SortMode::SORT_BY_ACTIVITY);
1032        ui.action_SortByAge->setChecked (i == SortMode::SORT_BY_AGE);
1033        ui.action_SortByETA->setChecked (i == SortMode::SORT_BY_ETA);
1034        ui.action_SortByName->setChecked (i == SortMode::SORT_BY_NAME);
1035        ui.action_SortByProgress->setChecked (i == SortMode::SORT_BY_PROGRESS);
1036        ui.action_SortByQueue->setChecked (i == SortMode::SORT_BY_QUEUE);
1037        ui.action_SortByRatio->setChecked (i == SortMode::SORT_BY_RATIO);
1038        ui.action_SortBySize->setChecked (i == SortMode::SORT_BY_SIZE);
1039        ui.action_SortByState->setChecked (i == SortMode::SORT_BY_STATE);
1040        break;
1041
1042      case Prefs::DSPEED_ENABLED:
1043        (myPrefs.get<bool> (key) ? myDlimitOnAction : myDlimitOffAction)->setChecked (true);
1044        break;
1045
1046      case Prefs::DSPEED:
1047        myDlimitOnAction->setText (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (myPrefs.get<int> (key)))));
1048        break;
1049
1050      case Prefs::USPEED_ENABLED:
1051        (myPrefs.get<bool> (key) ? myUlimitOnAction : myUlimitOffAction)->setChecked (true);
1052        break;
1053
1054      case Prefs::USPEED:
1055        myUlimitOnAction->setText (tr ("Limited at %1").arg (Formatter::speedToString (Speed::fromKBps (myPrefs.get<int> (key)))));
1056        break;
1057
1058      case Prefs::RATIO_ENABLED:
1059        (myPrefs.get<bool> (key) ? myRatioOnAction : myRatioOffAction)->setChecked (true);
1060        break;
1061
1062      case Prefs::RATIO:
1063        myRatioOnAction->setText (tr ("Stop at Ratio (%1)").arg (Formatter::ratioToString (myPrefs.get<double> (key))));
1064        break;
1065
1066      case Prefs::FILTERBAR:
1067        b = myPrefs.getBool (key);
1068        myFilterBar->setVisible (b);
1069        ui.action_Filterbar->setChecked (b);
1070        break;
1071
1072      case Prefs::STATUSBAR:
1073        b = myPrefs.getBool (key);
1074        myStatusBar->setVisible (b);
1075        ui.action_Statusbar->setChecked (b);
1076        break;
1077
1078      case Prefs::TOOLBAR:
1079        b = myPrefs.getBool (key);
1080        ui.toolBar->setVisible (b);
1081        ui.action_Toolbar->setChecked (b);
1082        break;
1083
1084      case Prefs::SHOW_TRAY_ICON:
1085        b = myPrefs.getBool (key);
1086        ui.action_TrayIcon->setChecked (b);
1087        myTrayIcon.setVisible (b);
1088        dynamic_cast<MyApp*> (QCoreApplication::instance ())->setQuitOnLastWindowClosed (!b);
1089        refreshTrayIconSoon ();
1090        break;
1091
1092      case Prefs::COMPACT_VIEW: {
1093            QItemSelectionModel * selectionModel (ui.listView->selectionModel ());
1094            const QItemSelection selection (selectionModel->selection ());
1095            const QModelIndex currentIndex (selectionModel->currentIndex ());
1096            b = myPrefs.getBool (key);
1097            ui.action_CompactView->setChecked (b);
1098            ui.listView->setItemDelegate (b ? myTorrentDelegateMin : myTorrentDelegate);
1099            selectionModel->clear ();
1100            ui.listView->reset (); // force the rows to resize
1101            selectionModel->select (selection, QItemSelectionModel::Select);
1102            selectionModel->setCurrentIndex (currentIndex, QItemSelectionModel::NoUpdate);
1103            break;
1104        }
1105
1106      case Prefs::MAIN_WINDOW_X:
1107      case Prefs::MAIN_WINDOW_Y:
1108      case Prefs::MAIN_WINDOW_WIDTH:
1109      case Prefs::MAIN_WINDOW_HEIGHT:
1110        setGeometry (myPrefs.getInt (Prefs::MAIN_WINDOW_X),
1111                     myPrefs.getInt (Prefs::MAIN_WINDOW_Y),
1112                     myPrefs.getInt (Prefs::MAIN_WINDOW_WIDTH),
1113                     myPrefs.getInt (Prefs::MAIN_WINDOW_HEIGHT));
1114        break;
1115
1116      case Prefs::ALT_SPEED_LIMIT_ENABLED:
1117      case Prefs::ALT_SPEED_LIMIT_UP:
1118      case Prefs::ALT_SPEED_LIMIT_DOWN:
1119        {
1120          b = myPrefs.getBool (Prefs::ALT_SPEED_LIMIT_ENABLED);
1121          myAltSpeedButton->setChecked (b);
1122          myAltSpeedButton->setIcon (b ? mySpeedModeOnIcon : mySpeedModeOffIcon);
1123          const QString fmt = b ? tr ("Click to disable Temporary Speed Limits\n (%1 down, %2 up)")
1124                                : tr ("Click to enable Temporary Speed Limits\n (%1 down, %2 up)");
1125          const Speed d = Speed::fromKBps (myPrefs.getInt (Prefs::ALT_SPEED_LIMIT_DOWN));
1126          const Speed u = Speed::fromKBps (myPrefs.getInt (Prefs::ALT_SPEED_LIMIT_UP));
1127          myAltSpeedButton->setToolTip (fmt.arg (Formatter::speedToString (d))
1128                                           .arg (Formatter::speedToString (u)));
1129          break;
1130        }
1131
1132      default:
1133        break;
1134    }
1135}
1136
1137/***
1138****
1139***/
1140
1141#define SHOW_OPTIONS_CHECKBOX_NAME "show-options-checkbox"
1142
1143void
1144TrMainWindow::newTorrent ()
1145{
1146  MakeDialog * dialog = new MakeDialog (mySession, this);
1147  dialog->show ();
1148}
1149
1150void
1151TrMainWindow::openTorrent ()
1152{
1153  QFileDialog * d;
1154  d = new QFileDialog (this,
1155                       tr ("Open Torrent"),
1156                       myPrefs.getString (Prefs::OPEN_DIALOG_FOLDER),
1157                       tr ("Torrent Files (*.torrent);;All Files (*.*)"));
1158  d->setFileMode (QFileDialog::ExistingFiles);
1159  d->setAttribute (Qt::WA_DeleteOnClose);
1160
1161  QCheckBox * b = new QCheckBox (tr ("Show &options dialog"));
1162  b->setChecked (myPrefs.getBool (Prefs::OPTIONS_PROMPT));
1163  b->setObjectName (SHOW_OPTIONS_CHECKBOX_NAME);
1164  auto l = dynamic_cast<QGridLayout*> (d->layout ());
1165  if (l == nullptr)
1166    {
1167      l = new QGridLayout;
1168      d->setLayout (l);
1169    }
1170  l->addWidget (b, l->rowCount(), 0, 1, -1, Qt::AlignLeft);
1171
1172  connect (d, SIGNAL (filesSelected (QStringList)),
1173           this, SLOT (addTorrents (QStringList)));
1174
1175  d->show ();
1176}
1177
1178void
1179TrMainWindow::openURL ()
1180{
1181  QString str = QApplication::clipboard ()->text (QClipboard::Selection);
1182
1183  if (!AddData::isSupported (str))
1184    str = QApplication::clipboard ()->text (QClipboard::Clipboard);
1185
1186  if (!AddData::isSupported (str))
1187    str.clear ();
1188
1189  addTorrent (str, true);
1190}
1191
1192void
1193TrMainWindow::addTorrents (const QStringList& filenames)
1194{
1195  bool showOptions = myPrefs.getBool (Prefs::OPTIONS_PROMPT);
1196
1197  const QFileDialog * const fileDialog = qobject_cast<const QFileDialog*> (sender ());
1198  if (fileDialog != NULL)
1199    {
1200      const QCheckBox * const b = fileDialog->findChild<const QCheckBox*> (SHOW_OPTIONS_CHECKBOX_NAME);
1201      if (b != NULL)
1202        showOptions = b->isChecked ();
1203    }
1204
1205  foreach (const QString& filename, filenames)
1206    addTorrent (filename, showOptions);
1207}
1208
1209void
1210TrMainWindow::addTorrent (const AddData& addMe, bool showOptions)
1211{
1212  if (showOptions)
1213    {
1214      Options * o = new Options (mySession, myPrefs, addMe, this);
1215      o->show ();
1216      QApplication::alert (o);
1217    }
1218  else
1219    {
1220      mySession.addTorrent (addMe);
1221      QApplication::alert (this);
1222    }
1223}
1224
1225void
1226TrMainWindow::removeTorrents (const bool deleteFiles)
1227{
1228  QSet<int> ids;
1229  QMessageBox msgBox (this);
1230  QString primary_text, secondary_text;
1231  int incomplete = 0;
1232  int connected  = 0;
1233  int count;
1234
1235  foreach (QModelIndex index, ui.listView->selectionModel ()->selectedRows ())
1236    {
1237      const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*> ());
1238      ids.insert (tor->id ());
1239
1240      if (tor->connectedPeers ())
1241        ++connected;
1242
1243      if (!tor->isDone ())
1244        ++incomplete;
1245    }
1246
1247  if (ids.isEmpty ())
1248    return;
1249
1250  count = ids.size ();
1251
1252  if (!deleteFiles)
1253    {
1254      primary_text = (count == 1)
1255        ? tr ("Remove torrent?")
1256        : tr ("Remove %1 torrents?").arg (count);
1257    }
1258  else
1259    {
1260      primary_text = (count == 1)
1261        ? tr ("Delete this torrent's downloaded files?")
1262        : tr ("Delete these %1 torrents' downloaded files?").arg (count);
1263    }
1264
1265  if (!incomplete && !connected)
1266    {
1267      secondary_text = (count == 1)
1268        ? tr ("Once removed, continuing the transfer will require the torrent file or magnet link.")
1269        : tr ("Once removed, continuing the transfers will require the torrent files or magnet links.");
1270    }
1271  else if (count == incomplete)
1272    {
1273      secondary_text = (count == 1)
1274        ? tr ("This torrent has not finished downloading.")
1275        : tr ("These torrents have not finished downloading.");
1276    }
1277  else if (count == connected)
1278    {
1279      secondary_text = (count == 1)
1280        ? tr ("This torrent is connected to peers.")
1281        : tr ("These torrents are connected to peers.");
1282    }
1283  else
1284    {
1285      if (connected)
1286        {
1287          secondary_text = (connected == 1)
1288            ? tr ("One of these torrents is connected to peers.")
1289            : tr ("Some of these torrents are connected to peers.");
1290        }
1291
1292      if (connected && incomplete)
1293        {
1294          secondary_text += "\n";
1295        }
1296
1297      if (incomplete)
1298        {
1299          secondary_text += (incomplete == 1)
1300            ? tr ("One of these torrents has not finished downloading.")
1301            : tr ("Some of these torrents have not finished downloading.");
1302        }
1303    }
1304
1305  msgBox.setWindowTitle (QString (" "));
1306  msgBox.setText (QString ("<big><b>%1</big></b>").arg (primary_text));
1307  msgBox.setInformativeText (secondary_text);
1308  msgBox.setStandardButtons (QMessageBox::Ok | QMessageBox::Cancel);
1309  msgBox.setDefaultButton (QMessageBox::Cancel);
1310  msgBox.setIcon (QMessageBox::Question);
1311  // hack needed to keep the dialog from being too narrow
1312  auto layout = dynamic_cast<QGridLayout*>(msgBox.layout());
1313  if (layout == nullptr)
1314    {
1315      layout = new QGridLayout;
1316      msgBox.setLayout (layout);
1317    }
1318  QSpacerItem* spacer = new QSpacerItem (450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
1319  layout->addItem (spacer, layout->rowCount (), 0, 1, layout->columnCount ());
1320
1321  if (msgBox.exec () == QMessageBox::Ok)
1322    {
1323      ui.listView->selectionModel ()->clear ();
1324      mySession.removeTorrents (ids, deleteFiles);
1325    }
1326}
1327
1328/***
1329****
1330***/
1331
1332void
1333TrMainWindow::updateNetworkIcon ()
1334{
1335  const time_t now = time (NULL);
1336  const int period = 3;
1337  const time_t secondsSinceLastSend = now - myLastSendTime;
1338  const time_t secondsSinceLastRead = now - myLastReadTime;
1339  const bool isSending = secondsSinceLastSend <= period;
1340  const bool isReading = secondsSinceLastRead <= period;
1341  const char * key;
1342
1343  if (myNetworkError)
1344    key = "network-error";
1345  else if (isSending && isReading)
1346    key = "network-transmit-receive";
1347  else if (isSending)
1348    key = "network-transmit";
1349  else if (isReading)
1350    key = "network-receive";
1351  else
1352    key = "network-idle";
1353  const QIcon icon = getStockIcon (key, QStyle::SP_DriveNetIcon);
1354  const QPixmap pixmap = icon.pixmap (16, 16);
1355
1356  QString tip;
1357  const QString url = mySession.getRemoteUrl ().host ();
1358  if (!myLastReadTime)
1359    tip = tr ("%1 has not responded yet").arg (url);
1360  else if (myNetworkError)
1361    tip = tr (myErrorMessage.toLatin1 ().constData ());
1362  else if (secondsSinceLastRead < 30)
1363    tip = tr ("%1 is responding").arg (url);
1364  else if (secondsSinceLastRead < (60*2))
1365    tip = tr ("%1 last responded %2 ago").arg (url).arg (Formatter::timeToString (secondsSinceLastRead));
1366  else
1367    tip = tr ("%1 is not responding").arg (url);
1368
1369  myNetworkLabel->setPixmap (pixmap);
1370  myNetworkLabel->setToolTip (tip);
1371}
1372
1373void
1374TrMainWindow::onNetworkTimer ()
1375{
1376  updateNetworkIcon ();
1377}
1378
1379void
1380TrMainWindow::dataReadProgress ()
1381{
1382  if (!myNetworkError)
1383  myLastReadTime = time (NULL);
1384}
1385
1386void
1387TrMainWindow::dataSendProgress ()
1388{
1389  myLastSendTime = time (NULL);
1390}
1391
1392void
1393TrMainWindow::onError (QNetworkReply::NetworkError code)
1394{
1395  const bool hadError = myNetworkError;
1396  const bool haveError = (code != QNetworkReply::NoError)
1397                      && (code != QNetworkReply::UnknownContentError);
1398
1399  myNetworkError = haveError;
1400  refreshTrayIconSoon();
1401  updateNetworkIcon();
1402
1403  // Refresh our model if we've just gotten a clean connection to the session.
1404  // That way we can rebuild after a restart of transmission-daemon
1405  if (hadError && !haveError)
1406    myModel.clear();
1407}
1408
1409void
1410TrMainWindow::errorMessage (const QString& msg)
1411{
1412    myErrorMessage = msg;
1413}
1414
1415void
1416TrMainWindow::wrongAuthentication ()
1417{
1418  mySession.stop ();
1419  mySessionDialog->show ();
1420}
1421
1422/***
1423****
1424***/
1425
1426void
1427TrMainWindow::dragEnterEvent (QDragEnterEvent * event)
1428{
1429  const QMimeData * mime = event->mimeData ();
1430
1431  if (mime->hasFormat ("application/x-bittorrent")
1432        || mime->hasUrls()
1433        || mime->text ().trimmed ().endsWith (".torrent", Qt::CaseInsensitive)
1434        || mime->text ().startsWith ("magnet:", Qt::CaseInsensitive))
1435    event->acceptProposedAction ();
1436}
1437
1438void
1439TrMainWindow::dropEvent (QDropEvent * event)
1440{
1441  QStringList list;
1442
1443  if (event->mimeData()->hasText())
1444    {
1445      list = event->mimeData()->text().trimmed().split('\n');
1446    }
1447  else if (event->mimeData()->hasUrls())
1448    {
1449      foreach (QUrl url, event->mimeData()->urls())
1450        list.append(url.toLocalFile());
1451    }
1452
1453  foreach (QString entry, list)
1454    {
1455      QString key = entry.trimmed();
1456
1457      if (!key.isEmpty())
1458        {
1459          const QUrl url (key);
1460
1461          if (url.scheme () == "file")
1462            key = QUrl::fromPercentEncoding (url.path().toUtf8());
1463
1464          dynamic_cast<MyApp*> (QApplication::instance ())->addTorrent (key);
1465        }
1466    }
1467}
1468
1469/***
1470****
1471***/
1472
1473void
1474TrMainWindow::contextMenuEvent (QContextMenuEvent * event)
1475{
1476  QMenu * menu = new QMenu (this);
1477
1478  menu->addAction (ui.action_Properties);
1479  menu->addAction (ui.action_OpenFolder);
1480
1481  QAction * sep = new QAction (menu);
1482  sep->setSeparator (true);
1483  menu->addAction (sep);
1484  menu->addAction (ui.action_Start);
1485  menu->addAction (ui.action_StartNow);
1486  menu->addAction (ui.action_Announce);
1487  QMenu * queueMenu = menu->addMenu (tr ("Queue"));
1488    queueMenu->addAction (ui.action_QueueMoveTop);
1489    queueMenu->addAction (ui.action_QueueMoveUp);
1490    queueMenu->addAction (ui.action_QueueMoveDown);
1491    queueMenu->addAction (ui.action_QueueMoveBottom);
1492  menu->addAction (ui.action_Pause);
1493
1494  sep = new QAction (menu);
1495  sep->setSeparator (true);
1496  menu->addAction (sep);
1497  menu->addAction (ui.action_Verify);
1498  menu->addAction (ui.action_SetLocation);
1499  menu->addAction (ui.action_CopyMagnetToClipboard);
1500
1501  sep = new QAction (menu);
1502  sep->setSeparator (true);
1503  menu->addAction (sep);
1504  menu->addAction (ui.action_Remove);
1505  menu->addAction (ui.action_Delete);
1506
1507  menu->popup (event->globalPos ());
1508}
Note: See TracBrowser for help on using the repository browser.