source: trunk/qt/mainwin.cc @ 13555

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

(trunk qt) #5059 'Memory leak in the TrMainWindow? :: openTorrent' -- fixed.

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