source: trunk/qt/MainWindow.cc @ 14547

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

Some look-and-feel improvements for Mac and GTK+ styles (Qt client)

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