source: trunk/wx/torrent-list.cc @ 6792

Last change on this file since 6792 was 6792, checked in by charles, 13 years ago

(wx) get the wxWidgets port building again. :)

  • Property svn:keywords set to Date Rev Author Id
File size: 20.8 KB
Line 
1/*
2 * Xmission - a cross-platform bittorrent client
3 * Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 2 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 *
18 * $Id: torrent-list.cc 6792 2008-09-19 17:17:34Z charles $
19 */
20
21#include <algorithm>
22#include <wx/intl.h>
23#include <torrent-list.h>
24#include <libtransmission/utils.h>
25
26namespace
27{
28    typedef std::vector<tr_torrent*> torrents_t;
29
30    enum
31    {
32        COL_POSITION,
33        COL_PERCENT_DONE,
34        COL_DOWNLOAD_SPEED,
35        COL_ETA,
36        COL_HASH,
37        COL_NAME,
38        COL_PEERS,
39        COL_RATIO,
40        COL_RECEIVED,
41        COL_REMAINING,
42        COL_SEEDS,
43        COL_SENT,
44        COL_SIZE,
45        COL_STATE,
46        COL_STATUS,
47        COL_TOTAL,
48        COL_UPLOAD_SPEED,
49        N_COLS
50    };
51
52    const wxString columnKeys[N_COLS] =
53    {
54        _T("position"),
55        _T("done"),
56        _T("download-speed"),
57        _T("eta"),
58        _T("hash"),
59        _T("name"),
60        _T("peers"),
61        _T("ratio"),
62        _T("received"),
63        _T("remaining"),
64        _T("seeds"),
65        _T("sent"),
66        _T("size"),
67        _T("state"),
68        _T("status"),
69        _T("total"),
70        _T("upload-speed")
71    };
72
73    int getTorrentColumn( const wxString& key )
74    {
75        typedef std::map<wxString,int> string2key_t;
76        static string2key_t columns;
77
78        if( columns.empty() )
79        {
80            columns[_T("position")]        = COL_POSITION;
81            columns[_T("done")]            = COL_PERCENT_DONE;
82            columns[_T("download-speed")]  = COL_DOWNLOAD_SPEED;
83            columns[_T("eta")]             = COL_ETA;
84            columns[_T("hash")]            = COL_HASH;
85            columns[_T("name")]            = COL_NAME;
86            columns[_T("peers")]           = COL_PEERS;
87            columns[_T("ratio")]           = COL_RATIO;
88            columns[_T("received")]        = COL_RECEIVED;
89            columns[_T("remaining")]       = COL_REMAINING;
90            columns[_T("seeds")]           = COL_SEEDS; 
91            columns[_T("sent")]            = COL_SENT; 
92            columns[_T("size")]            = COL_SIZE; 
93            columns[_T("state")]           = COL_STATE; 
94            columns[_T("status")]          = COL_STATUS; 
95            columns[_T("total")]           = COL_TOTAL; 
96            columns[_T("upload-speed")]    = COL_UPLOAD_SPEED;
97        }
98
99        int i = -1;
100        string2key_t::const_iterator it = columns.find( key );
101        if( it != columns.end() )
102            i = it->second;
103
104        return i;
105    }
106
107    typedef std::vector<int> int_v;
108
109    int_v getTorrentColumns( wxConfig * config )
110    {
111        const wxString key = _T("torrent-list-columns");
112        wxString columnStr;
113        if( !config->Read( key, &columnStr, _T("name|download-speed|upload-speed|eta|peers|size|done|status|seeds") ) )
114            config->Write( key, columnStr );
115
116        int_v cols;
117        while( !columnStr.IsEmpty() )
118        {
119            const wxString key = columnStr.BeforeFirst(_T('|'));
120            columnStr.Remove( 0, key.Len() + 1 );
121            cols.push_back( getTorrentColumn( key ) );
122        }
123        return cols;
124    }
125
126    int bestDecimal( double num ) {
127        if ( num < 10 ) return 2;
128        if ( num < 100 ) return 1;
129        return 0;
130    }
131
132    wxString toWxStr( const std::string& s )
133    {
134        return wxString( s.c_str(), wxConvUTF8 );
135    }
136
137    wxString toWxStr( const char * s )
138    {
139        return wxString( s, wxConvUTF8 );
140    }
141
142    wxString getReadableSize( uint64_t size )
143    {
144        int i;
145        static const char *sizestrs[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
146        for ( i=0; size>>10; ++i )
147            size = size >> 10;
148        char buf[512];
149        tr_snprintf( buf, sizeof(buf), "%.*f %s", bestDecimal(size), (double)size, sizestrs[i] );
150        return toWxStr( buf );
151    }
152
153    wxString getReadableSize( float f )
154    {
155        return getReadableSize( (uint64_t)f );
156    }
157
158    wxString getReadableSpeed( float kib_sec )
159    {
160        wxString xstr = getReadableSize(1024*kib_sec);
161        xstr += _T("/s");
162        return xstr;
163    }
164
165    wxString getReadableTime( int i /*seconds*/ )  /*FIXME*/
166    {
167        const int s = i % 60; i /= 60;
168        const int m = i % 60; i /= 60;
169        const int h = i;
170        return wxString::Format( _T("%d:%02d:%02d"), h, m, s );
171    }
172}
173
174enum
175{
176    TORRENT_LIST_CTRL = 1000
177};
178
179BEGIN_EVENT_TABLE(TorrentListCtrl, wxListCtrl)
180    EVT_LIST_COL_CLICK( TORRENT_LIST_CTRL, TorrentListCtrl::OnSort )
181    EVT_LIST_ITEM_SELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemSelected )
182    EVT_LIST_ITEM_DESELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemDeselected )
183END_EVENT_TABLE()
184
185
186
187TorrentListCtrl :: TorrentListCtrl( tr_handle       * handle,
188                                    wxConfig        * config,
189                                    wxWindow        * parent,
190                                    const wxPoint   & pos,
191                                    const wxSize    & size):
192    wxListCtrl( parent, TORRENT_LIST_CTRL, pos, size, wxLC_REPORT|wxLC_HRULES ),
193    myHandle( handle ),
194    myConfig( config )
195{
196    wxString sortColStr;
197    myConfig->Read( _T("torrent-sort-column"), &sortColStr, columnKeys[COL_NAME] );
198    prevSortCol = getTorrentColumn( sortColStr );
199    bool descending;
200    myConfig->Read( _T("torrent-sort-is-descending"), &descending, FALSE );
201    if( descending )
202        prevSortCol = -prevSortCol;
203    Rebuild ();
204}
205
206TorrentListCtrl :: ~TorrentListCtrl()
207{
208}
209
210void
211TorrentListCtrl :: SetCell( int item, int column, const wxString& xstr )
212{
213    wxListItem i;
214    i.SetId( item );
215    i.SetColumn( column );
216    i.SetMask( wxLIST_MASK_TEXT );
217    GetItem( i );
218    if( i.GetText() != xstr )
219        SetItem( item, column, xstr );
220}
221
222void
223TorrentListCtrl :: RefreshTorrent( tr_torrent   * tor,
224                                   int            myTorrents_index,
225                                   const int_v  & cols )
226{
227    int row = -1;
228    int col = 0;
229    char buf[512];
230    std::string str;
231    const tr_stat * s = getStat( tor );
232    const tr_info * info = tr_torrentInfo( tor );
233
234    for( int_v::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
235    {
236        wxString xstr;
237
238        switch( *it )
239        {
240            case COL_POSITION:
241                tr_snprintf( buf, sizeof(buf), "%d", 666 );
242                xstr = toWxStr( buf );
243                break;
244
245            case COL_PERCENT_DONE:
246                tr_snprintf( buf, sizeof(buf), "%d%%", (int)(s->percentDone*100.0) );
247                xstr = toWxStr( buf );
248                break;
249
250            case COL_DOWNLOAD_SPEED:
251                if( s->rateDownload > 0.01 )
252                    xstr = getReadableSpeed( s->rateDownload );
253                else
254                    xstr.Clear( );
255                break;
256
257            case COL_ETA:
258                if( (int)(s->percentDone*100) >= 100 )
259                    xstr.Clear ();
260                else if( s->eta < 0 )
261                    xstr = toWxStr( "\xE2\x88\x9E" ); /* infinity, in utf-8 */
262                else
263                    xstr = getReadableTime( s->eta );
264                break;
265               
266            case COL_HASH:
267                xstr = toWxStr( info->hashString );
268                break;
269
270            case COL_NAME:
271                xstr = toWxStr( info->name );
272                break;
273
274            case COL_PEERS:
275                xstr = wxString::Format( _("%d (%d)"), s->peersConnected, s->leechers );
276                break;
277
278            case COL_RATIO:
279                xstr = wxString::Format( _T("%%%d"), (int)(s->uploadedEver / (double)s->downloadedEver) );
280                break;
281
282            case COL_RECEIVED:
283                xstr = getReadableSize( s->downloadedEver );
284                break;
285
286            case COL_REMAINING:
287                xstr = getReadableSize( s->leftUntilDone );
288                break;
289
290            case COL_SEEDS:
291                if( s->seeders > 0 )
292                    xstr = wxString::Format( _T("%d"), s->seeders );
293                else
294                    xstr.Clear ();
295                break;
296
297            case COL_SENT:
298                xstr = getReadableSize( s->uploadedEver );
299                break;
300
301            case COL_SIZE:
302                xstr = getReadableSize( info->totalSize );
303                break;
304
305            case COL_STATE: /* FIXME: divine the meaning of these two columns */
306            case COL_STATUS:
307                switch( s->status ) {
308                    case TR_STATUS_STOPPED:     xstr = _("Stopped"); break;
309                    case TR_STATUS_CHECK:       xstr = wxString::Format ( _("Checking Files (%.0f)"), s->recheckProgress );  break;
310                    case TR_STATUS_CHECK_WAIT:  xstr = _("Waiting to Check"); break;
311                    case TR_STATUS_DOWNLOAD:    xstr = _("Downloading"); break;
312                    case TR_STATUS_SEED:        xstr = _("Seeding"); break;
313                    default: assert( 0 );
314                }
315                break;
316
317            case COL_TOTAL:
318                xstr = _T("Fixme");
319                break;
320
321            case COL_UPLOAD_SPEED:
322                if( s->rateUpload > 0.01 )
323                    xstr = getReadableSpeed( s->rateUpload );
324                else
325                    xstr.Clear( );
326                break;
327
328            default:
329                xstr = _T("Fixme");
330        }
331
332        if( col )
333            SetCell( row, col++, xstr );
334        else {
335            // first column... find the right row to put the info in.
336            // if the torrent's in the list already, update that row.
337            // otherwise, add a new row.
338            if( row < 0 ) {
339                str2int_t::const_iterator it = myHashToItem.find( info->hashString );
340                if( it != myHashToItem.end() ) {
341                    row = it->second;
342                }
343            }
344            if( row >= 0 ) {
345                SetCell( row, col++, xstr );
346            }
347            else {
348                row = InsertItem( GetItemCount(), xstr );
349                col = 1;
350                myHashToItem[info->hashString] = row;
351                SetItemData( row, myTorrents_index );
352            }
353        }
354    }
355}
356
357/***
358****
359***/
360
361void
362TorrentListCtrl :: OnSort( wxListEvent& event )
363{
364    const int_v  cols = getTorrentColumns( myConfig );
365    const int key = cols[ event.GetColumn() ];
366    Sort( key );
367}
368
369void
370TorrentListCtrl :: OnItemSelected( wxListEvent& WXUNUSED(event) )
371{
372    std::set<tr_torrent*> sel;
373    long item = -1;
374    for ( ;; ) {
375        item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
376        if ( item == -1 )
377            break;
378        sel.insert( myTorrents[GetItemData(item)] );
379    }
380    fire_selection_changed( sel );
381}
382
383void
384TorrentListCtrl :: OnItemDeselected( wxListEvent& event )
385{
386    OnItemSelected( event );
387}
388
389/***
390****
391***/
392
393static TorrentListCtrl * uglyHack = NULL;
394
395int wxCALLBACK
396TorrentListCtrl :: Compare( long item1, long item2, long sortData )
397{
398    TorrentListCtrl * self = uglyHack;
399    tr_torrent * a = self->myTorrents[item1];
400    tr_torrent * b = self->myTorrents[item2];
401    const tr_info* ia = tr_torrentInfo( a );
402    const tr_info* ib = tr_torrentInfo( b );
403    const tr_stat* sa = self->getStat( a );
404    const tr_stat* sb = self->getStat( b );
405    int ret = 0;
406
407    switch( abs(sortData) )
408    {
409        case COL_POSITION:
410            ret = item1 - item2;
411            break;
412
413        case COL_PERCENT_DONE:
414            if( sa->percentDone < sb->percentDone )
415                ret = -1;
416            else if( sa->percentDone > sb->percentDone )
417                ret =  1;
418            else
419                ret = 0;
420            break;
421
422        case COL_DOWNLOAD_SPEED:
423            if( sa->rateDownload < sb->rateDownload )
424                ret = -1;
425            else if( sa->rateDownload > sb->rateDownload )
426                ret =  1;
427            else
428                ret = 0;
429            break;
430
431        case COL_ETA:
432            ret = sa->eta - sb->eta;
433            break;
434           
435        case COL_HASH:
436            ret = strcmp( ia->hashString, ib->hashString );
437            break;
438
439        case COL_NAME:
440            ret = strcmp( ia->name, ib->name );
441            break;
442
443        case COL_PEERS:
444            /* FIXME: this is all peers, not just leechers
445            tr_snprintf( buf, sizeof(buf), "%d (%d)", s->peersTotal, s->peersConnected );
446            xstr = toWxStr( buf );*/
447            break;
448
449        case COL_RATIO: {
450            const double ra = sa->uploadedEver / (double)(sa->downloadedEver + 0.01);
451            const double rb = sb->uploadedEver / (double)(sb->downloadedEver + 0.01);
452            if( ra < rb )
453                ret = -1;
454            else if( ra > rb )
455                ret = 1;
456            else
457                ret = 0;
458            break;
459        }
460
461        case COL_RECEIVED:
462            if( sa->downloadedEver < sb->downloadedEver )
463                ret = -1;
464            else if( sa->downloadedEver > sb->downloadedEver )
465                ret = 1;
466            else
467                ret = 0;
468            break;
469
470        case COL_REMAINING:
471            if( sa->leftUntilDone < sb->leftUntilDone )
472                ret = -1;
473            else if( sa->leftUntilDone > sb->leftUntilDone )
474                ret = 1;
475            else
476                ret = 0;
477            break;
478
479        case COL_SEEDS:
480            /*tr_snprintf( buf, sizeof(buf), "%d", s->seeders );
481            xstr = toWxStr( buf );*/
482            break;
483
484        case COL_SENT:
485            if( sa->uploadedEver < sb->uploadedEver )
486                ret = -1;
487            else if( sa->uploadedEver > sb->uploadedEver )
488                ret = 1;
489            else
490                ret = 0;
491            break;
492
493        case COL_SIZE:
494            if( ia->totalSize < ib->totalSize ) ret = -1;
495            else if( ia->totalSize > ib->totalSize ) ret = 1;
496            else ret = 0;
497            break;
498
499        case COL_STATE: /* FIXME */
500        case COL_STATUS:
501            ret = sa->status - sb->status;
502            break;
503
504        case COL_TOTAL:
505            /*xstr = _T("Fixme");*/
506            break;
507
508        case COL_UPLOAD_SPEED:
509            if( sa->rateUpload < sb->rateUpload )
510                ret = -1;
511            else if( sa->rateUpload > sb->rateUpload )
512                ret = 1;
513            else
514                ret = 0;
515            break;
516
517        default:
518            abort ();
519    }
520
521    if( sortData < 0 )
522       ret = -ret;
523
524    return ret;
525}
526
527void
528TorrentListCtrl :: Sort( int column )
529{
530    if( column == prevSortCol )
531        column = -column;
532    prevSortCol = column;
533    Resort ();
534}
535
536bool
537TorrentListCtrl :: IsSorted( ) const
538{
539    bool is_sorted = true;
540    long prevItem=-1, curItem=-1;
541
542    uglyHack = const_cast<TorrentListCtrl*>(this);
543    while( is_sorted )
544    {
545        prevItem = curItem;
546        curItem = GetNextItem( curItem, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE );
547        if ( curItem == -1 )
548            break;
549        if( prevItem>=0 && curItem>=0 )
550            if( Compare( prevItem, curItem, prevSortCol ) > 0 )
551                is_sorted = false;
552    }
553    uglyHack = 0;
554
555    return is_sorted;
556}
557
558void
559TorrentListCtrl :: Resort( )
560{
561    myConfig->Write( _T("torrent-sort-column"), columnKeys[abs(prevSortCol)] );
562    myConfig->Write( _T("torrent-sort-is-descending"), prevSortCol < 0 );
563
564    if( !IsSorted ( ) )
565    {
566        uglyHack = this;
567        SortItems( Compare, prevSortCol );
568
569        const int n = GetItemCount ();
570        str2int_t tmp;
571        for( int i=0; i<n; ++i ) {
572            int idx = GetItemData( i );
573            const tr_info * info = tr_torrentInfo( myTorrents[idx] );
574            tmp[info->hashString] = i;
575        }
576        myHashToItem.swap( tmp );
577        uglyHack = NULL;
578    }
579}
580
581/***
582****
583***/
584
585void
586TorrentListCtrl :: Refresh ()
587{
588    const int_v  cols = getTorrentColumns( myConfig );
589    const int rowCount = GetItemCount();
590    for( int row=0; row<rowCount; ++row )
591    {
592        int array_index = GetItemData( row );
593        tr_torrent * tor = myTorrents[array_index];
594        RefreshTorrent( tor, array_index, cols );
595    }
596}
597
598void
599TorrentListCtrl :: Repopulate ()
600{
601    DeleteAllItems();
602    myHashToItem.clear ();
603
604    const int_v cols = getTorrentColumns( myConfig );
605    int i = 0;
606    for( torrents_t::const_iterator it(myTorrents.begin()),
607                                   end(myTorrents.end()); it!=end; ++it )
608        RefreshTorrent( *it, i++, cols );
609
610    Resort( );
611}
612
613void
614TorrentListCtrl :: Rebuild()
615{
616    ClearAll( );
617    myHashToItem.clear ();
618
619    int i = 0;
620    const int_v  cols = getTorrentColumns( myConfig );
621    for( int_v ::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
622    {
623        int format = wxLIST_FORMAT_LEFT;
624        int width = -1;
625        wxString h;
626
627        switch( *it )
628        {
629            case COL_POSITION:        h = _("#"); format = wxLIST_FORMAT_CENTRE; break;
630            case COL_PERCENT_DONE:    h = _("Done"); width = 50; format = wxLIST_FORMAT_RIGHT; break;
631            case COL_DOWNLOAD_SPEED:  h = _("Down"); width = 80; format = wxLIST_FORMAT_RIGHT; break;
632            case COL_ETA:             h = _("ETA"); format = wxLIST_FORMAT_RIGHT; break;
633            case COL_HASH:            h = _("Checksum"); break;
634            case COL_NAME:            h = _("Name"); width = 500; break;
635            case COL_PEERS:           h = _("Peers"); format = wxLIST_FORMAT_RIGHT; break;
636            case COL_RATIO:           h = _("Ratio"); format = wxLIST_FORMAT_RIGHT; break;
637            case COL_RECEIVED:        h = _("Received"); format = wxLIST_FORMAT_RIGHT; break;
638            case COL_REMAINING:       h = _("Remaining"); format = wxLIST_FORMAT_RIGHT; break;
639            case COL_SEEDS:           h = _("Seeds"); format = wxLIST_FORMAT_RIGHT; break;
640            case COL_SENT:            h = _("Sent"); format = wxLIST_FORMAT_RIGHT; break;
641            case COL_SIZE:            h = _("Size");  format = wxLIST_FORMAT_RIGHT; break;
642            case COL_STATE:           h = _("State"); width = 120; break;
643            case COL_STATUS:          h = _("Status"); width = 120; break;
644            case COL_TOTAL:           h = _("Total"); break;
645            case COL_UPLOAD_SPEED:    h = _("Up"); width = 80; format = wxLIST_FORMAT_RIGHT;break;
646            default:                  h = _("Error"); break;
647        }
648
649        InsertColumn( i++, h, format, width );
650    }
651
652    Repopulate( );
653}
654
655typedef std::set<tr_torrent*> torrent_set;
656
657void
658TorrentListCtrl :: Assign( const torrents_t& torrents )
659{
660    torrent_set prev, cur, removed;
661    torrents_v added;
662    prev.insert( myTorrents.begin(), myTorrents.end() );
663    cur.insert( torrents.begin(), torrents.end() );
664    std::set_difference (prev.begin(), prev.end(),
665                         cur.begin(), cur.end(), inserter(removed, removed.begin()));
666    std::set_difference (cur.begin(), cur.end(),
667                         prev.begin(), prev.end(), inserter(added, added.begin()));
668    Remove( removed );
669    Add( added );
670    Refresh( );
671    Resort( );
672}
673
674void
675TorrentListCtrl :: Add( const torrents_v& add )
676{
677    const int_v  cols = getTorrentColumns( myConfig );
678    int i = myTorrents.size();
679    myTorrents.insert( myTorrents.end(), add.begin(), add.end() );
680    for( torrents_v::const_iterator it(add.begin()), end(add.end()); it!=end; ++it )
681        RefreshTorrent( *it, i++, cols );
682}
683
684void
685TorrentListCtrl :: Remove( const torrent_set& remove )
686{
687    torrents_v vtmp;
688    str2int_t htmp;
689
690    for( int item=0; item<GetItemCount(); )
691    {
692        tr_torrent * tor = myTorrents[GetItemData(item)];
693        const tr_info * info = tr_torrentInfo( tor );
694
695        if( remove.count( tor ) )
696        {
697            DeleteItem( item );
698            continue;
699        }
700
701        vtmp.push_back( tor );
702        SetItemData( item, vtmp.size()-1 );
703        htmp[ info->hashString ] = item;
704        ++item;
705    }
706
707    myHashToItem.swap( htmp );
708    myTorrents.swap( vtmp );
709}
710
711/***
712****
713***/
714
715const tr_stat*
716TorrentListCtrl :: getStat( tr_torrent * tor )
717{
718    const tr_info * info = tr_torrentInfo( tor );
719    const time_t now = time( 0 );
720    TorStat& ts = myHashToStat[ info->hashString ];
721    if( ts.time < now ) {
722        ts.time = now;
723        ts.stat = tr_torrentStat( tor );
724    }
725    return ts.stat;
726}
727
728/***
729****
730***/
731
732void
733TorrentListCtrl :: SelectAll( )
734{
735    for( int i=0, n=GetItemCount(); i<n; ++i )
736        SetItemState( i, ~0, wxLIST_STATE_SELECTED );
737}
738
739void
740TorrentListCtrl :: DeselectAll( )
741{
742    for( int i=0, n=GetItemCount(); i<n; ++i )
743        SetItemState( i, 0, wxLIST_STATE_SELECTED );
744}
745
Note: See TracBrowser for help on using the repository browser.