source: trunk/wx/xmission.cc @ 2461

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

build errors reported by SoftwareElves?

  • Property svn:executable set to *
File size: 19.3 KB
Line 
1/*
2 * This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 */
10
11#include <set>
12#include <map>
13#include <string>
14#include <vector>
15#include <iostream>
16#include <stdint.h>
17#include <wx/artprov.h>
18#include <wx/defs.h>
19#include <wx/config.h>
20#include <wx/listctrl.h>
21#include <wx/toolbar.h>
22#include <wx/splitter.h>
23#include <wx/notebook.h>
24#include <wx/wx.h>
25#if wxCHECK_VERSION(2,8,0)
26#include <wx/aboutdlg.h>
27#endif
28extern "C" {
29  #include <libtransmission/transmission.h>
30  #include <libtransmission/utils.h>
31}
32
33class MyApp : public wxApp
34{
35    virtual bool OnInit();
36};
37
38IMPLEMENT_APP(MyApp)
39
40tr_handle_t * handle = NULL;
41
42class MyFrame : public wxFrame
43{
44public:
45    MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
46    virtual ~MyFrame();
47    void OnQuit(wxCommandEvent& event);
48    void OnAbout(wxCommandEvent& event);
49    void OnOpen(wxCommandEvent& event);
50    void OnTimer(wxTimerEvent& event);
51
52protected:
53    wxConfig * myConfig;
54    wxTimer myPulseTimer;
55
56private:
57    void rebuildTorrentList();
58    void repopulateTorrentList ();
59    void refreshTorrentList ();
60    wxListCtrl * myTorrentList;
61    typedef std::vector<tr_torrent_t*> torrents_t;
62    torrents_t myTorrents;
63    void refreshTorrent( tr_torrent_t*, int, const std::vector<int>& );
64
65    typedef std::map<std::string,int> str2int_t;
66
67    /** torrent hash -> the torrent's row in myTorrentList */
68    str2int_t myHashToRow;
69};
70
71enum
72{
73    ID_START,
74    ID_STOP,
75    ID_REMOVE,
76    ID_QUIT,
77    ID_TORRENT_INFO,
78    ID_EDIT_PREFS,
79    ID_SHOW_DEBUG_WINDOW,
80    ID_ABOUT,
81    ID_Pulse,
82    N_IDS
83};
84
85void MyFrame :: OnOpen( wxCommandEvent& event )
86{
87    const wxString key = _T("LastDirectory");
88    wxString directory;
89    myConfig->Read( key, &directory );
90    wxFileDialog * w = new wxFileDialog( this, _T("message"),
91                                         directory,
92                                         _T(""), /* default file */
93                                         _T("Torrent files|*.torrent"),
94                                         wxOPEN|wxMULTIPLE );
95
96    if( w->ShowModal() == wxID_OK )
97    {
98        wxArrayString paths;
99        w->GetPaths( paths );
100        size_t nPaths = paths.GetCount();
101        for( size_t i=0; i<nPaths; ++i )
102        {
103            const wxString& w = paths[i];
104            std::cerr << w.ToAscii() << std::endl;
105        }
106        myConfig->Write( key, w->GetDirectory() );
107    }
108
109    delete w;
110}
111
112
113bool MyApp::OnInit()
114{
115    handle = tr_init( "wx" );
116
117    MyFrame * frame = new MyFrame( _T("Xmission"),
118                                   wxPoint(50,50),
119                                   wxSize(450,350));
120
121    frame->Connect( wxID_OPEN, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &MyFrame::OnOpen );
122    frame->Connect( wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &MyFrame::OnQuit );
123    frame->Connect( wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction) &MyFrame::OnAbout );
124    frame->Connect( ID_Pulse, wxEVT_TIMER, (wxObjectEventFunction) &MyFrame::OnTimer );
125
126    frame->Show( true );
127    SetTopWindow( frame );
128    return true;
129}
130
131/***
132****
133***/
134
135
136namespace
137{
138    enum
139    {
140        COL_NUMBER,
141        COL_DONE,
142        COL_DOWNLOAD_SPEED,
143        COL_ETA,
144        COL_HASH,
145        COL_NAME,
146        COL_PEERS,
147        COL_RATIO,
148        COL_RECEIVED,
149        COL_REMAINING,
150        COL_SEEDS,
151        COL_SENT,
152        COL_SIZE,
153        COL_STATE,
154        COL_STATUS,
155        COL_TOTAL,
156        COL_UPLOAD_SPEED,
157        N_COLS
158    };
159
160    int getTorrentColumn( const wxString& key )
161    {
162        typedef std::map<wxString,int> string2key_t;
163        static string2key_t columns;
164
165        if( columns.empty() )
166        {
167            columns[_T("#")]         = COL_NUMBER;
168            columns[_T("done")]      = COL_DONE;
169            columns[_T("dspeed")]    = COL_DOWNLOAD_SPEED;
170            columns[_T("eta")]       = COL_ETA;
171            columns[_T("hash")]      = COL_HASH;
172            columns[_T("name")]      = COL_NAME;
173            columns[_T("peers")]     = COL_PEERS;
174            columns[_T("ratio")]     = COL_RATIO;
175            columns[_T("received")]  = COL_RECEIVED;
176            columns[_T("remaining")] = COL_REMAINING;
177            columns[_T("seeds")]     = COL_SEEDS; 
178            columns[_T("sent")]      = COL_SENT; 
179            columns[_T("size")]      = COL_SIZE; 
180            columns[_T("state")]     = COL_STATE; 
181            columns[_T("status")]    = COL_STATUS; 
182            columns[_T("total")]     = COL_TOTAL; 
183            columns[_T("uspeed")]    = COL_UPLOAD_SPEED;
184        }
185
186        int i = -1;
187        string2key_t::const_iterator it = columns.find( key );
188        if( it != columns.end() )
189            i = it->second;
190
191        return i;
192    }
193
194    typedef std::vector<int> int_v;
195
196    int_v getTorrentColumns( wxConfig * config )
197    {
198        const wxString key = _T("TorrentListColumns");
199        wxString columnStr;
200        if( !config->Read( key, &columnStr ) )
201            columnStr = _T("name|dspeed|uspeed|eta|peers|size|done|status|seeds");
202
203        int_v cols;
204        while( !columnStr.IsEmpty() )
205        {
206            const wxString key = columnStr.BeforeFirst(_T('|'));
207            columnStr.Remove( 0, key.Len() + 1 );
208            cols.push_back( getTorrentColumn( key ) );
209        }
210        return cols;
211    }
212
213    int bestDecimal( double num ) {
214        if ( num < 10 ) return 2;
215        if ( num < 100 ) return 1;
216        return 0;
217    }
218
219    std::string getReadableSize( uint64_t size )
220    {
221        int i;
222        static const char *sizestrs[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
223            //N_("B"), N_("KiB"), N_("MiB"), N_("GiB"), N_("TiB"), N_("PiB"), N_("EiB") };
224        for ( i=0; size>>10; ++i )
225            size = size>>10;
226        char buf[512];
227        snprintf( buf, sizeof(buf), "%.*f %s", bestDecimal(size), (double)size, sizestrs[i] );
228        return buf;
229    }
230    std::string getReadableSize( float f )
231    {
232        return getReadableSize( (uint64_t)f );
233    }
234
235    std::string getReadableTime( int i /*seconds*/ )  /*FIXME*/
236    {
237        const int s = i % 60; i /= 60;
238        const int m = i % 60; i /= 60;
239        const int h = i;
240        char buf[64];
241        snprintf( buf, sizeof(buf), "%d:%02d:%02d", h, m, s);
242        return buf;
243    }
244
245    wxString toWxStr( const std::string& s )
246    {
247        return wxString( s.c_str(), wxConvUTF8 );
248    }
249
250    wxString toWxStr( const char * s )
251    {
252        return wxString( s, wxConvUTF8 );
253    }
254}
255
256
257/***
258****
259****  PEERS LIST
260****
261****
262***/
263
264
265/***
266****
267****  TORRENT LIST
268****
269****
270***/
271
272void
273MyFrame :: refreshTorrent( tr_torrent_t  * tor,
274                           int             myTorrents_index,
275                           const int_v   & cols )
276{
277    int row = -1;
278    int col = 0;
279    char buf[512];
280    std::string str;
281    const tr_stat_t * s = tr_torrentStat( tor );
282    const tr_info_t* info = tr_torrentInfo( tor );
283
284    for( int_v::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
285    {
286        wxString xstr;
287
288        switch( *it )
289        {
290            case COL_NUMBER:
291                snprintf( buf, sizeof(buf), "%d", 666 );
292                xstr = toWxStr( buf );
293                break;
294
295            case COL_DONE:
296                snprintf( buf, sizeof(buf), "%d%%", (int)(s->percentDone*100.0) );
297                xstr = toWxStr( buf );
298                break;
299
300            case COL_DOWNLOAD_SPEED: break;
301                xstr = toWxStr( getReadableSize( s->rateDownload ) + "/s"  );
302                break;
303
304            case COL_ETA:
305                if( (int)(s->percentDone*100) >= 100 )
306                    xstr = wxString ();
307                else if( s->eta < 0 )
308                    xstr = toWxStr( "\xE2\x88\x9E" ); /* infinity, in utf-8 */
309                else
310                    xstr = toWxStr( getReadableTime( s->eta ) );
311                break;
312               
313            case COL_HASH:
314                xstr = toWxStr( info->hashString );
315                break;
316
317            case COL_NAME:
318                xstr = toWxStr( info->name );
319                break;
320
321            case COL_PEERS:
322                /* FIXME: this is all peers, not just leechers */
323                snprintf( buf, sizeof(buf), "%d (%d)", s->peersTotal, s->peersConnected );
324                xstr = toWxStr( buf );
325                break;
326
327            case COL_RATIO:
328                snprintf( buf, sizeof(buf), "%%%d", (int)(s->uploaded / (double)s->downloadedValid) );
329                xstr = toWxStr( buf );
330                break;
331
332            case COL_RECEIVED:
333                xstr = toWxStr( getReadableSize( s->downloaded ) );
334                break;
335
336            case COL_REMAINING:
337                xstr = toWxStr( getReadableSize( s->left ) );
338                break;
339
340            case COL_SEEDS:
341                snprintf( buf, sizeof(buf), "%d", s->seeders ); /* FIXME: %d (%d) */
342                xstr = toWxStr( buf );
343                break;
344
345            case COL_SENT:
346                xstr = toWxStr( getReadableSize( s->uploaded ) );
347                break;
348
349            case COL_SIZE:
350                xstr = toWxStr( getReadableSize( info->totalSize ) );
351                break;
352
353            case COL_STATE:
354                xstr = _T("Fixme");
355                break;
356
357            case COL_STATUS:
358                xstr = _T("Fixme");
359                break;
360
361            case COL_TOTAL:
362                xstr = _T("Fixme");
363                break;
364
365            case COL_UPLOAD_SPEED:
366                xstr = toWxStr( getReadableSize( s->rateUpload ) + "/s" );
367                break;
368
369            default:
370                xstr = _T("Fixme");
371        }
372
373        if( col ) {
374            myTorrentList->SetItem( row, col++, xstr );
375        }
376        else {
377            // first column... find the right row to put the info in.
378            // if the torrent's in the list already, update that row.
379            // otherwise, add a new row.
380            if( row < 0 ) {
381                str2int_t::const_iterator it = myHashToRow.find( info->hashString );
382                if( it != myHashToRow.end() ) {
383                    row = it->second;
384                }
385            }
386            if( row >= 0 ) {
387                myTorrentList->SetItem( row, col++, xstr );
388            }
389            else {
390                row = myTorrentList->InsertItem( myTorrentList->GetItemCount(), xstr );
391                col = 1;
392                myHashToRow[info->hashString] = row;
393                myTorrentList->SetItemData( row, myTorrents_index );
394            }
395        }
396    }
397}
398
399void
400MyFrame :: refreshTorrentList ()
401{
402    const int_v  cols = getTorrentColumns( myConfig );
403    const int rowCount = myTorrentList->GetItemCount();
404    for( int row=0; row<rowCount; ++row )
405    {
406        int array_index = myTorrentList->GetItemData( row );
407        tr_torrent_t * tor = myTorrents[array_index];
408        refreshTorrent( tor, array_index, cols );
409    }
410}
411
412void
413MyFrame :: repopulateTorrentList ()
414{
415std::cerr << __FILE__ << ':' << __LINE__ << " clearing all items from list" << std::endl;
416    myTorrentList->DeleteAllItems();
417    myHashToRow.clear ();
418
419    const int_v cols = getTorrentColumns( myConfig );
420    int i = 0;
421    for( torrents_t::const_iterator it(myTorrents.begin()),
422                                   end(myTorrents.end()); it!=end; ++it )
423        refreshTorrent( *it, i++, cols );
424}
425
426void
427MyFrame :: rebuildTorrentList()
428{
429    myTorrentList->ClearAll( );
430    myHashToRow.clear ();
431
432    int i = 0;
433    const int_v  cols = getTorrentColumns( myConfig );
434    for( int_v ::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
435    {
436        int format = wxLIST_FORMAT_LEFT;
437        int width = -1;
438        wxString h;
439
440        switch( *it )
441        {
442            case COL_NUMBER:          h = _T("#"); format = wxLIST_FORMAT_CENTRE; break;
443            case COL_DONE:            h = _T("Done"); format = wxLIST_FORMAT_RIGHT; break;
444            case COL_DOWNLOAD_SPEED:  h = _T("Download"); break;
445            case COL_ETA:             h = _T("ETA"); format = wxLIST_FORMAT_RIGHT; break;
446            case COL_HASH:            h = _T("SHA1 Hash"); break;
447            case COL_NAME:            h = _T("Name"); width = 500; break;
448            case COL_PEERS:           h = _T("Peers"); format = wxLIST_FORMAT_RIGHT; break;
449            case COL_RATIO:           h = _T("Ratio"); format = wxLIST_FORMAT_RIGHT; break;
450            case COL_RECEIVED:        h = _T("Received"); format = wxLIST_FORMAT_RIGHT; break;
451            case COL_REMAINING:       h = _T("Remaining"); format = wxLIST_FORMAT_RIGHT; break;
452            case COL_SEEDS:           h = _T("Seeds"); format = wxLIST_FORMAT_RIGHT; break;
453            case COL_SENT:            h = _T("Sent"); format = wxLIST_FORMAT_RIGHT; break;
454            case COL_SIZE:            h = _T("Size");  format = wxLIST_FORMAT_RIGHT; break;
455            case COL_STATE:           h = _T("State"); break;
456            case COL_STATUS:          h = _T("Status"); break;
457            case COL_TOTAL:           h = _T("Total"); break;
458            case COL_UPLOAD_SPEED:    h = _T("Upload"); format = wxLIST_FORMAT_RIGHT;break;
459            default:                  h = _T("Error"); break;
460        }
461
462        myTorrentList->InsertColumn( i++, h, format, width );
463    }
464
465    repopulateTorrentList ();
466}
467
468/***
469****
470***/
471
472void
473MyFrame :: OnTimer(wxTimerEvent& event)
474{
475    refreshTorrentList ();
476}
477
478MyFrame::~MyFrame()
479{
480    delete myConfig;
481}
482
483MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size):
484    wxFrame((wxFrame*)NULL,-1,title,pos,size),
485    myConfig( new wxConfig( _T("xmission") ) ),
486    myPulseTimer( this, ID_Pulse )
487{
488    wxImage::AddHandler( new wxPNGHandler );
489    wxImage transmission_logo ( _T("images/transmission.png"), wxBITMAP_TYPE_PNG );
490    wxIcon ico;
491    ico.CopyFromBitmap( wxBitmap( transmission_logo ) );
492    SetIcon( ico );
493
494    /**
495    ***  Torrents
496    **/
497
498    const int flags = TR_FLAG_PAUSED;
499    const char * destination = "/home/charles/torrents";
500    int count = 0;
501    tr_torrent_t ** torrents = tr_loadTorrents ( handle, destination, flags, &count );
502    myTorrents.insert( myTorrents.end(), torrents, torrents+count );
503    tr_free( torrents );
504
505
506    /**
507    ***  Menu
508    **/
509
510    wxMenuBar *menuBar = new wxMenuBar;
511
512    wxMenu * m = new wxMenu;
513    m->Append( wxID_OPEN, _T("&Open") );
514    m->Append( ID_START, _T("&Start") );
515    m->Append( wxID_STOP, _T("Sto&p") ) ;
516    m->Append( wxID_REFRESH, _T("Re&check") );
517    m->Append( wxID_REMOVE, _T("&Remove") );
518    m->AppendSeparator();
519    m->Append( wxID_NEW, _T("Create &New Torrent") );
520    m->AppendSeparator();
521    m->Append( wxID_CLOSE, _T("&Close") );
522    m->Append( wxID_EXIT, _T("&Exit") );
523    menuBar->Append( m, _T("&File") );
524
525    m = new wxMenu;
526    m->Append( ID_TORRENT_INFO, _T("Torrent &Info") );
527    m->Append( wxID_PREFERENCES, _T("Edit &Preferences") );
528    menuBar->Append( m, _T("&Edit") );
529
530    m = new wxMenu;
531    m->Append( ID_SHOW_DEBUG_WINDOW, _T("Show &Debug Window") );
532    m->AppendSeparator();
533    m->Append( wxID_ABOUT, _T("&About Xmission") );
534    menuBar->Append( m, _T("&Help") );
535
536    SetMenuBar(menuBar);
537
538    /**
539    ***  Toolbar
540    **/
541
542    wxImage open_image( _T("images/fileopen.png"),        wxBITMAP_TYPE_PNG );
543    wxImage exec_image( _T("images/exec.png"),            wxBITMAP_TYPE_PNG );
544    wxImage stop_image( _T("images/stop.png"),            wxBITMAP_TYPE_PNG );
545    wxImage drop_image( _T("images/gtk-remove.png"),      wxBITMAP_TYPE_PNG );
546    wxImage info_image( _T("images/gtk-properties.png"),  wxBITMAP_TYPE_PNG );
547
548    wxToolBar* toolbar = CreateToolBar( wxNO_BORDER | wxTB_HORIZONTAL | wxTB_FLAT | wxTB_TEXT );
549    toolbar->SetToolBitmapSize( wxSize( 16, 16 ) );
550    toolbar->AddTool( wxID_OPEN,   _T("Open"), open_image );
551    toolbar->AddTool( ID_START,    _T("Start"), exec_image );
552    toolbar->AddTool( wxID_STOP,   _T("Stop"), stop_image );
553    toolbar->AddTool( wxID_REMOVE, _T("Remove"), drop_image );
554    toolbar->AddSeparator();
555    toolbar->AddTool( ID_TORRENT_INFO, _("Torrent Info"), info_image );
556    toolbar->Realize();
557
558    /**
559    ***  Row 1
560    **/
561
562    wxSplitterWindow * hsplit = new wxSplitterWindow( this );
563    hsplit->SetSashGravity( 0.8 );
564
565    wxPanel * row1 = new wxPanel( hsplit, wxID_ANY );
566
567    /* Filters */
568
569    wxListCtrl * filters = new wxListCtrl( row1, wxID_ANY, wxDefaultPosition, wxDefaultSize,
570                                           wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_NO_HEADER );
571    filters->InsertColumn( wxLIST_FORMAT_LEFT, _T("YYZ") );
572    int i = 0;
573    filters->InsertItem( i++, _T("All") );
574    filters->InsertItem( i++, _T("Downloading (1)") );
575    filters->InsertItem( i++, _T("Completed") );
576    filters->InsertItem( i++, _T("Active (1)") );
577    filters->InsertItem( i++, _T("Inactive") );
578
579    /* Torrent List */
580
581    myTorrentList = new wxListCtrl( row1, wxID_ANY, wxDefaultPosition, wxDefaultSize,
582                                    wxLC_REPORT|wxLC_SINGLE_SEL );
583    rebuildTorrentList();
584
585    wxBoxSizer * boxSizer = new wxBoxSizer( wxHORIZONTAL );
586    boxSizer->Add( filters, 0, wxEXPAND|wxRIGHT, 5 );
587    boxSizer->Add( myTorrentList, 1, wxEXPAND, 0 );
588    row1->SetSizer( boxSizer );
589
590
591    wxNotebook * notebook = new wxNotebook( hsplit, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP );
592    wxButton * tmp = new wxButton( notebook, wxID_ANY, _T("Hello World"));
593    notebook->AddPage( tmp, _T("General"), false );
594    tmp = new wxButton( notebook, wxID_ANY, _T("Hello World"));
595    notebook->AddPage( tmp, _T("Peers"), false );
596    tmp = new wxButton( notebook, wxID_ANY, _T("Hello World"));
597    notebook->AddPage( tmp, _T("Pieces"), false );
598    tmp = new wxButton( notebook, wxID_ANY, _T("Hello World"));
599    notebook->AddPage( tmp, _T("Files"), false );
600    tmp = new wxButton( notebook, wxID_ANY, _T("Hello World"));
601    notebook->AddPage( tmp, _T("Logger"), false );
602
603    hsplit->SplitHorizontally( row1, notebook );
604
605    /**
606    ***  Statusbar
607    **/
608
609    CreateStatusBar();
610    SetStatusText(_T("Welcome to Xmission!"));
611
612    /**
613    ***  Refresh
614    **/
615
616    myPulseTimer.Start( 1500 );
617}
618
619void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
620{
621    Close(TRUE);
622}
623
624void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
625{
626    wxImage transmission_logo ( _T("images/transmission.png"), wxBITMAP_TYPE_PNG );
627    wxIcon ico;
628    ico.CopyFromBitmap( wxBitmap( transmission_logo ) );
629
630#if wxCHECK_VERSION(2,8,0)
631    wxAboutDialogInfo info;
632    info.SetName(_T("Xmission"));
633    info.SetVersion(_T(LONG_VERSION_STRING));
634    info.SetCopyright(_T("Copyright 2005-2007 The Transmission Project"));
635    info.SetDescription(_T("A fast, lightweight bittorrent client"));
636    info.SetWebSite( _T( "http://transmission.m0k.org/" ) );
637    info.SetIcon( ico );
638    info.AddDeveloper( _T("Josh Elsasser (Back-end; GTK+)") );
639    info.AddDeveloper( _T("Charles Kerr (Back-end, GTK+, wxWidgets)") );
640    info.AddDeveloper( _T("Mitchell Livingston (Back-end; OS X)")  );
641    info.AddDeveloper( _T("Eric Petit (Back-end; OS X)")  );
642    info.AddDeveloper( _T("Bryan Varner (BeOS)")  );
643    wxAboutBox( info );
644#else
645    wxMessageBox(_T("Xmission " LONG_VERSION_STRING "\n"
646                    "Copyright 2005-2007 The Transmission Project"),
647                 _T("About Xmission"),
648                wxOK|wxICON_INFORMATION, this);
649#endif
650
651}
Note: See TracBrowser for help on using the repository browser.