source: trunk/qt/mainwin.cc @ 14273

Last change on this file since 14273 was 14273, checked in by jordan, 8 years ago

update network error detection as described by rb07 in https://trac.transmissionbt.com/ticket/5514#comment:9

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