source: trunk/qt/mainwin.cc @ 14205

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

(trunk, qt) #5487 'Qt client crash when using Open URL action' -- fixed.

  • Property svn:keywords set to Date Rev Author Id
File size: 47.0 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 14205 2013-09-14 22:50:25Z jordan $
11 */
12
13#include <cassert>
14#include <iostream>
15
16#include <QtGui>
17#include <QCheckBox>
18#include <QProxyStyle>
19#include <QLabel>
20#include <QFileDialog>
21#include <QMessageBox>
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/utils.h>
25#include <libtransmission/version.h>
26
27#include "about.h"
28#include "add-data.h"
29#include "app.h"
30#include "details.h"
31#include "filterbar.h"
32#include "filters.h"
33#include "formatter.h"
34#include "hig.h"
35#include "mainwin.h"
36#include "make-dialog.h"
37#include "options.h"
38#include "prefs.h"
39#include "prefs-dialog.h"
40#include "relocate.h"
41#include "session.h"
42#include "session-dialog.h"
43#include "speed.h"
44#include "stats-dialog.h"
45#include "torrent-delegate.h"
46#include "torrent-delegate-min.h"
47#include "torrent-filter.h"
48#include "torrent-model.h"
49#include "triconpushbutton.h"
50#include "ui_mainwin.h"
51
52#define PREFS_KEY "prefs-key";
53
54
55/**
56 * This is a proxy-style for that forces it to be always disabled.
57 * We use this to make our torrent list view behave consistently on
58 * both GTK and Qt implementations.
59 */
60class ListViewProxyStyle: public QProxyStyle
61{
62  public:
63
64    int styleHint (StyleHint            hint,
65                   const QStyleOption * option = 0,
66                   const QWidget      * widget = 0,
67                   QStyleHintReturn   * returnData = 0) const
68    {
69      if (hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
70        return 0;
71      return QProxyStyle::styleHint (hint, option, widget, returnData);
72    }
73};
74
75
76QIcon
77TrMainWindow :: getStockIcon (const QString& name, int fallback)
78{
79  QIcon icon = QIcon::fromTheme (name);
80
81  if (icon.isNull () && (fallback >= 0))
82    icon = style ()->standardIcon (QStyle::StandardPixmap (fallback), 0, this);
83
84  return icon;
85}
86
87namespace
88{
89  QSize calculateTextButtonSizeHint (QPushButton * button)
90  {
91    QStyleOptionButton opt;
92    opt.initFrom (button);
93    QString s (button->text ());
94    if (s.isEmpty ())
95      s = QString::fromLatin1 ("XXXX");
96    QFontMetrics fm = button->fontMetrics ();
97    QSize sz = fm.size (Qt::TextShowMnemonic, s);
98    return button->style ()->sizeFromContents (QStyle::CT_PushButton, &opt, sz, button).expandedTo (QApplication::globalStrut ());
99  }
100}
101
102
103TrMainWindow :: TrMainWindow (Session& session, Prefs& prefs, TorrentModel& model, bool minimized):
104  myLastFullUpdateTime (0),
105  mySessionDialog (new SessionDialog (session, prefs, this)),
106  myPrefsDialog (0),
107  myAboutDialog (new AboutDialog (this)),
108  myStatsDialog (new StatsDialog (session, this)),
109  myDetailsDialog (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
1157#define SHOW_OPTIONS_CHECKBOX_NAME "show-options-checkbox"
1158
1159void
1160TrMainWindow :: newTorrent ()
1161{
1162  MakeDialog * dialog = new MakeDialog (mySession, this);
1163  dialog->show ();
1164}
1165
1166void
1167TrMainWindow :: openTorrent ()
1168{
1169  QFileDialog * d;
1170  d = new QFileDialog (this,
1171                       tr ("Open Torrent"),
1172                       myPrefs.getString (Prefs::OPEN_DIALOG_FOLDER),
1173                       tr ("Torrent Files (*.torrent);;All Files (*.*)"));
1174  d->setFileMode (QFileDialog::ExistingFiles);
1175  d->setAttribute (Qt::WA_DeleteOnClose);
1176
1177  QCheckBox * b = new QCheckBox (tr ("Show &options dialog"));
1178  b->setChecked (myPrefs.getBool (Prefs::OPTIONS_PROMPT));
1179  b->setObjectName (SHOW_OPTIONS_CHECKBOX_NAME);
1180  QGridLayout * l = dynamic_cast<QGridLayout*> (d->layout ());
1181  l->addWidget (b, l->rowCount (), 0, 1, -1, Qt::AlignLeft);
1182
1183  connect (d, SIGNAL (filesSelected (const QStringList&)),
1184           this, SLOT (addTorrents (const QStringList&)));
1185
1186  d->show ();
1187}
1188
1189void
1190TrMainWindow :: openURL ()
1191{
1192  QString str = QApplication::clipboard ()->text (QClipboard::Selection);
1193
1194  if (!AddData::isSupported (str))
1195    str = QApplication::clipboard ()->text (QClipboard::Clipboard);
1196
1197  if (!AddData::isSupported (str))
1198    str.clear ();
1199
1200  addTorrent (str, true);
1201}
1202
1203void
1204TrMainWindow :: addTorrents (const QStringList& filenames)
1205{
1206  bool showOptions = myPrefs.getBool (Prefs::OPTIONS_PROMPT);
1207
1208  const QFileDialog * const fileDialog = qobject_cast<const QFileDialog*> (sender ());
1209  if (fileDialog != NULL)
1210    {
1211      const QCheckBox * const b = fileDialog->findChild<const QCheckBox*> (SHOW_OPTIONS_CHECKBOX_NAME);
1212      if (b != NULL)
1213        showOptions = b->isChecked ();
1214    }
1215
1216  foreach (const QString& filename, filenames)
1217    addTorrent (filename, showOptions);
1218}
1219
1220void
1221TrMainWindow :: addTorrent (const AddData& addMe, bool showOptions)
1222{
1223  if (showOptions)
1224    {
1225      Options * o = new Options (mySession, myPrefs, addMe, this);
1226      o->show ();
1227      QApplication :: alert (o);
1228    }
1229  else
1230    {
1231      mySession.addTorrent (addMe);
1232      QApplication :: alert (this);
1233    }
1234}
1235
1236void
1237TrMainWindow :: removeTorrents (const bool deleteFiles)
1238{
1239  QSet<int> ids;
1240  QMessageBox msgBox (this);
1241  QString primary_text, secondary_text;
1242  int incomplete = 0;
1243  int connected  = 0;
1244  int count;
1245
1246  foreach (QModelIndex index, ui.listView->selectionModel ()->selectedRows ())
1247    {
1248      const Torrent * tor (index.data (TorrentModel::TorrentRole).value<const Torrent*> ());
1249      ids.insert (tor->id ());
1250
1251      if (tor->connectedPeers ())
1252        ++connected;
1253
1254      if (!tor->isDone ())
1255        ++incomplete;
1256    }
1257
1258  if (ids.isEmpty ())
1259    return;
1260
1261  count = ids.size ();
1262
1263  if (!deleteFiles)
1264    {
1265      primary_text = (count == 1)
1266        ? tr ("Remove torrent?")
1267        : tr ("Remove %1 torrents?").arg (count);
1268    }
1269  else
1270    {
1271      primary_text = (count == 1)
1272        ? tr ("Delete this torrent's downloaded files?")
1273        : tr ("Delete these %1 torrents' downloaded files?").arg (count);
1274    }
1275
1276  if (!incomplete && !connected)
1277    {
1278      secondary_text = (count == 1)
1279        ? tr ("Once removed, continuing the transfer will require the torrent file or magnet link.")
1280        : tr ("Once removed, continuing the transfers will require the torrent files or magnet links.");
1281    }
1282  else if (count == incomplete)
1283    {
1284      secondary_text = (count == 1)
1285        ? tr ("This torrent has not finished downloading.")
1286        : tr ("These torrents have not finished downloading.");
1287    }
1288  else if (count == connected)
1289    {
1290      secondary_text = (count == 1)
1291        ? tr ("This torrent is connected to peers.")
1292        : tr ("These torrents are connected to peers.");
1293    }
1294  else
1295    {
1296      if (connected)
1297        {
1298          secondary_text = (connected == 1)
1299            ? tr ("One of these torrents is connected to peers.")
1300            : tr ("Some of these torrents are connected to peers.");
1301        }
1302
1303      if (connected && incomplete)
1304        {
1305          secondary_text += "\n";
1306        }
1307
1308      if (incomplete)
1309        {
1310          secondary_text += (incomplete == 1)
1311            ? tr ("One of these torrents has not finished downloading.")
1312            : tr ("Some of these torrents have not finished downloading.");
1313        }
1314    }
1315
1316  msgBox.setWindowTitle (QString (" "));
1317  msgBox.setText (QString ("<big><b>%1</big></b>").arg (primary_text));
1318  msgBox.setInformativeText (secondary_text);
1319  msgBox.setStandardButtons (QMessageBox::Ok | QMessageBox::Cancel);
1320  msgBox.setDefaultButton (QMessageBox::Cancel);
1321  msgBox.setIcon (QMessageBox::Question);
1322  /* hack needed to keep the dialog from being too narrow */
1323  QGridLayout* layout = (QGridLayout*)msgBox.layout ();
1324  QSpacerItem* spacer = new QSpacerItem (450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding);
1325  layout->addItem (spacer, layout->rowCount (), 0, 1, layout->columnCount ());
1326
1327  if (msgBox.exec () == QMessageBox::Ok)
1328    {
1329      ui.listView->selectionModel ()->clear ();
1330      mySession.removeTorrents (ids, deleteFiles);
1331    }
1332}
1333
1334/***
1335****
1336***/
1337
1338void
1339TrMainWindow :: updateNetworkIcon ()
1340{
1341  const time_t now = time (NULL);
1342  const int period = 3;
1343  const time_t secondsSinceLastSend = now - myLastSendTime;
1344  const time_t secondsSinceLastRead = now - myLastReadTime;
1345  const bool isSending = secondsSinceLastSend <= period;
1346  const bool isReading = secondsSinceLastRead <= period;
1347  const char * key;
1348
1349  if (isSending && isReading)
1350    key = "network-transmit-receive";
1351  else if (isSending)
1352    key = "network-transmit";
1353  else if (isReading)
1354    key = "network-receive";
1355  else
1356    key = "network-idle";
1357  const QIcon icon = getStockIcon (key, QStyle::SP_DriveNetIcon);
1358  const QPixmap pixmap = icon.pixmap (16, 16);
1359
1360  QString tip;
1361  const QString url = mySession.getRemoteUrl ().host ();
1362  if (!myLastReadTime)
1363    tip = tr ("%1 has not responded yet").arg (url);
1364  else if (secondsSinceLastRead < 60)
1365    tip = tr ("%1 is responding").arg (url);
1366  else if (secondsSinceLastRead < (60*10))
1367    tip = tr ("%1 last responded %2 ago").arg (url).arg (Formatter::timeToString (secondsSinceLastRead));
1368  else
1369    tip = tr ("%1 is not responding").arg (url);
1370
1371  myNetworkLabel->setPixmap (pixmap);
1372  myNetworkLabel->setToolTip (tip);
1373}
1374
1375void
1376TrMainWindow :: onNetworkTimer ()
1377{
1378  updateNetworkIcon ();
1379}
1380
1381void
1382TrMainWindow :: dataReadProgress ()
1383{
1384  myLastReadTime = time (NULL);
1385  updateNetworkIcon ();
1386}
1387
1388void
1389TrMainWindow :: dataSendProgress ()
1390{
1391  myLastSendTime = time (NULL);
1392  updateNetworkIcon ();
1393}
1394
1395void
1396TrMainWindow :: wrongAuthentication ()
1397{
1398  mySession.stop ();
1399  mySessionDialog->show ();
1400}
1401
1402/***
1403****
1404***/
1405
1406void
1407TrMainWindow :: dragEnterEvent (QDragEnterEvent * event)
1408{
1409  const QMimeData * mime = event->mimeData ();
1410
1411  if (mime->hasFormat ("application/x-bittorrent")
1412        || mime->hasUrls()
1413        || mime->text ().trimmed ().endsWith (".torrent", Qt::CaseInsensitive)
1414        || mime->text ().startsWith ("magnet:", Qt::CaseInsensitive))
1415    event->acceptProposedAction ();
1416}
1417
1418void
1419TrMainWindow :: dropEvent (QDropEvent * event)
1420{
1421  QStringList list;
1422
1423  if (event->mimeData()->hasText())
1424    {
1425      list = event->mimeData()->text().trimmed().split('\n');
1426    }
1427  else if (event->mimeData()->hasUrls())
1428    {
1429      foreach (QUrl url, event->mimeData()->urls())
1430        list.append(url.toLocalFile());
1431    }
1432
1433  foreach (QString entry, list)
1434    {
1435      QString key = entry.trimmed();
1436
1437      if (!key.isEmpty())
1438        {
1439          const QUrl url (key);
1440
1441          if (url.scheme () == "file")
1442            key = QUrl::fromPercentEncoding (url.path().toUtf8());
1443
1444          dynamic_cast<MyApp*> (QApplication::instance ())->addTorrent (key);
1445        }
1446    }
1447}
1448
1449/***
1450****
1451***/
1452
1453void
1454TrMainWindow :: contextMenuEvent (QContextMenuEvent * event)
1455{
1456  QMenu * menu = new QMenu (this);
1457
1458  menu->addAction (ui.action_Properties);
1459  menu->addAction (ui.action_OpenFolder);
1460
1461  QAction * sep = new QAction (menu);
1462  sep->setSeparator (true);
1463  menu->addAction (sep);
1464  menu->addAction (ui.action_Start);
1465  menu->addAction (ui.action_StartNow);
1466  menu->addAction (ui.action_Announce);
1467  QMenu * queueMenu = menu->addMenu (tr ("Queue"));
1468    queueMenu->addAction (ui.action_QueueMoveTop);
1469    queueMenu->addAction (ui.action_QueueMoveUp);
1470    queueMenu->addAction (ui.action_QueueMoveDown);
1471    queueMenu->addAction (ui.action_QueueMoveBottom);
1472  menu->addAction (ui.action_Pause);
1473
1474  sep = new QAction (menu);
1475  sep->setSeparator (true);
1476  menu->addAction (sep);
1477  menu->addAction (ui.action_Verify);
1478  menu->addAction (ui.action_SetLocation);
1479  menu->addAction (ui.action_CopyMagnetToClipboard);
1480
1481  sep = new QAction (menu);
1482  sep->setSeparator (true);
1483  menu->addAction (sep);
1484  menu->addAction (ui.action_Remove);
1485  menu->addAction (ui.action_Delete);
1486
1487  menu->popup (event->globalPos ());
1488}
Note: See TracBrowser for help on using the repository browser.