source: trunk/qt/MainWindow.cc @ 14539

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

Unify/prettify Qt client headers style

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