source: trunk/qt/mainwin.cc @ 13883

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

(qt) #4076 'free space indicator' -- added.

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