source: trunk/qt/mainwin.cc @ 13733

Last change on this file since 13733 was 13733, checked in by jordan, 9 years ago

(qt) #5206 'tray icon cancels logout in KDE' -- fixed with patch from basinilya

  • Property svn:keywords set to Date Rev Author Id
File size: 49.8 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 13733 2013-01-01 20:02:20Z 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(dataReadProgress()), this, SLOT(dataReadProgress()) );
286    connect( &mySession, SIGNAL(dataSendProgress()), this, SLOT(dataSendProgress()) );
287    connect( &mySession, SIGNAL(httpAuthenticationRequired()), this, SLOT(wrongAuthentication()) );
288
289    if( mySession.isServer( ) )
290        myNetworkLabel->hide( );
291    else {
292        connect( &myNetworkTimer, SIGNAL(timeout()), this, SLOT(onNetworkTimer()));
293        myNetworkTimer.start( 1000 );
294    }
295
296    connect( &myRefreshTrayIconTimer, SIGNAL(timeout()), this, SLOT(refreshTrayIcon()) );
297    connect( &myRefreshActionSensitivityTimer, SIGNAL(timeout()), this, SLOT(refreshActionSensitivity()) );
298
299
300    refreshActionSensitivitySoon( );
301    refreshTrayIconSoon( );
302    refreshStatusBar( );
303    refreshTitle( );
304    refreshVisibleCount( );
305}
306
307TrMainWindow :: ~TrMainWindow( )
308{
309}
310
311/****
312*****
313****/
314
315void
316TrMainWindow :: onSessionSourceChanged( )
317{
318    myModel.clear( );
319}
320
321void
322TrMainWindow :: onModelReset( )
323{
324    refreshTitle( );
325    refreshVisibleCount( );
326    refreshActionSensitivitySoon( );
327    refreshStatusBar( );
328    refreshTrayIconSoon( );
329}
330
331/****
332*****
333****/
334
335#define PREF_VARIANTS_KEY "pref-variants-list"
336
337void
338TrMainWindow :: onSetPrefs( )
339{
340    const QVariantList p = sender()->property( PREF_VARIANTS_KEY ).toList( );
341    assert( ( p.size( ) % 2 ) == 0 );
342    for( int i=0, n=p.size(); i<n; i+=2 )
343        myPrefs.set( p[i].toInt(), p[i+1] );
344}
345
346void
347TrMainWindow :: onSetPrefs( bool isChecked )
348{
349    if( isChecked )
350        onSetPrefs( );
351}
352
353#define SHOW_KEY "show-mode"
354
355QWidget *
356TrMainWindow :: createStatusBar( )
357{
358    QMenu * m;
359    QLabel * l;
360    QHBoxLayout * h;
361    QPushButton * p;
362    QActionGroup * a;
363    const int i = style( )->pixelMetric( QStyle::PM_SmallIconSize, 0, this );
364    const QSize smallIconSize( i, i );
365
366    QWidget * top = myStatusBar = new QWidget;
367    h = new QHBoxLayout( top );
368    h->setContentsMargins( HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL, HIG::PAD_SMALL );
369    h->setSpacing( HIG::PAD_SMALL );
370
371        p = myOptionsButton = new TrIconPushButton( this );
372        p->setIcon( QIcon( ":/icons/utilities.png" ) );
373        p->setIconSize( QPixmap( ":/icons/utilities.png" ).size() );
374        p->setFlat( true );
375        p->setMenu( createOptionsMenu( ) );
376        h->addWidget( p );
377
378        p = myAltSpeedButton = new QPushButton( this );
379        p->setIcon( myPrefs.get<bool>(Prefs::ALT_SPEED_LIMIT_ENABLED) ? mySpeedModeOnIcon : mySpeedModeOffIcon );
380        p->setIconSize( QPixmap( ":/icons/alt-limit-on.png" ).size() );
381        p->setCheckable( true );
382        p->setFixedWidth( p->height() );
383        p->setFlat( true );
384        h->addWidget( p );
385        connect( p, SIGNAL(clicked()), this, SLOT(toggleSpeedMode()));
386
387        l = myNetworkLabel = new QLabel;
388        h->addWidget( l );
389
390    h->addStretch( 1 );
391
392        l = myVisibleCountLabel = new QLabel( this );
393        h->addWidget( l );
394
395    h->addStretch( 1 );
396
397        a = new QActionGroup( this );
398        a->addAction( ui.action_TotalRatio );
399        a->addAction( ui.action_TotalTransfer );
400        a->addAction( ui.action_SessionRatio );
401        a->addAction( ui.action_SessionTransfer );
402        m = new QMenu( );
403        m->addAction( ui.action_TotalRatio );
404        m->addAction( ui.action_TotalTransfer );
405        m->addAction( ui.action_SessionRatio );
406        m->addAction( ui.action_SessionTransfer );
407        connect( ui.action_TotalRatio, SIGNAL(triggered()), this, SLOT(showTotalRatio()));
408        connect( ui.action_TotalTransfer, SIGNAL(triggered()), this, SLOT(showTotalTransfer()));
409        connect( ui.action_SessionRatio, SIGNAL(triggered()), this, SLOT(showSessionRatio()));
410        connect( ui.action_SessionTransfer, SIGNAL(triggered()), this, SLOT(showSessionTransfer()));
411        p = myStatsModeButton = new TrIconPushButton( this );
412        p->setIcon( QIcon( ":/icons/ratio.png" ) );
413        p->setIconSize( QPixmap( ":/icons/ratio.png" ).size() );
414        p->setFlat( true );
415        p->setMenu( m );
416        h->addWidget( p );
417        l = myStatsLabel = new QLabel( this );
418        h->addWidget( l );
419
420    h->addStretch( 1 );
421
422        l = myDownloadSpeedLabel = new QLabel( this );
423        const int minimumSpeedWidth = l->fontMetrics().width( Formatter::speedToString(Speed::fromKBps(999.99)));
424        l->setMinimumWidth( minimumSpeedWidth );
425        l->setAlignment( Qt::AlignRight|Qt::AlignVCenter );
426        h->addWidget( l );
427        l = new QLabel( this );
428        l->setPixmap( getStockIcon( "go-down", QStyle::SP_ArrowDown ).pixmap( smallIconSize ) );
429        h->addWidget( l );
430
431    h->addStretch( 1 );
432
433        l = myUploadSpeedLabel = new QLabel;
434        l->setMinimumWidth( minimumSpeedWidth );
435        l->setAlignment( Qt::AlignRight|Qt::AlignVCenter );
436        h->addWidget( l );
437        l = new QLabel;
438        l->setPixmap( getStockIcon( "go-up", QStyle::SP_ArrowUp ).pixmap( smallIconSize ) );
439        h->addWidget( l );
440
441    return top;
442}
443
444QMenu *
445TrMainWindow :: createOptionsMenu( )
446{
447    QMenu * menu;
448    QMenu * sub;
449    QAction * a;
450    QActionGroup * g;
451
452    QList<int> stockSpeeds;
453    stockSpeeds << 5 << 10 << 20 << 30 << 40 << 50 << 75 << 100 << 150 << 200 << 250 << 500 << 750;
454    QList<double> stockRatios;
455    stockRatios << 0.25 << 0.50 << 0.75 << 1 << 1.5 << 2 << 3;
456
457    menu = new QMenu;
458    sub = menu->addMenu( tr( "Limit Download Speed" ) );
459        int currentVal = myPrefs.get<int>( Prefs::DSPEED );
460        g = new QActionGroup( this );
461        a = myDlimitOffAction = sub->addAction( tr( "Unlimited" ) );
462        a->setCheckable( true );
463        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED_ENABLED << false );
464        g->addAction( a );
465        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
466        a = myDlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( currentVal ) ) ) );
467        a->setCheckable( true );
468        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << currentVal << Prefs::DSPEED_ENABLED << true );
469        g->addAction( a );
470        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
471        sub->addSeparator( );
472        foreach( int i, stockSpeeds ) {
473            a = sub->addAction( Formatter::speedToString( Speed::fromKBps( i ) ) );
474            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::DSPEED << i << Prefs::DSPEED_ENABLED << true );
475            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
476        }
477
478    sub = menu->addMenu( tr( "Limit Upload Speed" ) );
479        currentVal = myPrefs.get<int>( Prefs::USPEED );
480        g = new QActionGroup( this );
481        a = myUlimitOffAction = sub->addAction( tr( "Unlimited" ) );
482        a->setCheckable( true );
483        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED_ENABLED << false );
484        g->addAction( a );
485        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
486        a = myUlimitOnAction = sub->addAction( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( currentVal ) ) ) );
487        a->setCheckable( true );
488        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << currentVal << Prefs::USPEED_ENABLED << true );
489        g->addAction( a );
490        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
491        sub->addSeparator( );
492        foreach( int i, stockSpeeds ) {
493            a = sub->addAction( Formatter::speedToString( Speed::fromKBps( i ) ) );
494            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::USPEED << i << Prefs::USPEED_ENABLED << true );
495            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
496        }
497
498    menu->addSeparator( );
499    sub = menu->addMenu( tr( "Stop Seeding at Ratio" ) );
500
501        double d = myPrefs.get<double>( Prefs::RATIO );
502        g = new QActionGroup( this );
503        a = myRatioOffAction = sub->addAction( tr( "Seed Forever" ) );
504        a->setCheckable( true );
505        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO_ENABLED << false );
506        g->addAction( a );
507        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
508        a = myRatioOnAction = sub->addAction( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( d ) ) );
509        a->setCheckable( true );
510        a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << d << Prefs::RATIO_ENABLED << true );
511        g->addAction( a );
512        connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs(bool)) );
513        sub->addSeparator( );
514        foreach( double i, stockRatios ) {
515            a = sub->addAction( Formatter::ratioToString( i ) );
516            a->setProperty( PREF_VARIANTS_KEY, QVariantList() << Prefs::RATIO << i << Prefs::RATIO_ENABLED << true );
517            connect( a, SIGNAL(triggered(bool)), this, SLOT(onSetPrefs()));
518        }
519
520    return menu;
521}
522
523/****
524*****
525****/
526
527void
528TrMainWindow :: setSortPref( int i )
529{
530    myPrefs.set( Prefs::SORT_MODE, SortMode( i ) );
531}
532void TrMainWindow :: onSortByActivityToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ACTIVITY ); }
533void TrMainWindow :: onSortByAgeToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_AGE );      }
534void TrMainWindow :: onSortByETAToggled      ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_ETA );      }
535void TrMainWindow :: onSortByNameToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_NAME );     }
536void TrMainWindow :: onSortByProgressToggled ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_PROGRESS ); }
537void TrMainWindow :: onSortByQueueToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_QUEUE );    }
538void TrMainWindow :: onSortByRatioToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_RATIO );    }
539void TrMainWindow :: onSortBySizeToggled     ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_SIZE );     }
540void TrMainWindow :: onSortByStateToggled    ( bool b ) { if( b ) setSortPref( SortMode::SORT_BY_STATE );    }
541
542void
543TrMainWindow :: setSortAscendingPref( bool b )
544{
545    myPrefs.set( Prefs::SORT_REVERSED, b );
546}
547
548/****
549*****
550****/
551
552void
553TrMainWindow :: showEvent( QShowEvent * event )
554{
555    Q_UNUSED (event);
556
557    ui.action_ShowMainWindow->setChecked(true);
558}
559
560/****
561*****
562****/
563
564void
565TrMainWindow :: hideEvent( QHideEvent * event )
566{
567    Q_UNUSED (event);
568
569    if (!isVisible())
570        ui.action_ShowMainWindow->setChecked(false);
571}
572
573/****
574*****
575****/
576
577void
578TrMainWindow :: onPrefsDestroyed( )
579{
580    myPrefsDialog = 0;
581}
582
583void
584TrMainWindow :: openPreferences( )
585{
586    if( myPrefsDialog == 0 ) {
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        myDetailsDialog = new Details( mySession, myPrefs, myModel, this );
605        connect( myDetailsDialog, SIGNAL(destroyed(QObject*)), this, SLOT(onDetailsDestroyed()));
606    }
607
608    myDetailsDialog->setIds( getSelectedTorrents( ) );
609    myDetailsDialog->show( );
610}
611
612void
613TrMainWindow :: setLocation( )
614{
615    QDialog * d = new RelocateDialog( mySession, myModel, getSelectedTorrents(), this );
616    d->show( );
617}
618
619// Open Folder & select torrent's file or top folder
620#undef HAVE_OPEN_SELECT
621#if defined(Q_OS_WIN)
622# define HAVE_OPEN_SELECT
623static
624void openSelect(const QString& path)
625{
626    const QString explorer = "explorer";
627        QString param;
628        if (!QFileInfo(path).isDir())
629            param = QLatin1String("/select,");
630        param += QDir::toNativeSeparators(path);
631        QProcess::startDetached(explorer, QStringList(param));
632}
633#elif defined(Q_OS_MAC)
634# define HAVE_OPEN_SELECT
635static
636void openSelect(const QString& path)
637{
638    QStringList scriptArgs;
639        scriptArgs << QLatin1String("-e")
640                   << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
641                                         .arg(path);
642        QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
643        scriptArgs.clear();
644        scriptArgs << QLatin1String("-e")
645                   << QLatin1String("tell application \"Finder\" to activate");
646        QProcess::execute("/usr/bin/osascript", scriptArgs);
647}
648#endif
649
650void
651TrMainWindow :: openFolder( )
652{
653    const int torrentId( *getSelectedTorrents().begin() );
654    const Torrent * tor( myModel.getTorrentFromId( torrentId ) );
655    QString path( tor->getPath( ) );
656    const FileList files = tor->files();
657    const QString firstfile = files.at(0).filename;
658    int slashIndex = firstfile.indexOf('/');
659    if (slashIndex > -1) {
660        path = path + "/" + firstfile.left(slashIndex);
661    }
662#ifdef HAVE_OPEN_SELECT
663    else {
664        openSelect( path + "/" + firstfile );
665        return;
666    }
667#endif
668    QDesktopServices :: openUrl( QUrl::fromLocalFile( path ) );
669}
670
671void
672TrMainWindow :: copyMagnetLinkToClipboard( )
673{
674    const int id( *getSelectedTorrents().begin() );
675    mySession.copyMagnetLinkToClipboard( id );
676}
677
678void
679TrMainWindow :: openDonate( )
680{
681    QDesktopServices :: openUrl( QUrl( "http://www.transmissionbt.com/donate.php" ) );
682}
683
684void
685TrMainWindow :: openHelp( )
686{
687    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
688    int major, minor;
689    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
690    char url[128];
691    tr_snprintf( url, sizeof( url ), fmt, major, minor/10 );
692    QDesktopServices :: openUrl( QUrl( url ) );
693}
694
695void
696TrMainWindow :: refreshTitle( )
697{
698    QString title( "Transmission" );
699    const QUrl url( mySession.getRemoteUrl( ) );
700    if( !url.isEmpty() )
701        title += tr( " - %1:%2" ).arg( url.host() ).arg( url.port() );
702    setWindowTitle( title );
703}
704
705void
706TrMainWindow :: refreshVisibleCount( )
707{
708    const int visibleCount( myFilterModel.rowCount( ) );
709    const int totalCount( visibleCount + myFilterModel.hiddenRowCount( ) );
710    QString str;
711    if( visibleCount == totalCount )
712        str = tr( "%Ln Torrent(s)", 0, totalCount );
713    else
714        str = tr( "%L1 of %Ln Torrent(s)", 0, totalCount ).arg( visibleCount );
715    myVisibleCountLabel->setText( str );
716    myVisibleCountLabel->setVisible( totalCount > 0 );
717}
718
719void
720TrMainWindow :: refreshTrayIconSoon( )
721{
722    if( !myRefreshTrayIconTimer.isActive( ) )
723    {
724        myRefreshTrayIconTimer.setSingleShot( true );
725        myRefreshTrayIconTimer.start( 100 );
726    }
727}
728void
729TrMainWindow :: refreshTrayIcon( )
730{
731    const QString idle = tr( "Idle" );
732    const Speed u (myModel.getUploadSpeed());
733    const Speed d (myModel.getDownloadSpeed());
734
735    myTrayIcon.setToolTip( tr( "Transmission\nUp: %1\nDown: %2" )
736                           .arg( u.isZero() ? idle : Formatter::speedToString( u ) )
737                           .arg( d.isZero() ? idle : Formatter::speedToString( d ) ) );
738}
739
740void
741TrMainWindow :: refreshStatusBar( )
742{
743    const Speed up( myModel.getUploadSpeed( ) );
744    const Speed down( myModel.getDownloadSpeed( ) );
745    myUploadSpeedLabel->setText( Formatter:: speedToString( up ) );
746    myDownloadSpeedLabel->setText( Formatter:: speedToString( down ) );
747
748    myNetworkLabel->setVisible( !mySession.isServer( ) );
749
750    const QString mode( myPrefs.getString( Prefs::STATUSBAR_STATS ) );
751    QString str;
752
753    if( mode == "session-ratio" )
754    {
755        str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getStats().ratio ) );
756    }
757    else if( mode == "session-transfer" )
758    {
759        const tr_session_stats& stats( mySession.getStats( ) );
760        str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
761                                      .arg( Formatter:: sizeToString( stats.uploadedBytes ) );
762    }
763    else if( mode == "total-transfer" )
764    {
765        const tr_session_stats& stats( mySession.getCumulativeStats( ) );
766        str = tr( "Down: %1, Up: %2" ).arg( Formatter:: sizeToString( stats.downloadedBytes ) )
767                                      .arg( Formatter:: sizeToString( stats.uploadedBytes ) );
768    }
769    else // default is "total-ratio"
770    {
771        str = tr( "Ratio: %1" ).arg( Formatter:: ratioToString( mySession.getCumulativeStats().ratio ) );
772    }
773
774    myStatsLabel->setText( str );
775}
776
777
778
779void
780TrMainWindow :: refreshActionSensitivitySoon( )
781{
782    if( !myRefreshActionSensitivityTimer.isActive( ) )
783    {
784        myRefreshActionSensitivityTimer.setSingleShot( true );
785        myRefreshActionSensitivityTimer.start( 100 );
786    }
787}
788void
789TrMainWindow :: refreshActionSensitivity( )
790{
791    int selected( 0 );
792    int paused( 0 );
793    int queued( 0 );
794    int selectedAndPaused( 0 );
795    int selectedAndQueued( 0 );
796    int canAnnounce( 0 );
797    const QAbstractItemModel * model( ui.listView->model( ) );
798    const QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
799    const int rowCount( model->rowCount( ) );
800
801    // count how many torrents are selected, paused, etc
802    for( int row=0; row<rowCount; ++row ) {
803        const QModelIndex modelIndex( model->index( row, 0 ) );
804        assert( model == modelIndex.model( ) );
805        const Torrent * tor( model->data( modelIndex, TorrentModel::TorrentRole ).value<const Torrent*>( ) );
806        if( tor ) {
807            const bool isSelected( selectionModel->isSelected( modelIndex ) );
808            const bool isPaused( tor->isPaused( ) );
809            const bool isQueued( tor->isQueued( ) );
810            if( isSelected ) ++selected;
811            if( isQueued ) ++queued;
812            if( isPaused ) ++ paused;
813            if( isSelected && isPaused ) ++selectedAndPaused;
814            if( isSelected && isQueued ) ++selectedAndQueued;
815            if( tor->canManualAnnounce( ) ) ++canAnnounce;
816        }
817    }
818
819    const bool haveSelection( selected > 0 );
820    ui.action_Verify->setEnabled( haveSelection );
821    ui.action_Remove->setEnabled( haveSelection );
822    ui.action_Delete->setEnabled( haveSelection );
823    ui.action_Properties->setEnabled( haveSelection );
824    ui.action_DeselectAll->setEnabled( haveSelection );
825    ui.action_SetLocation->setEnabled( haveSelection );
826
827    const bool oneSelection( selected == 1 );
828    ui.action_OpenFolder->setEnabled( oneSelection && mySession.isLocal( ) );
829    ui.action_CopyMagnetToClipboard->setEnabled( oneSelection );
830
831    ui.action_SelectAll->setEnabled( selected < rowCount );
832    ui.action_StartAll->setEnabled( paused > 0 );
833    ui.action_PauseAll->setEnabled( paused < rowCount );
834    ui.action_Start->setEnabled( selectedAndPaused > 0 );
835    ui.action_StartNow->setEnabled( selectedAndPaused + selectedAndQueued > 0 );
836    ui.action_Pause->setEnabled( selectedAndPaused < selected );
837    ui.action_Announce->setEnabled( selected > 0 && ( canAnnounce == selected ) );
838
839    ui.action_QueueMoveTop->setEnabled( haveSelection );
840    ui.action_QueueMoveUp->setEnabled( haveSelection );
841    ui.action_QueueMoveDown->setEnabled( haveSelection );
842    ui.action_QueueMoveBottom->setEnabled( haveSelection );
843
844    if( myDetailsDialog )
845        myDetailsDialog->setIds( getSelectedTorrents( ) );
846}
847
848/**
849***
850**/
851
852void
853TrMainWindow :: clearSelection( )
854{
855    ui.action_DeselectAll->trigger( );
856}
857
858QSet<int>
859TrMainWindow :: getSelectedTorrents( ) const
860{
861    QSet<int> ids;
862
863    foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
864    {
865        const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>( ) );
866        ids.insert( tor->id( ) );
867    }
868
869    return ids;
870}
871
872void
873TrMainWindow :: startSelected( )
874{
875    mySession.startTorrents( getSelectedTorrents( ) );
876}
877void
878TrMainWindow :: startSelectedNow( )
879{
880    mySession.startTorrentsNow( getSelectedTorrents( ) );
881}
882void
883TrMainWindow :: pauseSelected( )
884{
885    mySession.pauseTorrents( getSelectedTorrents( ) );
886}
887void
888TrMainWindow :: queueMoveTop( )
889{
890    mySession.queueMoveTop( getSelectedTorrents( ) );
891}
892void
893TrMainWindow :: queueMoveUp( )
894{
895    mySession.queueMoveUp( getSelectedTorrents( ) );
896}
897void
898TrMainWindow :: queueMoveDown( )
899{
900    mySession.queueMoveDown( getSelectedTorrents( ) );
901}
902void
903TrMainWindow :: queueMoveBottom( )
904{
905    mySession.queueMoveBottom( getSelectedTorrents( ) );
906}
907void
908TrMainWindow :: startAll( )
909{
910    mySession.startTorrents( );
911}
912void
913TrMainWindow :: pauseAll( )
914{
915    mySession.pauseTorrents( );
916}
917void
918TrMainWindow :: removeSelected( )
919{
920    removeTorrents( false );
921}
922void
923TrMainWindow :: deleteSelected( )
924{
925    removeTorrents( true );
926}
927void
928TrMainWindow :: verifySelected( )
929{
930    mySession.verifyTorrents( getSelectedTorrents( ) );
931}
932void
933TrMainWindow :: reannounceSelected( )
934{
935    mySession.reannounceTorrents( getSelectedTorrents( ) );
936}
937
938/**
939***
940**/
941
942void TrMainWindow :: showTotalRatio      ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-ratio"); }
943void TrMainWindow :: showTotalTransfer   ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "total-transfer"); }
944void TrMainWindow :: showSessionRatio    ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-ratio"); }
945void TrMainWindow :: showSessionTransfer ( ) { myPrefs.set( Prefs::STATUSBAR_STATS, "session-transfer"); }
946
947/**
948***
949**/
950
951void
952TrMainWindow :: setCompactView( bool visible )
953{
954    myPrefs.set( Prefs :: COMPACT_VIEW, visible );
955}
956void
957TrMainWindow :: toggleSpeedMode( )
958{
959    bool mode;
960   
961    myPrefs.toggleBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
962    mode = myPrefs.get<bool>( Prefs::ALT_SPEED_LIMIT_ENABLED );
963    myAltSpeedAction->setIcon( mode ? mySpeedModeOnIcon : mySpeedModeOffIcon );
964}
965void
966TrMainWindow :: setToolbarVisible( bool visible )
967{
968    myPrefs.set( Prefs::TOOLBAR, visible );
969}
970void
971TrMainWindow :: setFilterbarVisible( bool visible )
972{
973    myPrefs.set( Prefs::FILTERBAR, visible );
974}
975void
976TrMainWindow :: setStatusbarVisible( bool visible )
977{
978    myPrefs.set( Prefs::STATUSBAR, visible );
979}
980
981/**
982***
983**/
984
985void
986TrMainWindow :: toggleWindows( bool doShow )
987{
988    if( !doShow )
989    {
990        hide( );
991    }
992    else
993    {
994        if ( !isVisible( ) ) show( );
995        if ( isMinimized( ) ) showNormal( );
996        //activateWindow( );
997        raise( );
998        QApplication::setActiveWindow( this );
999    }
1000}
1001
1002void
1003TrMainWindow :: trayActivated( QSystemTrayIcon::ActivationReason reason )
1004{
1005    if( reason == QSystemTrayIcon::Trigger )
1006    {
1007        if( isMinimized ( ) )
1008            toggleWindows( true );
1009        else
1010            toggleWindows( !isVisible() );
1011    }
1012}
1013
1014
1015void
1016TrMainWindow :: refreshPref( int key )
1017{
1018    bool b;
1019    int i;
1020    QString str;
1021
1022    switch( key )
1023    {
1024        case Prefs::STATUSBAR_STATS:
1025            str = myPrefs.getString( key );
1026            ui.action_TotalRatio->setChecked     ( str == "total-ratio" );
1027            ui.action_TotalTransfer->setChecked  ( str == "total-transfer" );
1028            ui.action_SessionRatio->setChecked   ( str == "session-ratio" );
1029            ui.action_SessionTransfer->setChecked( str == "session-transfer" );
1030            refreshStatusBar( );
1031            break;
1032
1033        case Prefs::SORT_REVERSED:
1034            ui.action_ReverseSortOrder->setChecked( myPrefs.getBool( key ) );
1035            break;
1036
1037        case Prefs::SORT_MODE:
1038            i = myPrefs.get<SortMode>(key).mode( );
1039            ui.action_SortByActivity->setChecked ( i == SortMode::SORT_BY_ACTIVITY );
1040            ui.action_SortByAge->setChecked      ( i == SortMode::SORT_BY_AGE );
1041            ui.action_SortByETA->setChecked      ( i == SortMode::SORT_BY_ETA );
1042            ui.action_SortByName->setChecked     ( i == SortMode::SORT_BY_NAME );
1043            ui.action_SortByProgress->setChecked ( i == SortMode::SORT_BY_PROGRESS );
1044            ui.action_SortByQueue->setChecked    ( i == SortMode::SORT_BY_QUEUE );
1045            ui.action_SortByRatio->setChecked    ( i == SortMode::SORT_BY_RATIO );
1046            ui.action_SortBySize->setChecked     ( i == SortMode::SORT_BY_SIZE );
1047            ui.action_SortByState->setChecked    ( i == SortMode::SORT_BY_STATE );
1048            break;
1049
1050        case Prefs::DSPEED_ENABLED:
1051            (myPrefs.get<bool>(key) ? myDlimitOnAction : myDlimitOffAction)->setChecked( true );
1052            break;
1053
1054        case Prefs::DSPEED:
1055            myDlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( myPrefs.get<int>(key) ) ) ) );
1056            break;
1057
1058        case Prefs::USPEED_ENABLED:
1059            (myPrefs.get<bool>(key) ? myUlimitOnAction : myUlimitOffAction)->setChecked( true );
1060            break;
1061
1062        case Prefs::USPEED:
1063            myUlimitOnAction->setText( tr( "Limited at %1" ).arg( Formatter::speedToString( Speed::fromKBps( myPrefs.get<int>(key) ) ) ) );
1064            break;
1065
1066        case Prefs::RATIO_ENABLED:
1067            (myPrefs.get<bool>(key) ? myRatioOnAction : myRatioOffAction)->setChecked( true );
1068            break;
1069
1070        case Prefs::RATIO:
1071            myRatioOnAction->setText( tr( "Stop at Ratio (%1)" ).arg( Formatter::ratioToString( myPrefs.get<double>(key) ) ) );
1072            break;
1073
1074        case Prefs::FILTERBAR:
1075            b = myPrefs.getBool( key );
1076            myFilterBar->setVisible( b );
1077            ui.action_Filterbar->setChecked( b );
1078            break;
1079
1080        case Prefs::STATUSBAR:
1081            b = myPrefs.getBool( key );
1082            myStatusBar->setVisible( b );
1083            ui.action_Statusbar->setChecked( b );
1084            break;
1085
1086        case Prefs::TOOLBAR:
1087            b = myPrefs.getBool( key );
1088            ui.toolBar->setVisible( b );
1089            ui.action_Toolbar->setChecked( b );
1090            break;
1091
1092        case Prefs::SHOW_TRAY_ICON:
1093            b = myPrefs.getBool( key );
1094            ui.action_TrayIcon->setChecked( b );
1095            myTrayIcon.setVisible( b );
1096            dynamic_cast<MyApp*>(QCoreApplication::instance())->setQuitOnLastWindowClosed(!b);
1097            refreshTrayIconSoon( );
1098            break;
1099
1100        case Prefs::COMPACT_VIEW: {
1101            QItemSelectionModel * selectionModel( ui.listView->selectionModel( ) );
1102            const QItemSelection selection( selectionModel->selection( ) );
1103            const QModelIndex currentIndex( selectionModel->currentIndex( ) );
1104            b = myPrefs.getBool( key );
1105            ui.action_CompactView->setChecked( b );
1106            ui.listView->setItemDelegate( b ? myTorrentDelegateMin : myTorrentDelegate );
1107            selectionModel->clear( );
1108            ui.listView->reset( ); // force the rows to resize
1109            selectionModel->select( selection, QItemSelectionModel::Select );
1110            selectionModel->setCurrentIndex( currentIndex, QItemSelectionModel::NoUpdate );
1111            break;
1112        }
1113
1114        case Prefs::MAIN_WINDOW_X:
1115        case Prefs::MAIN_WINDOW_Y:
1116        case Prefs::MAIN_WINDOW_WIDTH:
1117        case Prefs::MAIN_WINDOW_HEIGHT:
1118            setGeometry( myPrefs.getInt( Prefs::MAIN_WINDOW_X ),
1119                         myPrefs.getInt( Prefs::MAIN_WINDOW_Y ),
1120                         myPrefs.getInt( Prefs::MAIN_WINDOW_WIDTH ),
1121                         myPrefs.getInt( Prefs::MAIN_WINDOW_HEIGHT ) );
1122            break;
1123
1124        case Prefs :: ALT_SPEED_LIMIT_ENABLED:
1125        case Prefs :: ALT_SPEED_LIMIT_UP:
1126        case Prefs :: ALT_SPEED_LIMIT_DOWN: {
1127            b = myPrefs.getBool( Prefs :: ALT_SPEED_LIMIT_ENABLED );
1128            myAltSpeedButton->setChecked( b );
1129            myAltSpeedButton->setIcon( b ? mySpeedModeOnIcon : mySpeedModeOffIcon );
1130            const QString fmt = b ? tr( "Click to disable Temporary Speed Limits\n(%1 down, %2 up)" )
1131                                  : tr( "Click to enable Temporary Speed Limits\n(%1 down, %2 up)" );
1132            const Speed d = Speed::fromKBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_DOWN ) );
1133            const Speed u = Speed::fromKBps( myPrefs.getInt( Prefs::ALT_SPEED_LIMIT_UP ) );
1134            myAltSpeedButton->setToolTip( fmt.arg( Formatter::speedToString( d ) )
1135                                             .arg( Formatter::speedToString( u ) ) );
1136            break;
1137        }
1138
1139        default:
1140            break;
1141    }
1142}
1143
1144/***
1145****
1146***/
1147
1148void
1149TrMainWindow :: newTorrent( )
1150{
1151    MakeDialog * dialog = new MakeDialog( mySession, this );
1152    dialog->show( );
1153}
1154
1155void
1156TrMainWindow :: openTorrent( )
1157{
1158    QFileDialog * myFileDialog;
1159    myFileDialog = new QFileDialog( this,
1160                                    tr( "Open Torrent" ),
1161                                    myPrefs.getString( Prefs::OPEN_DIALOG_FOLDER ),
1162                                    tr( "Torrent Files (*.torrent);;All Files (*.*)" ) );
1163    myFileDialog->setFileMode( QFileDialog::ExistingFiles );
1164    myFileDialog->setAttribute( Qt::WA_DeleteOnClose );
1165
1166    QCheckBox * button = new QCheckBox( tr( "Show &options dialog" ) );
1167    button->setChecked( myPrefs.getBool( Prefs::OPTIONS_PROMPT ) );
1168    QGridLayout * layout = dynamic_cast<QGridLayout*>(myFileDialog->layout());
1169    layout->addWidget( button, layout->rowCount( ), 0, 1, -1, Qt::AlignLeft );
1170    myFileDialogOptionsCheck = button;
1171
1172    connect( myFileDialog, SIGNAL(filesSelected(const QStringList&)),
1173             this, SLOT(addTorrents(const QStringList&)));
1174
1175    myFileDialog->show( );
1176}
1177
1178void
1179TrMainWindow :: openURL( )
1180{
1181    QString str = QApplication::clipboard()->text( QClipboard::Selection );
1182
1183    if( !AddData::isSupported( str ) )
1184        str = QApplication::clipboard()->text( QClipboard::Clipboard );
1185
1186    if( !AddData::isSupported( str ) )
1187        str.clear();
1188
1189    openURL( str );
1190}
1191
1192void
1193TrMainWindow :: openURL( QString url )
1194{
1195    bool ok;
1196    const QString key = QInputDialog::getText( this,
1197                                               tr( "Open Link" ),
1198                                               tr( "Open URL or Magnet Link" ),
1199                                               QLineEdit::Normal,
1200                                               url,
1201                                               &ok,
1202                                               Qt::WindowStaysOnTopHint );
1203    if( ok && !key.isEmpty( ) )
1204        mySession.addTorrent( key );
1205}
1206
1207void
1208TrMainWindow :: addTorrents( const QStringList& filenames )
1209{
1210    foreach( const QString& filename, filenames )
1211        addTorrent( filename );
1212}
1213
1214void
1215TrMainWindow :: addTorrent( const QString& filename )
1216{
1217    if( !myFileDialogOptionsCheck->isChecked( ) ) {
1218        mySession.addTorrent( filename );
1219        QApplication :: alert ( this );
1220    } else {
1221        Options * o = new Options( mySession, myPrefs, filename, this );
1222        o->show( );
1223        QApplication :: alert( o );
1224    }
1225}
1226
1227void
1228TrMainWindow :: removeTorrents( const bool deleteFiles )
1229{
1230    QSet<int> ids;
1231    QMessageBox msgBox( this );
1232    QString primary_text, secondary_text;
1233    int incomplete = 0;
1234    int connected  = 0;
1235    int count;
1236
1237    foreach( QModelIndex index, ui.listView->selectionModel( )->selectedRows( ) )
1238    {
1239        const Torrent * tor( index.data( TorrentModel::TorrentRole ).value<const Torrent*>( ) );
1240        ids.insert( tor->id( ) );
1241        if( tor->connectedPeers( ) )
1242            ++connected;
1243        if( !tor->isDone( ) )
1244            ++incomplete;
1245    }
1246
1247    if( ids.isEmpty() )
1248        return;
1249    count = ids.size();
1250
1251    if( !deleteFiles )
1252    {
1253        primary_text = ( count == 1 )
1254            ? tr( "Remove torrent?" )
1255            : tr( "Remove %1 torrents?" ).arg( count );
1256    }
1257    else
1258    {
1259        primary_text = ( count == 1 )
1260            ? tr( "Delete this torrent's downloaded files?" )
1261            : tr( "Delete these %1 torrents' downloaded files?" ).arg( count );
1262    }
1263
1264    if( !incomplete && !connected )
1265    {
1266        secondary_text = ( count == 1 )
1267            ? tr( "Once removed, continuing the transfer will require the torrent file or magnet link." )
1268            : tr( "Once removed, continuing the transfers will require the torrent files or magnet links." );
1269    }
1270    else if( count == incomplete )
1271    {
1272        secondary_text = ( count == 1 )
1273            ? tr( "This torrent has not finished downloading." )
1274            : tr( "These torrents have not finished downloading." );
1275    }
1276    else if( count == connected )
1277    {
1278        secondary_text = ( count == 1 )
1279            ? tr( "This torrent is connected to peers." )
1280            : tr( "These torrents are connected to peers." );
1281    }
1282    else
1283    {
1284        if( connected )
1285        {
1286            secondary_text = ( connected == 1 )
1287                ? tr( "One of these torrents is connected to peers." )
1288                : tr( "Some of these torrents are connected to peers." );
1289        }
1290
1291        if( connected && incomplete )
1292        {
1293            secondary_text += "\n";
1294        }
1295
1296        if( incomplete )
1297        {
1298            secondary_text += ( incomplete == 1 )
1299                ? tr( "One of these torrents has not finished downloading." )
1300                : tr( "Some of these torrents have not finished downloading." );
1301        }
1302    }
1303
1304    msgBox.setWindowTitle( QString(" ") );
1305    msgBox.setText( QString( "<big><b>%1</big></b>" ).arg( primary_text ) );
1306    msgBox.setInformativeText( secondary_text );
1307    msgBox.setStandardButtons( QMessageBox::Ok | QMessageBox::Cancel );
1308    msgBox.setDefaultButton( QMessageBox::Cancel );
1309    msgBox.setIcon( QMessageBox::Question );
1310    /* hack needed to keep the dialog from being too narrow */
1311    QGridLayout* layout = (QGridLayout*)msgBox.layout();
1312    QSpacerItem* spacer = new QSpacerItem( 450, 0, QSizePolicy::Minimum, QSizePolicy::Expanding );
1313    layout->addItem( spacer, layout->rowCount(), 0, 1, layout->columnCount() );
1314
1315    if( msgBox.exec() == QMessageBox::Ok )
1316    {
1317        ui.listView->selectionModel()->clear();
1318        mySession.removeTorrents( ids, deleteFiles );
1319    }
1320}
1321
1322/***
1323****
1324***/
1325
1326void
1327TrMainWindow :: updateNetworkIcon( )
1328{
1329    const time_t now = time( NULL );
1330    const int period = 3;
1331    const time_t secondsSinceLastSend = now - myLastSendTime;
1332    const time_t secondsSinceLastRead = now - myLastReadTime;
1333    const bool isSending = secondsSinceLastSend <= period;
1334    const bool isReading = secondsSinceLastRead <= period;
1335    const char * key;
1336
1337    if( isSending && isReading )
1338        key = "network-transmit-receive";
1339    else if( isSending )
1340        key = "network-transmit";
1341    else if( isReading )
1342        key = "network-receive";
1343    else
1344        key = "network-idle";
1345
1346    QIcon icon = getStockIcon( key, QStyle::SP_DriveNetIcon );
1347    QPixmap pixmap = icon.pixmap ( 16, 16 );
1348    myNetworkLabel->setPixmap( pixmap );
1349
1350    QString tip;
1351    const QString url = mySession.getRemoteUrl().host();
1352    if( !myLastReadTime )
1353      tip = tr( "Server '%1' has not responded yet" ).arg (url);
1354    else if( secondsSinceLastRead < 60 )
1355      tip = tr( "Server '%1' is responding" ).arg (url);
1356    else if( secondsSinceLastRead < (60*10) )
1357      tip = tr( "Server '%1' last responded %2 ago" ).arg(url).arg(Formatter::timeToString(secondsSinceLastRead));
1358    else
1359      tip = tr( "Server '%1' is not responding" ).arg (url);
1360    myNetworkLabel->setToolTip (tip);
1361}
1362
1363void
1364TrMainWindow :: onNetworkTimer( )
1365{
1366    updateNetworkIcon( );
1367}
1368
1369void
1370TrMainWindow :: dataReadProgress( )
1371{
1372    myLastReadTime = time( NULL );
1373    updateNetworkIcon( );
1374}
1375
1376void
1377TrMainWindow :: dataSendProgress( )
1378{
1379    myLastSendTime = time( NULL );
1380    updateNetworkIcon( );
1381}
1382
1383void
1384TrMainWindow :: wrongAuthentication( )
1385{
1386    mySession.stop( );
1387    mySessionDialog->show( );
1388}
1389
1390/***
1391****
1392***/
1393
1394void
1395TrMainWindow :: dragEnterEvent( QDragEnterEvent * event )
1396{
1397    const QMimeData * mime = event->mimeData( );
1398
1399    if( mime->hasFormat("application/x-bittorrent")
1400            || mime->text().trimmed().endsWith(".torrent", Qt::CaseInsensitive)
1401            || mime->text().startsWith("magnet:", Qt::CaseInsensitive) )
1402        event->acceptProposedAction();
1403}
1404
1405void
1406TrMainWindow :: dropEvent( QDropEvent * event )
1407{
1408    QString key = event->mimeData()->text().trimmed();
1409
1410    const QUrl url( key );
1411    if( url.scheme() == "file" )
1412        key = QUrl::fromPercentEncoding( url.path().toUtf8( ) );
1413
1414    dynamic_cast<MyApp*>(QApplication::instance())->addTorrent( key );
1415}
1416
1417/***
1418****
1419***/
1420
1421void
1422TrMainWindow :: contextMenuEvent( QContextMenuEvent * event )
1423{
1424    QMenu * menu = new QMenu( this );
1425
1426    menu->addAction( ui.action_Properties );
1427    menu->addAction( ui.action_OpenFolder );
1428
1429    QAction * sep = new QAction( this );
1430    sep->setSeparator( true );
1431    menu->addAction( sep );
1432    menu->addAction( ui.action_Start );
1433    menu->addAction( ui.action_StartNow );
1434    menu->addAction( ui.action_Announce );
1435    QMenu * queueMenu = menu->addMenu( tr( "Queue" ) );
1436        queueMenu->addAction( ui.action_QueueMoveTop );
1437        queueMenu->addAction( ui.action_QueueMoveUp );
1438        queueMenu->addAction( ui.action_QueueMoveDown );
1439        queueMenu->addAction( ui.action_QueueMoveBottom );
1440    menu->addAction( ui.action_Pause );
1441
1442    sep = new QAction( this );
1443    sep->setSeparator( true );
1444    menu->addAction( sep );
1445    menu->addAction( ui.action_Verify );
1446    menu->addAction( ui.action_SetLocation );
1447    menu->addAction( ui.action_CopyMagnetToClipboard );
1448
1449    sep = new QAction( this );
1450    sep->setSeparator( true );
1451    menu->addAction( sep );
1452    menu->addAction( ui.action_Remove );
1453    menu->addAction( ui.action_Delete );
1454
1455    menu->popup( event->globalPos( ) );
1456}
Note: See TracBrowser for help on using the repository browser.