source: trunk/qt/mainwin.cc @ 14177

Last change on this file since 14177 was 14177, checked in by jordan, 10 years ago

instead of inttypes, use Qt integral types -- eg, s/uint64_t/quint64/

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