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

Last change on this file since 4494 was 4494, checked in by charles, 15 years ago

get the wx client compiling again, at least.

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