source: trunk/wx/xmission.cc @ 2460

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

cleaner sizer code in the main window

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