source: trunk/qt/mainwin.cc @ 13264

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

(trunk, qt) fix #4824 'Open Folder bug' with a patch from rb07

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