/* * This file Copyright (C) 2007 Charles Kerr * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. */ #include #include #include namespace { typedef std::vector torrents_t; enum { COL_POSITION, COL_DONE, COL_DOWNLOAD_SPEED, COL_ETA, COL_HASH, COL_NAME, COL_PEERS, COL_RATIO, COL_RECEIVED, COL_REMAINING, COL_SEEDS, COL_SENT, COL_SIZE, COL_STATE, COL_STATUS, COL_TOTAL, COL_UPLOAD_SPEED, N_COLS }; const wxString columnKeys[N_COLS] = { _T("position"), _T("done"), _T("download-speed"), _T("eta"), _T("hash"), _T("name"), _T("peers"), _T("ratio"), _T("received"), _T("remaining"), _T("seeds"), _T("sent"), _T("size"), _T("state"), _T("status"), _T("total"), _T("upload-speed") }; int getTorrentColumn( const wxString& key ) { typedef std::map string2key_t; static string2key_t columns; if( columns.empty() ) { columns[_T("position")] = COL_POSITION; columns[_T("done")] = COL_DONE; columns[_T("download-speed")] = COL_DOWNLOAD_SPEED; columns[_T("eta")] = COL_ETA; columns[_T("hash")] = COL_HASH; columns[_T("name")] = COL_NAME; columns[_T("peers")] = COL_PEERS; columns[_T("ratio")] = COL_RATIO; columns[_T("received")] = COL_RECEIVED; columns[_T("remaining")] = COL_REMAINING; columns[_T("seeds")] = COL_SEEDS; columns[_T("sent")] = COL_SENT; columns[_T("size")] = COL_SIZE; columns[_T("state")] = COL_STATE; columns[_T("status")] = COL_STATUS; columns[_T("total")] = COL_TOTAL; columns[_T("upload-speed")] = COL_UPLOAD_SPEED; } int i = -1; string2key_t::const_iterator it = columns.find( key ); if( it != columns.end() ) i = it->second; return i; } typedef std::vector int_v; int_v getTorrentColumns( wxConfig * config ) { const wxString key = _T("torrent-list-columns"); wxString columnStr; if( !config->Read( key, &columnStr, _T("name|download-speed|upload-speed|eta|peers|size|done|status|seeds") ) ) config->Write( key, columnStr ); int_v cols; while( !columnStr.IsEmpty() ) { const wxString key = columnStr.BeforeFirst(_T('|')); columnStr.Remove( 0, key.Len() + 1 ); cols.push_back( getTorrentColumn( key ) ); } return cols; } int bestDecimal( double num ) { if ( num < 10 ) return 2; if ( num < 100 ) return 1; return 0; } wxString toWxStr( const std::string& s ) { return wxString( s.c_str(), wxConvUTF8 ); } wxString toWxStr( const char * s ) { return wxString( s, wxConvUTF8 ); } wxString getReadableSize( uint64_t size ) { int i; static const char *sizestrs[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" }; for ( i=0; size>>10; ++i ) size = size>>10; char buf[512]; snprintf( buf, sizeof(buf), "%.*f %s", bestDecimal(size), (double)size, sizestrs[i] ); return toWxStr( buf ); } wxString getReadableSize( float f ) { return getReadableSize( (uint64_t)f ); } wxString getReadableSpeed( float f ) { wxString xstr = getReadableSize(f); xstr += _T("/s"); return xstr; } wxString getReadableTime( int i /*seconds*/ ) /*FIXME*/ { const int s = i % 60; i /= 60; const int m = i % 60; i /= 60; const int h = i; return wxString::Format( _T("%d:%02d:%02d"), h, m, s ); } } enum { TORRENT_LIST_CTRL = 1000 }; BEGIN_EVENT_TABLE(TorrentListCtrl, wxListCtrl) EVT_LIST_COL_CLICK( TORRENT_LIST_CTRL, TorrentListCtrl::OnSort ) EVT_LIST_ITEM_SELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemSelected ) EVT_LIST_ITEM_DESELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemDeselected ) END_EVENT_TABLE() TorrentListCtrl :: TorrentListCtrl( tr_handle_t * handle, wxConfig * config, wxWindow * parent, const wxPoint & pos, const wxSize & size): wxListCtrl( parent, TORRENT_LIST_CTRL, pos, size, wxLC_REPORT ), myHandle( handle ), myConfig( config ) { wxString sortColStr; myConfig->Read( _T("torrent-sort-column"), &sortColStr, columnKeys[COL_NAME] ); prevSortCol = getTorrentColumn( sortColStr ); bool descending; myConfig->Read( _T("torrent-sort-is-descending"), &descending, FALSE ); if( descending ) prevSortCol = -prevSortCol; Rebuild (); } TorrentListCtrl :: ~TorrentListCtrl() { } void TorrentListCtrl :: RefreshTorrent( tr_torrent_t * tor, int myTorrents_index, const int_v & cols ) { int row = -1; int col = 0; char buf[512]; std::string str; const tr_stat_t * s = tr_torrentStat( tor ); const tr_info_t* info = tr_torrentInfo( tor ); for( int_v::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it ) { wxString xstr; switch( *it ) { case COL_POSITION: snprintf( buf, sizeof(buf), "%d", 666 ); xstr = toWxStr( buf ); break; case COL_DONE: snprintf( buf, sizeof(buf), "%d%%", (int)(s->percentDone*100.0) ); xstr = toWxStr( buf ); break; case COL_DOWNLOAD_SPEED: break; xstr = getReadableSpeed( s->rateDownload ); break; case COL_ETA: if( (int)(s->percentDone*100) >= 100 ) xstr = wxString (); else if( s->eta < 0 ) xstr = toWxStr( "\xE2\x88\x9E" ); /* infinity, in utf-8 */ else xstr = getReadableTime( s->eta ); break; case COL_HASH: xstr = toWxStr( info->hashString ); break; case COL_NAME: xstr = toWxStr( info->name ); break; case COL_PEERS: /* FIXME: this is all peers, not just leechers */ snprintf( buf, sizeof(buf), "%d (%d)", s->peersTotal, s->peersConnected ); xstr = toWxStr( buf ); break; case COL_RATIO: snprintf( buf, sizeof(buf), "%%%d", (int)(s->uploaded / (double)s->downloadedValid) ); xstr = toWxStr( buf ); break; case COL_RECEIVED: xstr = getReadableSize( s->downloaded ); break; case COL_REMAINING: xstr = getReadableSize( s->left ); break; case COL_SEEDS: snprintf( buf, sizeof(buf), "%d", s->seeders ); /* FIXME: %d (%d) */ xstr = toWxStr( buf ); break; case COL_SENT: xstr = getReadableSize( s->uploaded ); break; case COL_SIZE: xstr = getReadableSize( info->totalSize ); break; case COL_STATE: xstr = _T("Fixme"); break; case COL_STATUS: xstr = _T("Fixme"); break; case COL_TOTAL: xstr = _T("Fixme"); break; case COL_UPLOAD_SPEED: xstr = getReadableSpeed( s->rateUpload ); break; default: xstr = _T("Fixme"); } if( col ) SetItem( row, col++, xstr ); else { // first column... find the right row to put the info in. // if the torrent's in the list already, update that row. // otherwise, add a new row. if( row < 0 ) { str2int_t::const_iterator it = myHashToItem.find( info->hashString ); if( it != myHashToItem.end() ) { row = it->second; } } if( row >= 0 ) { SetItem( row, col++, xstr ); } else { row = InsertItem( GetItemCount(), xstr ); col = 1; myHashToItem[info->hashString] = row; SetItemData( row, myTorrents_index ); } } } } /*** **** ***/ void TorrentListCtrl :: OnSort( wxListEvent& event ) { const int_v cols = getTorrentColumns( myConfig ); const int key = cols[ event.GetColumn() ]; Sort( key ); } void TorrentListCtrl :: OnItemSelected( wxListEvent& event ) { std::set sel; long item = -1; for ( ;; ) { item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); if ( item == -1 ) break; sel.insert( myTorrents[GetItemData(item)] ); } fire_selection_changed( sel ); } void TorrentListCtrl :: OnItemDeselected( wxListEvent& event ) { OnItemSelected( event ); } /*** **** ***/ static torrents_t * uglyHack = NULL; int TorrentListCtrl :: Compare( long item1, long item2, long sortData ) { const tr_torrent_t * a = (*uglyHack)[item1]; const tr_torrent_t * b = (*uglyHack)[item2]; const tr_info_t* ia = tr_torrentInfo( a ); const tr_info_t* ib = tr_torrentInfo( b ); int ret = 0; switch( abs(sortData) ) { case COL_POSITION: ret = item1 - item2; case COL_DONE: /* ccc snprintf( buf, sizeof(buf), "%d%%", (int)(s->percentDone*100.0) ); xstr = toWxStr( buf );*/ break; case COL_DOWNLOAD_SPEED: break; /*xstr = getReadableSpeed( s->rateDownload );*/ break; case COL_ETA: /* if( (int)(s->percentDone*100) >= 100 ) */ break; case COL_HASH: /*xstr = toWxStr( info->hashString );*/ break; case COL_NAME: ret = strcmp( ia->name, ib->name ); break; case COL_PEERS: /* FIXME: this is all peers, not just leechers snprintf( buf, sizeof(buf), "%d (%d)", s->peersTotal, s->peersConnected ); xstr = toWxStr( buf );*/ break; case COL_RATIO: /*snprintf( buf, sizeof(buf), "%%%d", (int)(s->uploaded / (double)s->downloadedValid) ); xstr = toWxStr( buf );*/ break; case COL_RECEIVED: /*xstr = getReadableSize( s->downloaded );*/ break; case COL_REMAINING: /*xstr = getReadableSize( s->left );*/ break; case COL_SEEDS: /*snprintf( buf, sizeof(buf), "%d", s->seeders ); xstr = toWxStr( buf );*/ break; case COL_SENT: /*xstr = getReadableSize( s->uploaded );*/ break; case COL_SIZE: if( ia->totalSize < ib->totalSize ) ret = -1; else if( ia->totalSize > ib->totalSize ) ret = 1; else ret = 0; break; case COL_STATE: /*xstr = _T("Fixme");*/ break; case COL_STATUS: /*xstr = _T("Fixme");*/ break; case COL_TOTAL: /*xstr = _T("Fixme");*/ break; case COL_UPLOAD_SPEED: /*xstr = getReadableSpeed( s->rateUpload );*/ break; default: abort (); } if( sortData < 0 ) ret = -ret; return ret; } void TorrentListCtrl :: Sort( int column ) { if( column == prevSortCol ) column = -column; prevSortCol = column; Resort (); } void TorrentListCtrl :: Resort( ) { uglyHack = &myTorrents; myConfig->Write( _T("torrent-sort-column"), columnKeys[abs(prevSortCol)] ); myConfig->Write( _T("torrent-sort-is-descending"), prevSortCol < 0 ); SortItems( Compare, prevSortCol ); const int n = GetItemCount (); str2int_t tmp; for( int i=0; ihashString] = i; } myHashToItem.swap( tmp ); uglyHack = NULL; } /*** **** ***/ void TorrentListCtrl :: Refresh () { const int_v cols = getTorrentColumns( myConfig ); const int rowCount = GetItemCount(); for( int row=0; row torrent_set; void TorrentListCtrl :: Assign( const torrents_t& torrents ) { torrent_set prev, cur, removed; torrents_v added; prev.insert( myTorrents.begin(), myTorrents.end() ); cur.insert( torrents.begin(), torrents.end() ); std::set_difference (prev.begin(), prev.end(), cur.begin(), cur.end(), inserter(removed, removed.begin())); std::set_difference (cur.begin(), cur.end(), prev.begin(), prev.end(), inserter(added, added.begin())); Remove( removed ); Add( added ); Refresh (); } void TorrentListCtrl :: Add( const torrents_v& add ) { const int_v cols = getTorrentColumns( myConfig ); int i = myTorrents.size(); myTorrents.insert( myTorrents.end(), add.begin(), add.end() ); for( torrents_v::const_iterator it(add.begin()), end(add.end()); it!=end; ++it ) RefreshTorrent( *it, i++, cols ); Resort( ); } void TorrentListCtrl :: Remove( const torrent_set& remove ) { torrents_v vtmp; str2int_t htmp; for( int item=0; itemhashString ] = item; ++item; } myHashToItem.swap( htmp ); myTorrents.swap( vtmp ); }