Revert "transmission: update from 2.13 to 2.22"
[tomato.git] / release / src / router / transmission / web / javascript / transmission.js
blob3d24c587ec5baa04d334f5223ea7f17b677c30cd
1 /*
2  *      Copyright © Dave Perrett and Malcolm Jarvis
3  *      This code is licensed under the GPL version 2.
4  *      For details, see http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
5  *
6  * Class Transmission
7  */
9 function Transmission(){
10         this.initialize();
13 Transmission.prototype =
15         /*--------------------------------------------
16          *
17          *  C O N S T R U C T O R
18          *
19          *--------------------------------------------*/
21         initialize: function()
22         {
23                 // Initialize the helper classes
24                 this.remote = new TransmissionRemote(this);
26                 // Initialize the implementation fields
27                 this._current_search         = '';
28                 this._torrents               = { };
29                 this._rows                   = [ ];
31                 // Initialize the clutch preferences
32                 Prefs.getClutchPrefs( this );
34                 this.preloadImages();
36                 // Set up user events
37                 var tr = this;
38                 $('#pause_all_link').bind('click', function(e){ tr.stopAllClicked(e); });
39                 $('#resume_all_link').bind('click', function(e){ tr.startAllClicked(e); });
40                 $('#pause_selected_link').bind('click', function(e){ tr.stopSelectedClicked(e); } );
41                 $('#resume_selected_link').bind('click', function(e){ tr.startSelectedClicked(e); });
42                 $('#remove_link').bind('click',  function(e){ tr.removeClicked(e); });
43                 $('#filter_all_link').parent().bind('click', function(e){ tr.showAllClicked(e); });
44                 $('#filter_active_link').parent().bind('click', function(e){ tr.showActiveClicked(e); });
45                 $('#filter_downloading_link').parent().bind('click', function(e){ tr.showDownloadingClicked(e); });
46                 $('#filter_seeding_link').parent().bind('click', function(e){ tr.showSeedingClicked(e); });
47                 $('#filter_paused_link').parent().bind('click', function(e){ tr.showPausedClicked(e); });
48                 $('#filter_finished_link').parent().bind('click', function(e){ tr.showFinishedClicked(e); });
49                 $('#prefs_save_button').bind('click', function(e) { tr.savePrefsClicked(e); return false;});
50                 $('#prefs_cancel_button').bind('click', function(e){ tr.cancelPrefsClicked(e); return false; });
51                 $('#stats_close_button').bind('click', function(e){ tr.closeStatsClicked(e); return false; });
52                 $('.inspector_tab').bind('click', function(e){ tr.inspectorTabClicked(e, this); });
53                 $('.file_wanted_control').live('click', function(e){ tr.fileWantedClicked(e, this); });
54                 $('.file_priority_control').live('click', function(e){ tr.filePriorityClicked(e, this); });
55                 $('#files_select_all').live('click', function(e){ tr.filesSelectAllClicked(e, this); });
56                 $('#files_deselect_all').live('click', function(e){ tr.filesDeselectAllClicked(e, this); });
57                 $('#open_link').bind('click', function(e){ tr.openTorrentClicked(e); });
58                 $('#upload_confirm_button').bind('click', function(e){ tr.confirmUploadClicked(e); return false;});
59                 $('#upload_cancel_button').bind('click', function(e){ tr.cancelUploadClicked(e); return false; });
60                 $('#turtle_button').bind('click', function(e){ tr.toggleTurtleClicked(e); return false; });
61                 $('#prefs_tab_general_tab').click(function(e){ changeTab(this, 'prefs_tab_general') });
62                 $('#prefs_tab_speed_tab').click(function(e){ changeTab(this, 'prefs_tab_speed') });
63         $('#torrent_upload_form').submit(function(){ $('#upload_confirm_button').click(); return false; });
65                 if (iPhone) {
66                         $('#inspector_close').bind('click', function(e){ tr.hideInspector(); });
67                         $('#preferences_link').bind('click', function(e){ tr.releaseClutchPreferencesButton(e); });
68                 } else {
69                         $(document).bind('keydown',  function(e){ tr.keyDown(e); });
70                         $('#torrent_container').bind('click', function(e){ tr.deselectAll( true ); });
71                         $('#filter_toggle_link').bind('click', function(e){ tr.toggleFilterClicked(e); });
72                         $('#inspector_link').bind('click', function(e){ tr.toggleInspectorClicked(e); });
74                         this.setupSearchBox();
75                         this.createContextMenu();
76                         this.createSettingsMenu();
77                 }
78                 this.initTurtleDropDowns();
80                 this._torrent_list             = $('#torrent_list')[0];
81                 this._inspector_file_list      = $('#inspector_file_list')[0];
82                 this._inspector_peers_list     = $('#inspector_peers_list')[0];
83                 this._inspector_trackers_list  = $('#inspector_trackers_list')[0];
84                 this._inspector_tab_files      = $('#inspector_tab_files')[0];
85                 this._toolbar_buttons          = $('#torrent_global_menu ul li');
86                 this._toolbar_pause_button     = $('li#pause_selected')[0];
87                 this._toolbar_pause_all_button = $('li#pause_all')[0];
88                 this._toolbar_start_button     = $('li#resume_selected')[0];
89                 this._toolbar_start_all_button = $('li#resume_all')[0];
90                 this._toolbar_remove_button    = $('li#remove')[0];
91                 this._context_pause_button     = $('li#context_pause_selected')[0];
92                 this._context_start_button     = $('li#context_resume_selected')[0];
94                 var ti = '#torrent_inspector_';
95                 this._inspector = { };
96                 this._inspector._info_tab = { };
97                 this._inspector._info_tab.availability = $(ti+'availability')[0];
98                 this._inspector._info_tab.comment = $(ti+'comment')[0];
99                 this._inspector._info_tab.creator_date = $(ti+'creator_date')[0];
100                 this._inspector._info_tab.creator = $(ti+'creator')[0];
101                 this._inspector._info_tab.download_dir = $(ti+'download_dir')[0];
102                 this._inspector._info_tab.downloaded = $(ti+'downloaded')[0];
103                 this._inspector._info_tab.download_from = $(ti+'download_from')[0];
104                 this._inspector._info_tab.download_speed = $(ti+'download_speed')[0];
105                 this._inspector._info_tab.error = $(ti+'error')[0];
106                 this._inspector._info_tab.hash = $(ti+'hash')[0];
107                 this._inspector._info_tab.have = $(ti+'have')[0];
108                 this._inspector._info_tab.name = $(ti+'name')[0];
109                 this._inspector._info_tab.progress = $(ti+'progress')[0];
110                 this._inspector._info_tab.ratio = $(ti+'ratio')[0];
111                 this._inspector._info_tab.secure = $(ti+'secure')[0];
112                 this._inspector._info_tab.size = $(ti+'size')[0];
113                 this._inspector._info_tab.state = $(ti+'state')[0];
114                 this._inspector._info_tab.pieces = $(ti+'pieces')[0];
115                 this._inspector._info_tab.uploaded = $(ti+'uploaded')[0];
116                 this._inspector._info_tab.upload_speed = $(ti+'upload_speed')[0];
117                 this._inspector._info_tab.upload_to = $(ti+'upload_to')[0];
119                 // Setup the preference box
120                 this.setupPrefConstraints();
122                 // Setup the prefs gui
123                 this.initializeSettings( );
125                 // Get preferences & torrents from the daemon
126                 var tr = this;
127                 var async = false;
128                 this.loadDaemonPrefs( async );
129                 this.loadDaemonStats( async );
130                 this.initializeAllTorrents();
132                 this.togglePeriodicRefresh( true );
133                 this.togglePeriodicSessionRefresh( true );
134         },
136         loadDaemonPrefs: function( async ){
137                 var tr = this;
138                 this.remote.loadDaemonPrefs( function(data){
139                         var o = data.arguments;
140                         Prefs.getClutchPrefs( o );
141                         tr.updatePrefs( o );
142                 }, async );
143         },
145         loadDaemonStats: function( async ){
146                 var tr = this;
147                 this.remote.loadDaemonStats( function(data){
148                         var o = data.arguments;
149                         tr.updateStats( o );
150                 }, async );
151         },
153         preloadImages: function() {
154                 if (iPhone) {
155                         this.loadImages(
156                                 'images/buttons/info_general.png',
157                                 'images/buttons/info_activity.png',
158                                 'images/buttons/info_files.png',
159                                 'images/buttons/toolbar_buttons.png',
160                                 'images/graphics/filter_bar.png',
161                                 'images/graphics/iphone_chrome.png',
162                                 'images/graphics/logo.png'
163                         );
164                 } else {
165                         this.loadImages(
166                                 'images/buttons/info_general.png',
167                                 'images/buttons/info_activity.png',
168                                 'images/buttons/info_files.png',
169                                 'images/buttons/tab_backgrounds.png',
170                                 'images/buttons/toolbar_buttons.png',
171                                 'images/buttons/torrent_buttons.png',
172                                 'images/buttons/file_wanted_buttons.png',
173                                 'images/buttons/file_priority_buttons.png',
174                                 'images/graphics/chrome.png',
175                                 'images/graphics/filter_bar.png',
176                                 'images/graphics/logo.png',
177                                 'images/graphics/transfer_arrows.png',
178                                 'images/progress/progress.png'
179                         );
180                 }
181         },
182         loadImages: function() {
183                 for( var i=0, row; row=arguments[i]; ++i )
184                         jQuery("<img>").attr("src", row);
185         },
187         /*
188          * Set up the preference validation
189          */
190         setupPrefConstraints: function() {
191                 // only allow integers for speed limit & port options
192                 $('div.preference input[@type=text]:not(#download_location)').blur( function() {
193                         this.value = this.value.replace(/[^0-9]/gi, '');
194                         if (this.value == '') {
195                                 if ($(this).is('#refresh_rate')) {
196                                         this.value = 5;
197                                 } else {
198                                         this.value = 0;
199                                 }
200                         }
201                 });
202         },
204         /*
205          * Load the clutch prefs and init the GUI according to those prefs
206          */
207         initializeSettings: function( )
208         {
209                 Prefs.getClutchPrefs( this );
211                 // iPhone conditions in the section allow us to not
212                 // include transmenu js to save some bandwidth; if we
213                 // start using prefs on iPhone we need to weed
214                 // transmenu refs out of that too.
216                 $('#filter_' + this[Prefs._FilterMode] + '_link').parent().addClass('selected');
218                 if (!iPhone) $('#sort_by_' + this[Prefs._SortMethod] ).selectMenuItem();
220                 if (!iPhone && ( this[Prefs._SortDirection] == Prefs._SortDescending ) )
221                         $('#reverse_sort_order').selectMenuItem();
223                 if( this[Prefs._ShowFilter] )
224                         this.showFilter( );
226                 if( !iPhone && this[Prefs._ShowInspector] )
227                         this.showInspector( );
229                 if( !iPhone && this[Prefs._CompactDisplayState] )
230                         $('#compact_view').selectMenuItem();
231         },
233         /*
234          * Set up the search box
235          */
236         setupSearchBox: function()
237         {
238                 var tr = this;
239                 var search_box = $('#torrent_search');
240                 search_box.bind('keyup click', {transmission: this}, function(event) {
241                         tr.setSearch(this.value);
242                 });
243                 if (!$.browser.safari)
244                 {
245                         search_box.addClass('blur');
246                         search_box[0].value = 'Filter';
247                         search_box.bind('blur', {transmission: this}, function(event) {
248                                 if (this.value == '') {
249                                         $(this).addClass('blur');
250                                         this.value = 'Filter';
251                                         tr.setSearch(null);
252                                 }
253                         }).bind('focus', {}, function(event) {
254                                 if ($(this).is('.blur')) {
255                                         this.value = '';
256                                         $(this).removeClass('blur');
257                                 }
258                         });
259                 }
260         },
262         contextStopSelected: function( ) {
263                 this.stopSelectedTorrents( );
264         },
265         contextStartSelected: function( ) {
266                 this.startSelectedTorrents( );
267         },
268         contextRemoveSelected: function( ) {
269                 this.removeSelectedTorrents( );
270         },
271         contextRemoveDataSelected: function( ) {
272                 this.removeSelectedTorrentsAndData( );
273         },
274         contextVerifySelected: function( ) {
275                 this.verifySelectedTorrents( );
276         },
277         contextToggleInspector: function( ) {
278                 this.toggleInspector( );
279         },
280         contextSelectAll: function( ) {
281                 this.selectAll( true );
282         },
283         contextDeselectAll: function( ) {
284                 this.deselectAll( true );
285         },
287         /*
288          * Create the torrent right-click menu
289          */
290         createContextMenu: function() {
291                 var tr = this;
292                 var bindings = {
293                         context_pause_selected:    function(e){ tr.contextStopSelected(e); },
294                         context_resume_selected:   function(e){ tr.contextStartSelected(e); },
295                         context_remove:            function(e){ tr.contextRemoveSelected(e); },
296                         context_removedata:        function(e){ tr.contextRemoveDataSelected(e); },
297                         context_verify:            function(e){ tr.contextVerifySelected(e); },
298                         context_toggle_inspector:  function(e){ tr.contextToggleInspector(e); },
299                         context_select_all:        function(e){ tr.contextSelectAll(e); },
300                         context_deselect_all:      function(e){ tr.contextDeselectAll(e); }
301                 };
303                 // Setup the context menu
304                 $('ul#torrent_list').contextMenu('torrent_context_menu', {
305                         bindings:          bindings,
306                         menuStyle:         Menu.context.menu_style,
307                         itemStyle:         Menu.context.item_style,
308                         itemHoverStyle:    Menu.context.item_hover_style,
309                         itemDisabledStyle: Menu.context.item_disabled_style,
310                         shadow:            false,
311                         boundingElement:   $('div#torrent_container'),
312                         boundingRightPad:  20,
313                         boundingBottomPad: 5,
314                         onContextMenu:     function(e) {
315                                 var closestRow = $(e.target).closest('.torrent')[0]._torrent;
316                                 if(!closestRow.isSelected())
317                                         tr.setSelectedTorrent( closestRow, true );
318                                 return true;
319                         }
320                 });
321         },
323         /*
324          * Create the footer settings menu
325          */
326         createSettingsMenu: function() {
327                 var tr = this;
328                 $('#settings_menu').transMenu({
329                         selected_char: '&#x2714;',
330                         direction: 'up',
331                         onClick: function(e){ return tr.processSettingsMenuEvent(e); }
332                 });
334                 $('#unlimited_download_rate').selectMenuItem();
335                 $('#unlimited_upload_rate').selectMenuItem();
336         },
339         initTurtleDropDowns: function() {
340                 var i, out, hour, mins, start, end, value, content;
341                 // Build the list of times
342                 out = "";
343                 start = $('#turtle_start_time')[0];
344                 end = $('#turtle_end_time')[0];
345                 for (i = 0; i < 24 * 4; i++) {
346                         hour = parseInt(i / 4);
347                         mins = ((i % 4) * 15);
349                         value = (i * 15);
350                         content = hour + ":" + (mins == 0 ? "00" : mins);
351                         start.options[i] = new Option(content, value);
352                         end.options[i]  = new Option(content, value);
353                 }
354         },
356         /*--------------------------------------------
357          *
358          *  U T I L I T I E S
359          *
360          *--------------------------------------------*/
362         getAllTorrents: function()
363         {
364                 var torrents = [];
365                 for(var key in this._torrents)
366                   torrents.push(this._torrents[key]);
367                 return torrents;
368         },
370         getVisibleTorrents: function()
371         {
372                 var torrents = [ ];
373                 for( var i=0, row; row=this._rows[i]; ++i )
374                         if( row._torrent && ( row[0].style.display != 'none' ) )
375                                 torrents.push( row._torrent );
376                 return torrents;
377         },
379         getSelectedTorrents: function()
380         {
381                 var v = this.getVisibleTorrents( );
382                 var s = [ ];
383                 for( var i=0, row; row=v[i]; ++i )
384                         if( row.isSelected( ) )
385                                 s.push( row );
386                 return s;
387         },
389         getDeselectedTorrents: function() {
390                 var visible_torrent_ids = jQuery.map(this.getVisibleTorrents(), function(t) { return t.id(); } );
391                 var s = [ ];
392                 jQuery.each( this.getAllTorrents( ), function() {
393                         var visible = (-1 != jQuery.inArray(this.id(), visible_torrent_ids));
394                         if (!this.isSelected() || !visible)
395                                 s.push( this );
396                 } );
397                 return s;
398         },
400         getVisibleRows: function()
401         {
402                 var rows = [ ];
403                 for( var i=0, row; row=this._rows[i]; ++i )
404                         if( row[0].style.display != 'none' )
405                                 rows.push( row );
406                 return rows;
407         },
409         getTorrentIndex: function( rows, torrent )
410         {
411                 for( var i=0, row; row=rows[i]; ++i )
412                         if( row._torrent == torrent )
413                                 return i;
414                 return null;
415         },
417         setPref: function( key, val )
418         {
419                 this[key] = val;
420                 Prefs.setValue( key, val );
421         },
423         scrollToElement: function( e )
424         {
425                 if( iPhone )
426                         return;
428                 var container = $('#torrent_container');
429                 var scrollTop = container.scrollTop( );
430                 var innerHeight = container.innerHeight( );
432                 var offsetTop = e[0].offsetTop;
433                 var offsetHeight = e.outerHeight( );
435                 if( offsetTop < scrollTop )
436                         container.scrollTop( offsetTop );
437                 else if( innerHeight + scrollTop < offsetTop + offsetHeight )
438                         container.scrollTop( offsetTop + offsetHeight - innerHeight );
439         },
441         seedRatioLimit: function(){
442                 if(this._prefs && this._prefs['seedRatioLimited'])
443                         return this._prefs['seedRatioLimit'];
444                 else
445                         return -1;
446         },
448         /*--------------------------------------------
449          *
450          *  S E L E C T I O N
451          *
452          *--------------------------------------------*/
454         setSelectedTorrent: function( torrent, doUpdate ) {
455                 this.deselectAll( );
456                 this.selectTorrent( torrent, doUpdate );
457         },
459         selectElement: function( e, doUpdate ) {
460                 e.addClass('selected');
461                 this.scrollToElement( e );
462                 if( doUpdate )
463                         this.selectionChanged( );
464         },
465         selectRow: function( rowIndex, doUpdate ) {
466                 this.selectElement( this._rows[rowIndex], doUpdate );
467         },
468         selectTorrent: function( torrent, doUpdate ) {
469                 if( torrent._element )
470                         this.selectElement( torrent._element, doUpdate );
471         },
473         deselectElement: function( e, doUpdate ) {
474                 e.removeClass('selected');
475                 if( doUpdate )
476                         this.selectionChanged( );
477         },
478         deselectTorrent: function( torrent, doUpdate ) {
479                 if( torrent._element )
480                         this.deselectElement( torrent._element, doUpdate );
481         },
483         selectAll: function( doUpdate ) {
484                 var tr = this;
485                 for( var i=0, row; row=tr._rows[i]; ++i )
486                         tr.selectElement( row );
487                 if( doUpdate )
488                         tr.selectionChanged();
489         },
490         deselectAll: function( doUpdate ) {
491                 var tr = this;
492                 for( var i=0, row; row=tr._rows[i]; ++i )
493                         tr.deselectElement( row );
494                 tr._last_torrent_clicked = null;
495                 if( doUpdate )
496                         tr.selectionChanged( );
497         },
499         /*
500          * Select a range from this torrent to the last clicked torrent
501          */
502         selectRange: function( torrent, doUpdate )
503         {
504                 if( !this._last_torrent_clicked )
505                 {
506                         this.selectTorrent( torrent );
507                 }
508                 else // select the range between the prevous & current
509                 {
510                         var rows = this.getVisibleRows( );
511                         var i = this.getTorrentIndex( rows, this._last_torrent_clicked );
512                         var end = this.getTorrentIndex( rows, torrent );
513                         var step = i < end ? 1 : -1;
514                         for( ; i!=end; i+=step )
515                                 this.selectRow( i );
516                         this.selectRow( i );
517                 }
519                 if( doUpdate )
520                         this.selectionChanged( );
521         },
523         selectionChanged: function()
524         {
525                 this.updateButtonStates();
526                 this.updateInspector();
527                 this.updateSelectedData();
528         },
530         /*--------------------------------------------
531          *
532          *  E V E N T   F U N C T I O N S
533          *
534          *--------------------------------------------*/
536         /*
537          * Process key event
538          */
539         keyDown: function(event)
540         {
541                 var tr = this;
542                 var sel = tr.getSelectedTorrents( );
543                 var rows = tr.getVisibleRows( );
544                 var i = -1;
546                 if( event.keyCode == 40 ) // down arrow
547                 {
548                         var t = sel.length ? sel[sel.length-1] : null;
549                         i = t==null ? null : tr.getTorrentIndex(rows,t)+1;
550                         if( i == rows.length || i == null )
551                                 i = 0;
552                 }
553                 else if( event.keyCode == 38 ) // up arrow
554                 {
555                         var t = sel.length ? sel[0] : null
556                         i = t==null ? null : tr.getTorrentIndex(rows,t)-1;
557                         if( i == -1 || i == null )
558                                 i = rows.length - 1;
559                 }
561                 if( 0<=i && i<rows.length ) {
562                         tr.deselectAll( );
563                         tr.selectRow( i, true );
564                 }
565         },
567         isButtonEnabled: function(e) {
568                 var p = e.target ? e.target.parentNode : e.srcElement.parentNode;
569                 return p.className!='disabled' && p.parentNode.className!='disabled';
570         },
572         stopAllClicked: function( event ) {
573                 var tr = this;
574                 if( tr.isButtonEnabled( event ) ) {
575                         tr.stopAllTorrents( );
576                         tr.hideiPhoneAddressbar( );
577                 }
578         },
580         stopSelectedClicked: function( event ) {
581                 var tr = this;
582                 if( tr.isButtonEnabled( event ) ) {
583                         tr.stopSelectedTorrents( );
584                         tr.hideiPhoneAddressbar( );
585                 }
586         },
588         startAllClicked: function( event ) {
589                 var tr = this;
590                 if( tr.isButtonEnabled( event ) ) {
591                         tr.startAllTorrents( );
592                         tr.hideiPhoneAddressbar( );
593                 }
594         },
596         startSelectedClicked: function( event ) {
597                 var tr = this;
598                 if( tr.isButtonEnabled( event ) ) {
599                         tr.startSelectedTorrents( );
600                         tr.hideiPhoneAddressbar( );
601                 }
602         },
604         openTorrentClicked: function( event ) {
605                 var tr = this;
606                 if( tr.isButtonEnabled( event ) ) {
607                         $('body').addClass('open_showing');
608                         tr.uploadTorrentFile( );
609                 }
610                 tr.updateButtonStates();
611         },
613         hideUploadDialog: function( ) {
614                 $('body.open_showing').removeClass('open_showing');
615                 if (!iPhone && Safari3) {
616                         $('div#upload_container div.dialog_window').css('top', '-205px');
617                         setTimeout("$('#upload_container').hide();",500);
618                 } else {
619                         $('#upload_container').hide();
620                 }
621                 this.updateButtonStates();
622         },
624         cancelUploadClicked: function(event) {
625                 this.hideUploadDialog( );
626         },
628         confirmUploadClicked: function(event) {
629                 this.uploadTorrentFile( true );
630                 this.hideUploadDialog( );
631         },
633         cancelPrefsClicked: function(event) {
634                 this.hidePrefsDialog( );
635         },
637         savePrefsClicked: function(event)
638         {
639                 // handle the clutch prefs locally
640                 var tr = this;
641                 var rate = parseInt( $('#prefs_form #refresh_rate')[0].value );
642                 if( rate != tr[Prefs._RefreshRate] ) {
643                         tr.setPref( Prefs._RefreshRate, rate );
644                         tr.togglePeriodicRefresh( false );
645                         tr.togglePeriodicRefresh( true );
646                 }
648                 var up_bytes        = parseInt( $('#prefs_form #upload_rate'  )[0].value );
649                 var dn_bytes        = parseInt( $('#prefs_form #download_rate')[0].value );
650                 var turtle_up_bytes = parseInt( $('#prefs_form #turtle_upload_rate'  )[0].value );
651                 var turtle_dn_bytes = parseInt( $('#prefs_form #turtle_download_rate')[0].value );
653                 // pass the new prefs upstream to the RPC server
654                 var o = { };
655                 o[RPC._StartAddedTorrent]    = $('#prefs_form #auto_start')[0].checked;
656                 o[RPC._PeerPort]             = parseInt( $('#prefs_form #port')[0].value );
657                 o[RPC._UpSpeedLimit]         = up_bytes;
658                 o[RPC._DownSpeedLimit]       = dn_bytes;
659                 o[RPC._DownloadDir]          = $('#prefs_form #download_location')[0].value;
660                 o[RPC._UpSpeedLimited]       = $('#prefs_form #limit_upload'  )[0].checked;
661                 o[RPC._DownSpeedLimited]     = $('#prefs_form #limit_download')[0].checked;
662                 o[RPC._Encryption]           = $('#prefs_form #encryption')[0].checked
663                                                    ? RPC._EncryptionRequired
664                                                    : RPC._EncryptionPreferred;
665                 o[RPC._TurtleDownSpeedLimit] = turtle_dn_bytes;
666                 o[RPC._TurtleUpSpeedLimit]   = turtle_up_bytes;
667                 o[RPC._TurtleTimeEnabled]    = $('#prefs_form #turtle_schedule')[0].checked;
668                 o[RPC._TurtleTimeBegin]      = parseInt( $('#prefs_form #turtle_start_time').val() );
669                 o[RPC._TurtleTimeEnd]        = parseInt( $('#prefs_form #turtle_end_time').val() );
670                 o[RPC._TurtleTimeDay]        = parseInt( $('#prefs_form #turtle_days').val() );
672                 tr.remote.savePrefs( o );
674                 tr.hidePrefsDialog( );
675         },
677         closeStatsClicked: function(event) {
678                 this.hideStatsDialog( );
679         },
681         removeClicked: function( event ) {
682                 var tr = this;
683                 if( tr.isButtonEnabled( event ) ) {
684                         tr.removeSelectedTorrents( );
685                         tr.hideiPhoneAddressbar( );
686                 }
687         },
689         toggleInspectorClicked: function( event ) {
690                 var tr = this;
691                 if( tr.isButtonEnabled( event ) )
692                         tr.toggleInspector( );
693         },
695         inspectorTabClicked: function(event, tab) {
696                 if (iPhone) event.stopPropagation();
698                 // Select the clicked tab, unselect the others,
699                 // and display the appropriate info
700                 var tab_ids = $(tab).parent('#inspector_tabs').find('.inspector_tab').map(
701                         function() { return $(this).attr('id'); }
702                 );
703                 for( var i=0, row; row=tab_ids[i]; ++i ) {
704                         if (tab.id == row) {
705                                 $('#'+row).addClass('selected');
706                                 $('#'+row+'_container').show();
707                         } else {
708                                 $('#'+row).removeClass('selected');
709                                 $('#'+row+'_container').hide();
710                         }
711                 }
712                 this.hideiPhoneAddressbar();
714                 this.updateVisibleFileLists();
715                 this.updatePeersLists();
716                 this.updateTrackersLists();
717         },
719         fileWantedClicked: function(event, element){
720                 this.extractFileFromElement(element).fileWantedControlClicked(event);
721         },
723         filePriorityClicked: function(event, element){
724                 this.extractFileFromElement(element).filePriorityControlClicked(event, element);
725         },
727         filesSelectAllClicked: function(event) {
728                 var tr = this;
729                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
730                 var files_list = this.toggleFilesWantedDisplay(ids, true);
731                 for (i = 0; i < ids.length; ++i) {
732                         if (files_list[i].length)
733                                 this.remote.filesSelectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
734                 }
735         },
737         filesDeselectAllClicked: function(event) {
738                 var tr = this;
739                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
740                 var files_list = this.toggleFilesWantedDisplay(ids, false);
741                 for (i = 0; i < ids.length; ++i) {
742                         if (files_list[i].length)
743                                 this.remote.filesDeselectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
744                 }
745         },
747         extractFileFromElement: function(element) {
748                 var match = $(element).closest('.inspector_torrent_file_list_entry').attr('id').match(/^t(\d+)f(\d+)$/);
749                 var torrent_id = match[1];
750                 var file_id = match[2];
751                 var torrent = this._torrents[torrent_id];
752                 return torrent._file_view[file_id];
753         },
755         toggleFilesWantedDisplay: function(ids, wanted) {
756                 var i, j, k, torrent, files_list = [ ];
757                 for (i = 0; i < ids.length; ++i) {
758                         torrent = this._torrents[ids[i]];
759                         files_list[i] = [ ];
760                         for (j = k = 0; j < torrent._file_view.length; ++j) {
761                                 if (torrent._file_view[j].isEditable() && torrent._file_view[j]._wanted != wanted) {
762                                         torrent._file_view[j].setWanted(wanted, false);
763                                         files_list[i][k++] = j;
764                                 }
765                         }
766                         torrent.refreshFileView;
767                 }
768                 return files_list;
769         },
771         toggleFilterClicked: function(event) {
772                 if (this.isButtonEnabled(event))
773                         this.toggleFilter();
774         },
775         setFilter: function( mode )
776         {
777                 // update the radiobuttons
778                 var c;
779                 switch( mode ) {
780                         case Prefs._FilterAll:         c = '#filter_all_link'; break;
781                         case Prefs._FilterActive:      c = '#filter_active_link'; break;
782                         case Prefs._FilterSeeding:     c = '#filter_seeding_link'; break;
783                         case Prefs._FilterDownloading: c = '#filter_downloading_link'; break;
784                         case Prefs._FilterPaused:      c = '#filter_paused_link'; break;
785                         case Prefs._FilterFinished:    c = '#filter_finished_link'; break;
786                 }
787                 $(c).parent().siblings().removeClass('selected');
788                 $(c).parent().addClass('selected');
790                 // do the filtering
791                 this.setPref( Prefs._FilterMode, mode );
792                 this.refilter( );
793         },
794         showAllClicked: function( event ) {
795                 this.setFilter( Prefs._FilterAll );
796         },
797         showActiveClicked: function( event ) {
798                 this.setFilter( Prefs._FilterActive );
799         },
800         showDownloadingClicked: function( event ) {
801                 this.setFilter( Prefs._FilterDownloading );
802         },
803         showSeedingClicked: function(event) {
804                 this.setFilter( Prefs._FilterSeeding );
805         },
806         showPausedClicked: function(event) {
807                 this.setFilter( Prefs._FilterPaused );
808         },
809         showFinishedClicked: function(event) {
810                 this.setFilter( Prefs._FilterFinished );
811         },
813         /*
814          * 'Clutch Preferences' was clicked (iPhone only)
815          */
816         releaseClutchPreferencesButton: function(event) {
817                 $('div#prefs_container div#pref_error').hide();
818                 $('div#prefs_container h2.dialog_heading').show();
819                 this.showPrefsDialog( );
820         },
822         /*
823          * Turn the periodic ajax torrents refresh on & off
824          */
825         togglePeriodicRefresh: function(state) {
826                 var tr = this;
827                 if (state && this._periodic_refresh == null) {
828                         // sanity check
829                         if( !this[Prefs._RefreshRate] )
830                              this[Prefs._RefreshRate] = 5;
831                         remote = this.remote;
832                         this._periodic_refresh = setInterval(function(){ tr.refreshTorrents(); }, this[Prefs._RefreshRate] * 1000 );
833                 } else {
834                         clearInterval(this._periodic_refresh);
835                         this._periodic_refresh = null;
836                 }
837         },
839         /*
840          * Turn the periodic ajax torrents refresh on & off for the selected torrents
841          */
842         periodicTorrentUpdate: function( ids ) {
843                 var tr = this;
844                 if( ids ) {
845                         var curIds = this._extra_data_ids;
846                         if( curIds == null )
847                                 curIds = [ ];
848                         if( ids.length == curIds.length ) {
849                                 var duplicate = true;
850                                 for(var i = 0; i < ids.length; i++ ) {
851                                         if( ids[i] != curIds[i] ) {
852                                                 duplicate = false;
853                                                 break;
854                                         }
855                                 }
856                                 if( duplicate ) return;
857                         }
858                         tr.refreshTorrents(ids);
859                         clearInterval(this._metadata_refresh);
860                         // sanity check
861                         if( !this[Prefs._RefreshRate] ) this[Prefs._RefreshRate] = 5;
862                         this._metadata_refresh = setInterval(function(){ tr.refreshTorrents(ids); }, this[Prefs._RefreshRate] * 1000 );
863                         this._extra_data_ids = ids;
864                 } else {
865                         clearInterval(this._metadata_refresh);
866                         this._metadata_refresh = null;
867                         this._extra_data_ids = null;
868                 }
869         },
871         /*
872          * Turn the periodic ajax session refresh on & off
873          */
874         togglePeriodicSessionRefresh: function(state) {
875                 var tr = this;
876                 if (state && this._periodic_session_refresh == null) {
877                         // sanity check
878                         if( !this[Prefs._SessionRefreshRate] )
879                              this[Prefs._SessionRefreshRate] = 5;
880                         remote = this.remote;
881                         this._periodic_session_refresh = setInterval(
882                                 function(){ tr.loadDaemonPrefs(); }, this[Prefs._SessionRefreshRate] * 1000
883                         );
884                 } else {
885                         clearInterval(this._periodic_session_refresh);
886                         this._periodic_session_refresh = null;
887                 }
888         },
890         /*
891          * Turn the periodic ajax stats refresh on & off
892          */
893         togglePeriodicStatsRefresh: function(state) {
894                 var tr = this;
895                 if (state && this._periodic_stats_refresh == null) {
896                         // sanity check
897                         if( !this[Prefs._SessionRefreshRate] )
898                              this[Prefs._SessionRefreshRate] = 5;
899                         remote = this.remote;
900                         this._periodic_stats_refresh = setInterval(
901                                 function(){ tr.loadDaemonStats(); }, this[Prefs._SessionRefreshRate] * 1000
902                         );
903                 } else {
904                         clearInterval(this._periodic_stats_refresh);
905                         this._periodic_stats_refresh = null;
906                 }
907         },
909         toggleTurtleClicked: function() {
910                 // Toggle the value
911                 this[Prefs._TurtleState] = !this[Prefs._TurtleState];
912                 // Store the result
913                 var args = { };
914                 args[RPC._TurtleState] = this[Prefs._TurtleState];
915                 this.remote.savePrefs( args );
916         },
918         updateSelectedData: function()
919         {
920                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
921                 if( ids.length > 0 )
922                         this.periodicTorrentUpdate( ids );
923                 else
924                         this.periodicTorrentUpdate( false );
925         },
927         updateTurtleButton: function() {
928                 var t;
929                 var w = $('#turtle_button');
930                 if ( this[Prefs._TurtleState] ) {
931                         w.addClass('turtleEnabled');
932                         w.removeClass('turtleDisabled');
933                         t = [ 'Click to disable Temporary Speed Limits' ];
934                 } else {
935                         w.removeClass('turtleEnabled');
936                         w.addClass('turtleDisabled');
937                         t = [ 'Click to enable Temporary Speed Limits' ];
938                 }
939                 t.push( '(', Transmission.fmt.speed(this._prefs[RPC._TurtleUpSpeedLimit]), 'up,',
940                              Transmission.fmt.speed(this._prefs[RPC._TurtleDownSpeedLimit]), 'down)' );
941                 w.attr( 'title', t.join(' ') );
942         },
944         /*--------------------------------------------
945          *
946          *  I N T E R F A C E   F U N C T I O N S
947          *
948          *--------------------------------------------*/
950         showPrefsDialog: function( ) {
951                 $('body').addClass('prefs_showing');
952                 $('#prefs_container').show();
953                 this.hideiPhoneAddressbar();
954                 if( Safari3 )
955                         setTimeout("$('div#prefs_container div.dialog_window').css('top', '0px');",10);
956                 this.updateButtonStates( );
957                 this.togglePeriodicSessionRefresh(false);
958         },
960         hidePrefsDialog: function( )
961         {
962                 $('body.prefs_showing').removeClass('prefs_showing');
963                 if (iPhone) {
964                         this.hideiPhoneAddressbar();
965                         $('#prefs_container').hide();
966                 } else if (Safari3) {
967                         $('div#prefs_container div.dialog_window').css('top', '-425px');
968                         setTimeout("$('#prefs_container').hide();",500);
969                 } else {
970                         $('#prefs_container').hide();
971                 }
972                 this.updateButtonStates( );
973                 this.togglePeriodicSessionRefresh(true);
974         },
976         /*
977          * Process got some new session data from the server
978          */
979         updatePrefs: function( prefs )
980         {
981                 // remember them for later
982                 this._prefs = prefs;
984                 var up_limited        = prefs[RPC._UpSpeedLimited];
985                 var dn_limited        = prefs[RPC._DownSpeedLimited];
986                 var up_limit_k        = prefs[RPC._UpSpeedLimit];
987                 var dn_limit_k        = prefs[RPC._DownSpeedLimit];
988                 var turtle_up_limit_k = prefs[RPC._TurtleUpSpeedLimit];
989                 var turtle_dn_limit_k = prefs[RPC._TurtleDownSpeedLimit];
991                 if( prefs.units )
992                     Transmission.fmt.updateUnits( prefs.units );
994                 $('div.download_location input')[0].value = prefs[RPC._DownloadDir];
995                 $('div.port input')[0].value              = prefs[RPC._PeerPort];
996                 $('div.auto_start input')[0].checked      = prefs[RPC._StartAddedTorrent];
997                 $('input#limit_download')[0].checked      = dn_limited;
998                 $('input#download_rate')[0].value         = dn_limit_k;
999                 $('input#limit_upload')[0].checked        = up_limited;
1000                 $('input#upload_rate')[0].value           = up_limit_k;
1001                 $('input#refresh_rate')[0].value          = prefs[Prefs._RefreshRate];
1002                 $('div.encryption input')[0].checked      = prefs[RPC._Encryption] == RPC._EncryptionRequired;
1003                 $('input#turtle_download_rate')[0].value  = turtle_dn_limit_k;
1004                 $('input#turtle_upload_rate')[0].value    = turtle_up_limit_k;
1005                 $('input#turtle_schedule')[0].checked     = prefs[RPC._TurtleTimeEnabled];
1006                 $('select#turtle_start_time').val(          prefs[RPC._TurtleTimeBegin] );
1007                 $('select#turtle_end_time').val(            prefs[RPC._TurtleTimeEnd] );
1008                 $('select#turtle_days').val(                prefs[RPC._TurtleTimeDay] );
1009                 $('#transmission_version').text(            prefs[RPC._DaemonVersion] );
1011                 if (!iPhone)
1012                 {
1013                         setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(dn_limit_k), ')' ].join('') );
1014                         var key = dn_limited ? '#limited_download_rate'
1015                                                : '#unlimited_download_rate';
1016                         $(key).deselectMenuSiblings().selectMenuItem();
1018                         setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(up_limit_k), ')' ].join('') );
1019                         key = up_limited ? '#limited_upload_rate'
1020                                          : '#unlimited_upload_rate';
1021                         $(key).deselectMenuSiblings().selectMenuItem();
1022                 }
1024                 this[Prefs._TurtleState] = prefs[RPC._TurtleState];
1025                 this.updateTurtleButton();
1026         },
1028         showStatsDialog: function( ) {
1029                 this.loadDaemonStats();
1030                 $('body').addClass('stats_showing');
1031                 $('#stats_container').show();
1032                 this.hideiPhoneAddressbar();
1033                 if( Safari3 )
1034                         setTimeout("$('div#stats_container div.dialog_window').css('top', '0px');",10);
1035                 this.updateButtonStates( );
1036                 this.togglePeriodicStatsRefresh(true);
1037         },
1039         hideStatsDialog: function( ){
1040                 $('body.stats_showing').removeClass('stats_showing');
1041                 if (iPhone) {
1042                         this.hideiPhoneAddressbar();
1043                         $('#stats_container').hide();
1044                 } else if (Safari3) {
1045                         $('div#stats_container div.dialog_window').css('top', '-425px');
1046                         setTimeout("$('#stats_container').hide();",500);
1047                 } else {
1048                         $('#stats_container').hide();
1049                 }
1050                 this.updateButtonStates( );
1051                 this.togglePeriodicStatsRefresh(false);
1052         },
1054         /*
1055          * Process got some new session stats from the server
1056          */
1057         updateStats: function( stats )
1058         {
1059                 // can't think of a reason to remember this
1060                 //this._stats = stats;
1062                 var fmt = Transmission.fmt;
1063                 var session = stats["current-stats"];
1064                 var total = stats["cumulative-stats"];
1066                 setInnerHTML( $('#stats_session_uploaded')[0], fmt.size(session["uploadedBytes"]) );
1067                 setInnerHTML( $('#stats_session_downloaded')[0], fmt.size(session["downloadedBytes"]) );
1068                 setInnerHTML( $('#stats_session_ratio')[0], fmt.ratioString(Math.ratio(session["uploadedBytes"],session["downloadedBytes"])));
1069                 setInnerHTML( $('#stats_session_duration')[0], fmt.timeInterval(session["secondsActive"]) );
1070                 setInnerHTML( $('#stats_total_count')[0], total["sessionCount"] + " times" );
1071                 setInnerHTML( $('#stats_total_uploaded')[0], fmt.size(total["uploadedBytes"]) );
1072                 setInnerHTML( $('#stats_total_downloaded')[0], fmt.size(total["downloadedBytes"]) );
1073                 setInnerHTML( $('#stats_total_ratio')[0], fmt.ratioString(Math.ratio(total["uploadedBytes"],total["downloadedBytes"])));
1074                 setInnerHTML( $('#stats_total_duration')[0], fmt.timeInterval(total["secondsActive"]) );
1075         },
1077         setSearch: function( search ) {
1078                 this._current_search = search ? search.trim() : null;
1079                 this.refilter( );
1080         },
1082         setSortMethod: function( sort_method ) {
1083                 this.setPref( Prefs._SortMethod, sort_method );
1084                 this.refilter( );
1085         },
1087         setSortDirection: function( direction ) {
1088                 this.setPref( Prefs._SortDirection, direction );
1089                 this.refilter( );
1090         },
1092         /*
1093          * Process an event in the footer-menu
1094          */
1095         processSettingsMenuEvent: function(event) {
1096                 var tr = this;
1097                 var $element = $(event.target);
1099                 // Figure out which menu has been clicked
1100                 switch ($element.parent()[0].id) {
1102                         // Display the preferences dialog
1103                         case 'footer_super_menu':
1104                                 if ($element[0].id == 'preferences') {
1105                                         $('div#prefs_container div#pref_error').hide();
1106                                         $('div#prefs_container h2.dialog_heading').show();
1107                                         tr.showPrefsDialog( );
1108                                 }
1109                                 else if ($element[0].id == 'statistics') {
1110                                         $('div#stats_container div#stats_error').hide();
1111                                         $('div#stats_container h2.dialog_heading').show();
1112                                         tr.showStatsDialog( );
1113                                 }
1114                                 else if ($element[0].id == 'compact_view') {
1115                                         this.setPref( Prefs._CompactDisplayState, !this[Prefs._CompactDisplayState])
1116                                         if(this[Prefs._CompactDisplayState])
1117                                                 $element.selectMenuItem();
1118                                         else
1119                                                 $element.deselectMenuItem();
1120                                         this.refreshDisplay( );
1121                                 }
1122                                 else if ($element[0].id == 'homepage') {
1123                                         window.open('http://www.transmissionbt.com/');
1124                                 }
1125                                 else if ($element[0].id == 'tipjar') {
1126                                         window.open('http://www.transmissionbt.com/donate.php');
1127                                 }
1128                                 break;
1130                         // Limit the download rate
1131                         case 'footer_download_rate_menu':
1132                                 var args = { };
1133                                 if ($element.is('#unlimited_download_rate')) {
1134                                         $element.deselectMenuSiblings().selectMenuItem();
1135                                         args[RPC._DownSpeedLimited] = false;
1136                                 } else {
1137                                         var rate_str = ($element[0].innerHTML).replace(/[^0-9]/ig, '');
1138                                         var rate_val = parseInt( rate_str );
1139                                         setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('') );
1140                                         $('#limited_download_rate').deselectMenuSiblings().selectMenuItem();
1141                                         $('div.preference input#download_rate')[0].value = rate_str;
1142                                         args[RPC._DownSpeedLimit] = rate_val;
1143                                         args[RPC._DownSpeedLimited] = true;
1144                                 }
1145                                 $('div.preference input#limit_download')[0].checked = args[RPC._DownSpeedLimited];
1146                                 tr.remote.savePrefs( args );
1147                                 break;
1149                         // Limit the upload rate
1150                         case 'footer_upload_rate_menu':
1151                                 var args = { };
1152                                 if ($element.is('#unlimited_upload_rate')) {
1153                                         $element.deselectMenuSiblings().selectMenuItem();
1154                                         args[RPC._UpSpeedLimited] = false;
1155                                 } else {
1156                                         var rate_str = ($element[0].innerHTML).replace(/[^0-9]/ig, '');
1157                                         var rate_val = parseInt( rate_str );
1158                                         setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('')  );
1159                                         $('#limited_upload_rate').deselectMenuSiblings().selectMenuItem();
1160                                         $('div.preference input#upload_rate')[0].value = rate_str;
1161                                         args[RPC._UpSpeedLimit] = rate_val;
1162                                         args[RPC._UpSpeedLimited] = true;
1163                                 }
1164                                 $('div.preference input#limit_upload')[0].checked = args[RPC._UpSpeedLimited];
1165                                 tr.remote.savePrefs( args );
1166                                 break;
1168                         // Sort the torrent list
1169                         case 'footer_sort_menu':
1171                                 // The 'reverse sort' option state can be toggled independently of the other options
1172                                 if ($element.is('#reverse_sort_order')) {
1173                                         if(!$element.is('#reverse_sort_order.active')) break;
1174                                         var dir;
1175                                         if ($element.menuItemIsSelected()) {
1176                                                 $element.deselectMenuItem();
1177                                                 dir = Prefs._SortAscending;
1178                                         } else {
1179                                                 $element.selectMenuItem();
1180                                                 dir = Prefs._SortDescending;
1181                                         }
1182                                         tr.setSortDirection( dir );
1184                                 // Otherwise, deselect all other options (except reverse-sort) and select this one
1185                                 } else {
1186                                         $element.parent().find('span.selected').each( function() {
1187                                                 if (! $element.parent().is('#reverse_sort_order')) {
1188                                                         $element.parent().deselectMenuItem();
1189                                                 }
1190                                         });
1191                                         $element.selectMenuItem();
1192                                         var method = $element[0].id.replace(/sort_by_/, '');
1193                                         tr.setSortMethod( method );
1194                                 }
1195                                 break;
1196                 }
1197                 $('#settings_menu').trigger('closemenu');
1198                 return false; // to prevent the event from bubbling up
1199         },
1201         setLastTorrentClicked: function( torrent )
1202         {
1203                 this._last_torrent_clicked = torrent;
1204         },
1206         /*
1207          * Update the inspector with the latest data for the selected torrents
1208          */
1209         updateInspector: function()
1210         {
1211                 if( !this[Prefs._ShowInspector] )
1212                         return;
1214                 var torrents = this.getSelectedTorrents( );
1215                 if( !torrents.length && iPhone ) {
1216                         this.hideInspector();
1217                         return;
1218                 }
1220                 var creator = 'N/A';
1221                 var comment = 'N/A';
1222                 var download_dir = 'N/A';
1223                 var date_created = 'N/A';
1224                 var error = 'None';
1225                 var hash = 'N/A';
1226                 var have_public = false;
1227                 var have_private = false;
1228                 var name;
1229                 var sizeWhenDone = 0;
1230                 var sizeDone = 0;
1231                 var total_completed = 0;
1232                 var total_download = 0;
1233                 var total_download_peers = 0;
1234                 var total_download_speed = 0;
1235                 var total_availability = 0;
1236                 var total_have = 0;
1237                 var total_size = 0;
1238                 var total_state = [ ];
1239                 var pieces = 'N/A';
1240                 var total_upload = 0;
1241                 var total_upload_peers = 0;
1242                 var total_upload_speed = 0;
1243                 var total_verified = 0;
1244                 var na = 'N/A';
1245                 var tab = this._inspector._info_tab;
1247                 $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
1249                 if( torrents.length == 0 )
1250                 {
1251                         setInnerHTML( tab.name, 'No Selection' );
1252                         setInnerHTML( tab.size, na );
1253                         setInnerHTML( tab.pieces, na );
1254                         setInnerHTML( tab.hash, na );
1255                         setInnerHTML( tab.state, na );
1256                         setInnerHTML( tab.download_speed, na );
1257                         setInnerHTML( tab.upload_speed, na );
1258                         setInnerHTML( tab.uploaded, na );
1259                         setInnerHTML( tab.downloaded, na );
1260                         setInnerHTML( tab.availability, na );
1261                         setInnerHTML( tab.ratio, na );
1262                         setInnerHTML( tab.have, na );
1263                         setInnerHTML( tab.upload_to, na );
1264                         setInnerHTML( tab.download_from, na );
1265                         setInnerHTML( tab.secure, na );
1266                         setInnerHTML( tab.creator_date, na );
1267                         setInnerHTML( tab.progress, na );
1268                         setInnerHTML( tab.comment, na );
1269                         setInnerHTML( tab.creator, na );
1270                         setInnerHTML( tab.download_dir, na );
1271                         setInnerHTML( tab.error, na );
1272                         this.updateVisibleFileLists();
1273                         this.updatePeersLists();
1274                         this.updateTrackersLists();
1275                         $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666');
1276                         return;
1277                 }
1279                 name = torrents.length == 1
1280                         ? torrents[0].name()
1281                         : torrents.length+' Transfers Selected';
1283                 if( torrents.length == 1 )
1284                 {
1285                         var t = torrents[0];
1286                         var err = t.getErrorMessage( );
1287                         if( err )
1288                                 error = err;
1289                         if( t._comment)
1290                                 comment = t._comment ;
1291                         if( t._creator )
1292                                 creator = t._creator ;
1293                         if( t._download_dir)
1294                                 download_dir = t._download_dir;
1296                         hash = t.hash();
1297                         pieces = [ t._pieceCount, 'pieces @', Transmission.fmt.mem(t._pieceSize) ].join(' ');
1298                         date_created = Transmission.fmt.timestamp( t._creator_date );
1299                 }
1301                 for( var i=0, t; t=torrents[i]; ++i ) {
1302                         sizeWhenDone         += t._sizeWhenDone;
1303                         sizeDone             += t._sizeWhenDone - t._leftUntilDone;
1304                         total_completed      += t.completed();
1305                         total_verified       += t._verified;
1306                         total_size           += t.size();
1307                         total_upload         += t.uploadTotal();
1308                         total_download       += t.downloadTotal();
1309                         total_upload_speed   += t.uploadSpeed();
1310                         total_download_speed += t.downloadSpeed();
1311                         total_upload_peers   += t.peersGettingFromUs();
1312                         total_download_peers += t.peersSendingToUs();
1313                         total_availability   += t._sizeWhenDone - t._leftUntilDone + t._desiredAvailable;
1315                         var s = t.stateStr();
1316                         if( total_state.indexOf( s ) == -1 )
1317                                 total_state.push( s );
1319                         if( t._is_private )
1320                                 have_private = true;
1321                         else
1322                                 have_public = true;
1323                 }
1325                 var private_string = '';
1326                 var fmt = Transmission.fmt;
1327                 if( have_private && have_public ) private_string = 'Mixed';
1328                 else if( have_private ) private_string = 'Private Torrent';
1329                 else if( have_public ) private_string = 'Public Torrent';
1331                 setInnerHTML( tab.name, name );
1332                 setInnerHTML( tab.size, torrents.length ? fmt.size( total_size ) : na );
1333                 setInnerHTML( tab.pieces, pieces );
1334                 setInnerHTML( tab.hash, hash );
1335                 setInnerHTML( tab.state, total_state.join('/') );
1336                 setInnerHTML( tab.download_speed, torrents.length ? fmt.speedBps( total_download_speed ) : na );
1337                 setInnerHTML( tab.upload_speed, torrents.length ? fmt.speedBps( total_upload_speed ) : na );
1338                 setInnerHTML( tab.uploaded, torrents.length ? fmt.size( total_upload ) : na );
1339                 setInnerHTML( tab.downloaded, torrents.length ? fmt.size( total_download ) : na );
1340                 setInnerHTML( tab.availability, torrents.length ? fmt.percentString(Math.ratio( total_availability*100, sizeWhenDone )) + '%' : na );
1341                 setInnerHTML( tab.ratio, torrents.length ? fmt.ratioString(Math.ratio( total_upload, total_download )) : na );
1342                 setInnerHTML( tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na );
1343                 setInnerHTML( tab.upload_to, torrents.length ? total_upload_peers : na );
1344                 setInnerHTML( tab.download_from, torrents.length ? total_download_peers : na );
1345                 setInnerHTML( tab.secure, private_string );
1346                 setInnerHTML( tab.creator_date, date_created );
1347                 setInnerHTML( tab.progress, torrents.length ? fmt.percentString(Math.ratio( sizeDone*100, sizeWhenDone )) + '%' : na );
1348                 setInnerHTML( tab.comment, comment == na ? comment : comment.replace(/\//g, '/&#8203;') );
1349                 setInnerHTML( tab.creator, creator );
1350                 setInnerHTML( tab.download_dir, download_dir == na ? download_dir : download_dir.replace(/([\/_\.])/g, "$1&#8203;") );
1351                 setInnerHTML( tab.error, error );
1353                 this.updatePeersLists();
1354                 this.updateTrackersLists();
1355                 $(".inspector_row > div:contains('N/A')").css('color', '#666');
1356                 this.updateVisibleFileLists();
1357         },
1359         fileListIsVisible: function() {
1360                 return this._inspector_tab_files.className.indexOf('selected') != -1;
1361         },
1363         updateVisibleFileLists: function() {
1364                 if( this.fileListIsVisible( ) === true ) {
1365                         var selected = this.getSelectedTorrents();
1366                         jQuery.each( selected, function() { this.showFileList(); } );
1367                         jQuery.each( this.getDeselectedTorrents(), function() { this.hideFileList(); } );
1368                         // Check if we need to display the select all buttions
1369                         if ( !selected.length ) {
1370                                 if ( $("#select_all_button_container").is(':visible') )
1371                                         $("#select_all_button_container").hide();
1372                         } else {
1373                                 if ( !$("#select_all_button_container").is(':visible') )
1374                                         $("#select_all_button_container").show();
1375                         }
1376                 }
1377         },
1379         updatePeersLists: function() {
1380                 var tr = this;
1381                 var html = [ ];
1382                 var fmt = Transmission.fmt;
1383                 var torrents = this.getSelectedTorrents( );
1384                 if( $(this._inspector_peers_list).is(':visible') ) {
1385                         for( var k=0, torrent; torrent=torrents[k]; ++k ) {
1386                                 html.push( '<div class="inspector_group">' );
1387                                 if( torrents.length > 1 ) {
1388                                         html.push( '<div class="inspector_torrent_label">', torrent._name, '</div>' );
1389                                 }
1390                                 if( torrent._peers.length == 0 ) {
1391                                         html.push( '<br></div>' ); // firefox won't paint the top border if the div is empty
1392                                         continue;
1393                                 }
1394                                 html.push( '<table class="peer_list">',
1395                                            '<tr class="inspector_peer_entry even">',
1396                                            '<th class="encryptedCol"></th>',
1397                                            '<th class="upCol">Up</th>',
1398                                            '<th class="downCol">Down</th>',
1399                                            '<th class="percentCol">%</th>',
1400                                            '<th class="statusCol">Status</th>',
1401                                            '<th class="addressCol">Address</th>',
1402                                            '<th class="clientCol">Client</th>',
1403                                            '</tr>' );
1404                                 for( var i=0, peer; peer=torrent._peers[i]; ++i ) {
1405                                         var parity = ((i+1) % 2 == 0 ? 'even' : 'odd');
1406                                         html.push( '<tr class="inspector_peer_entry ', parity, '">',
1407                                                    '<td>', (peer.isEncrypted ? '<img src="images/graphics/lock_icon.png" alt="Encrypted"/>' : ''), '</td>',
1408                                                    '<td>', ( peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : '' ), '</td>',
1409                                                    '<td>', ( peer.rateToClient ? fmt.speedBps(peer.rateToClient) : '' ), '</td>',
1410                                                    '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
1411                                                    '<td>', peer.flagStr, '</td>',
1412                                                    '<td>', peer.address, '</td>',
1413                                                    '<td class="clientCol">', peer.clientName, '</td>',
1414                                                    '</tr>' );
1415                                 }
1416                                 html.push( '</table></div>' );
1417                         }
1418                 }
1419                 setInnerHTML(this._inspector_peers_list, html.join('') );
1420         },
1422         updateTrackersLists: function() {
1423                 // By building up the HTML as as string, then have the browser
1424                 // turn this into a DOM tree, this is a fast operation.
1425                 var tr = this;
1426                 var html = [ ];
1427                 var na = 'N/A';
1428                 var torrents = this.getSelectedTorrents( );
1429                 if( $(this._inspector_trackers_list).is(':visible') ) {
1430                         for( var k=0, torrent; torrent = torrents[k]; ++k ) {
1431                                 html.push( '<div class="inspector_group">' );
1432                                 if( torrents.length > 1 ) {
1433                                         html.push( '<div class="inspector_torrent_label">', torrent._name, '</div>' );
1434                                 }
1435                                 for( var i=0, tier; tier=torrent._trackerStats[i]; ++i ) {
1436                                         html.push( '<div class="inspector_group_label">',
1437                                                    'Tier ', (i + 1), '</div>',
1438                                                    '<ul class="tier_list">' );
1439                                         for( var j=0, tracker; tracker=tier[j]; ++j ) {
1440                                                 var lastAnnounceStatusHash = tr.lastAnnounceStatus(tracker);
1441                                                 var announceState = tr.announceState(tracker);
1442                                                 var lastScrapeStatusHash = tr.lastScrapeStatus(tracker);
1444                                                 // Display construction
1445                                                 var parity = ((j+1) % 2 == 0 ? 'even' : 'odd');
1446                                                 html.push( '<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
1447                                                            tracker.host, '</div>',
1448                                                            '<div class="tracker_activity">',
1449                                                            '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
1450                                                            '<div>', announceState, '</div>',
1451                                                            '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
1452                                                            '</div><table class="tracker_stats">',
1453                                                            '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
1454                                                            '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
1455                                                            '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
1456                                                            '</table></li>' );
1457                                         }
1458                                         html.push( '</ul>' );
1459                                 }
1460                                 html.push( '</div>' );
1461                         }
1462                 }
1463                 setInnerHTML(this._inspector_trackers_list, html.join(''));
1464         },
1466         lastAnnounceStatus: function(tracker){
1467                 var lastAnnounceLabel = 'Last Announce';
1468                 var lastAnnounce = [ 'N/A' ];
1469                 if (tracker.hasAnnounced) {
1470                         var lastAnnounceTime = Transmission.fmt.timestamp(tracker.lastAnnounceTime);
1471                         if (tracker.lastAnnounceSucceeded) {
1472                                 lastAnnounce = [ lastAnnounceTime, ' (got ',  Transmission.fmt.plural(tracker.lastAnnouncePeerCount, 'peer'), ')' ];
1473                         } else {
1474                                 lastAnnounceLabel = 'Announce error';
1475                                 lastAnnounce = [ (tracker.lastAnnounceResult ? (tracker.lastAnnounceResult + ' - ') : ''), lastAnnounceTime ];
1476                         }
1477                 }
1478                 return { 'label':lastAnnounceLabel, 'value':lastAnnounce.join('') };
1479         },
1481         announceState: function(tracker){
1482                 var announceState = '';
1483                 switch (tracker.announceState) {
1484                         case Torrent._TrackerActive:
1485                                 announceState = 'Announce in progress';
1486                                 break;
1487                         case Torrent._TrackerWaiting:
1488                                 var timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
1489                                 if(timeUntilAnnounce < 0){
1490                                         timeUntilAnnounce = 0;
1491                                 }
1492                                 announceState = 'Next announce in ' + Transmission.fmt.timeInterval(timeUntilAnnounce);
1493                                 break;
1494                         case Torrent._TrackerQueued:
1495                                 announceState = 'Announce is queued';
1496                                 break;
1497                         case Torrent._TrackerInactive:
1498                                 announceState = tracker.isBackup ?
1499                                         'Tracker will be used as a backup' :
1500                                         'Announce not scheduled';
1501                                 break;
1502                         default:
1503                                 announceState = 'unknown announce state: ' + tracker.announceState;
1504                 }
1505                 return announceState;
1506         },
1508         lastScrapeStatus: function(tracker){
1509                 var lastScrapeLabel = 'Last Scrape';
1510                 var lastScrape = 'N/A';
1511                 if (tracker.hasScraped) {
1512                         var lastScrapeTime = Transmission.fmt.timestamp(tracker.lastScrapeTime);
1513                         if (tracker.lastScrapeSucceeded) {
1514                                 lastScrape = lastScrapeTime;
1515                         } else {
1516                                 lastScrapeLabel = 'Scrape error';
1517                                 lastScrape = (tracker.lastScrapeResult ? tracker.lastScrapeResult + ' - ' : '') + lastScrapeTime;
1518                         }
1519                 }
1520                 return {'label':lastScrapeLabel, 'value':lastScrape}
1521         },
1523         /*
1524          * Toggle the visibility of the inspector (used by the context menu)
1525          */
1526         toggleInspector: function() {
1527                 if( this[Prefs._ShowInspector] )
1528                         this.hideInspector( );
1529                 else
1530                         this.showInspector( );
1531         },
1533         showInspector: function() {
1534                 $('#torrent_inspector').show();
1535                 if (iPhone) {
1536                         $('body').addClass('inspector_showing');
1537                         $('#inspector_close').show();
1538                         this.hideiPhoneAddressbar();
1539                 } else {
1540                         var w = $('#torrent_inspector').width() + 1 + 'px';
1541                         $('#torrent_filter_bar')[0].style.right = w;
1542                         $('#torrent_container')[0].style.right = w;
1543                 }
1545                 setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Hide Inspector' );
1547                 this.setPref( Prefs._ShowInspector, true );
1548                 this.updateInspector( );
1549                 this.refreshDisplay( );
1550         },
1552         /*
1553          * Hide the inspector
1554          */
1555         hideInspector: function() {
1557                 $('#torrent_inspector').hide();
1559                 if (iPhone) {
1560                         this.deselectAll( );
1561                         $('body.inspector_showing').removeClass('inspector_showing');
1562                         $('#inspector_close').hide();
1563                         this.hideiPhoneAddressbar();
1564                 } else {
1565                         $('#torrent_filter_bar')[0].style.right = '0px';
1566                         $('#torrent_container')[0].style.right = '0px';
1567                         setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Show Inspector' );
1568                 }
1570                 this.setPref( Prefs._ShowInspector, false );
1571                 this.refreshDisplay( );
1572         },
1574         /*
1575          * Toggle the visibility of the filter bar
1576          */
1577         toggleFilter: function() {
1578                 if( this[Prefs._ShowFilter] )
1579                         this.hideFilter();
1580                 else
1581                         this.showFilter();
1582         },
1584         showFilter: function( ) {
1585                 var container_top = parseInt($('#torrent_container').position().top) + $('#torrent_filter_bar').height() + 1;
1586                 $('#torrent_container').css('top', container_top + 'px');
1587                 $('#torrent_filter_bar').show();
1588                 this.setPref( Prefs._ShowFilter, true );
1589         },
1591         hideFilter: function()
1592         {
1593                 var container_top = parseInt($('#torrent_container').css('top')) - $('#torrent_filter_bar').height() - 1;
1594                 $('#torrent_container').css('top', container_top + 'px');
1595                 $('#torrent_filter_bar').hide();
1596                 this.setPref( Prefs._ShowFilter, false );
1597                 this.setFilter( Prefs._FilterAll );
1598         },
1600         refreshMetaData: function(ids) {
1601                 var tr = this;
1602                 this.remote.getMetaDataFor(ids, function(active, removed){ tr.updateMetaData(active); });
1603         },
1605         updateMetaData: function( torrents )
1606         {
1607                 var tr = this;
1608                 var refresh_files_for = [ ];
1609                 jQuery.each( torrents, function( ) {
1610                         var t = tr._torrents[ this.id ];
1611                         if( t ) {
1612                                 t.refreshMetaData( this );
1613                                 if( t.isSelected( ) )
1614                                         refresh_files_for.push( t.id( ) );
1615                         }
1616                 } );
1617                 if( refresh_files_for.length > 0 )
1618                         tr.remote.loadTorrentFiles( refresh_files_for );
1619         },
1621         refreshTorrents: function(ids) {
1622                 var tr = this;
1623                 if (!ids)
1624                         ids = 'recently-active';
1626                 this.remote.getUpdatedDataFor(ids, function(active, removed){ tr.updateTorrentsData(active, removed); });
1627         },
1629         updateTorrentsData: function( updated, removed_ids ) {
1630                 var tr = this;
1631                 var new_torrent_ids = [];
1632                 var refresh_files_for = [];
1633                 jQuery.each( updated, function() {
1634                         var t = tr._torrents[this.id];
1635                         if (t){
1636                                 t.refresh(this);
1637                                 if(t.isSelected())
1638                                         refresh_files_for.push(t.id());
1639                         }
1640                         else
1641                                 new_torrent_ids.push(this.id);
1642                 } );
1644                 if(refresh_files_for.length > 0)
1645                         tr.remote.loadTorrentFiles( refresh_files_for );
1647                 if(new_torrent_ids.length > 0)
1648                         tr.remote.getInitialDataFor(new_torrent_ids, function(torrents){ tr.addTorrents(torrents) } );
1650                 var removedAny = tr.deleteTorrents(removed_ids);
1652                 if( ( new_torrent_ids.length != 0 ) || removedAny ) {
1653                         tr.hideiPhoneAddressbar();
1654                         tr.deselectAll( true );
1655                 }
1657                 this.refilter();
1658         },
1660         updateTorrentsFileData: function( torrents ){
1661                 var tr = this;
1662                 var listIsVisible = tr.fileListIsVisible( );
1663                 jQuery.each( torrents, function() {
1664                         var t = tr._torrents[this.id];
1665                         if (t) {
1666                                 t.refreshFileModel(this);
1667                                 if( listIsVisible && t.isSelected())
1668                                         t.refreshFileView();
1669                         }
1670                 } );
1671         },
1673         initializeAllTorrents: function(){
1674                 var tr = this;
1675                 this.remote.getInitialDataFor( null ,function(torrents) { tr.addTorrents(torrents); } );
1676         },
1678         addTorrents: function( new_torrents )
1679         {
1680                 var transferFragment = document.createDocumentFragment( );
1681                 var fileFragment = document.createDocumentFragment( );
1683                 for( var i=0, row; row=new_torrents[i]; ++i ) {
1684                         var new_torrent = new Torrent( transferFragment, fileFragment, this, row );
1685                         this._torrents[new_torrent.id()] = new_torrent;
1686                 }
1688                 this._inspector_file_list.appendChild( fileFragment );
1689                 this._torrent_list.appendChild( transferFragment );
1691                 this.refilter( );
1692         },
1694         deleteTorrents: function(torrent_ids){
1695                 if(typeof torrent_ids == 'undefined')
1696                         return false;
1697                 var tr = this;
1698                 var removedAny = false;
1699                 $.each( torrent_ids, function(index, id){
1700                         var torrent = tr._torrents[id];
1702                         if(torrent) {
1703                                 removedAny = true;
1704                                 var e = torrent.element();
1705                                 if( e ) {
1706                                         var row_index;
1707                                         for( var i=0, row; row = tr._rows[i]; ++i ) {
1708                                                 if( row._id == torrent._id )
1709                                                 {
1710                                                         row_index = i;
1711                                                         e = tr._rows[row_index];
1712                                                         break;
1713                                                 }
1714                                         }
1715                                         delete e._torrent; //remove circular refernce to help IE garbage collect
1716                                         tr._rows.splice(row_index, 1)
1717                                         e.remove();
1718                                 }
1720                                 torrent.hideFileList();
1721                                 torrent.deleteFiles();
1722                                 delete tr._torrents[torrent.id()];
1723                         }
1724                 });
1726                 return removedAny;
1727         },
1729         refreshDisplay: function( )
1730         {
1731                 var torrents = this.getVisibleTorrents();
1732                 for( var i=0; torrents[i]; ++i )
1733                         torrents[i].refreshHTML();
1734         },
1736         /*
1737          * Set the alternating background colors for torrents
1738          */
1739         setTorrentBgColors: function( )
1740         {
1741                 var rows = this.getVisibleRows( );
1742                 for( var i=0, row; row=rows[i]; ++i ) {
1743                         var wasEven = row[0].className.indexOf('even') != -1;
1744                         var isEven = ((i+1) % 2 == 0);
1745                         if( wasEven != isEven )
1746                                 row.toggleClass('even', isEven);
1747                 }
1748         },
1750         updateStatusbar: function()
1751         {
1752                 var torrents = this.getAllTorrents();
1753                 var torrentCount = torrents.length;
1754                 var visibleCount = this.getVisibleTorrents().length;
1756                 // calculate the overall speed
1757                 var upSpeed = 0;
1758                 var downSpeed = 0;
1759                 for( var i=0, row; row=torrents[i]; ++i ) {
1760                         upSpeed += row.uploadSpeed( );
1761                         downSpeed += row.downloadSpeed( );
1762                 }
1764                 // update torrent count label
1765                 var s;
1766                 if( torrentCount == visibleCount )
1767                         s = torrentCount + ' Transfers';
1768                 else
1769                         s = visibleCount + ' of ' + torrentCount + ' Transfers';
1770                 setInnerHTML( $('#torrent_global_transfer')[0], s );
1772                 // update the speeds
1773                 s = Transmission.fmt.speedBps( upSpeed );
1774                 if( iPhone ) s = 'UL: ' + s;
1775                 setInnerHTML( $('#torrent_global_upload')[0], s );
1777                 // download speeds
1778                 s = Transmission.fmt.speedBps( downSpeed );
1779                 if( iPhone ) s = 'DL: ' + s;
1780                 setInnerHTML( $('#torrent_global_download')[0], s );
1781         },
1783         /*
1784          * Select a torrent file to upload
1785          * FIXME
1786          */
1787         uploadTorrentFile: function(confirmed)
1788         {
1789                 // Display the upload dialog
1790                 if (! confirmed) {
1791                                 $('input#torrent_upload_file').attr('value', '');
1792                                 $('input#torrent_upload_url').attr('value', '');
1793                                 $('input#torrent_auto_start').attr('checked', $('#prefs_form #auto_start')[0].checked);
1794                                 $('#upload_container').show();
1795                 $('#torrent_upload_url').focus();
1796                         if (!iPhone && Safari3) {
1797                                 setTimeout("$('div#upload_container div.dialog_window').css('top', '0px');",10);
1798                         }
1800                 // Submit the upload form
1801                 } else {
1802                         var tr = this;
1803                         var args = { };
1804                         var paused = !$('#torrent_auto_start').is(':checked');
1805                         if ('' != $('#torrent_upload_url').val()) {
1806                                 tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
1807                         } else {
1808                                 args.url = '/transmission/upload?paused=' + paused;
1809                                 args.type = 'POST';
1810                                 args.data = { 'X-Transmission-Session-Id' : tr.remote._token };
1811                                 args.dataType = 'xml';
1812                                 args.iframe = true;
1813                                 args.success = function( data ) {
1814                                         tr.refreshTorrents();
1815                                         tr.togglePeriodicRefresh( true );
1816                                 };
1817                                 tr.togglePeriodicRefresh( false );
1818                                 $('#torrent_upload_form').ajaxSubmit( args );
1819                         }
1820                 }
1821         },
1823         removeSelectedTorrents: function() {
1824                 var torrents = this.getSelectedTorrents( );
1825                 if( torrents.length )
1826                         this.promptToRemoveTorrents( torrents );
1827         },
1829         removeSelectedTorrentsAndData: function() {
1830                 var torrents = this.getSelectedTorrents( );
1831                 if( torrents.length )
1832                         this.promptToRemoveTorrentsAndData( torrents );
1833         },
1835         promptToRemoveTorrents:function( torrents )
1836         {
1837                 if( torrents.length == 1 )
1838                 {
1839                         var torrent = torrents[0];
1840                         var header = 'Remove ' + torrent.name() + '?';
1841                         var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
1842                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
1843                 }
1844                 else
1845                 {
1846                         var header = 'Remove ' + torrents.length + ' transfers?';
1847                         var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
1848                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
1849                 }
1850         },
1852         promptToRemoveTorrentsAndData:function( torrents )
1853         {
1854                 if( torrents.length == 1 )
1855                 {
1856                         var torrent = torrents[0],
1857                                 header = 'Remove ' + torrent.name() + ' and delete data?',
1858                                 message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
1859                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
1860                 }
1861                 else
1862                 {
1863                         var header = 'Remove ' + torrents.length + ' transfers and delete data?',
1864                                 message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
1865                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
1866                 }
1867         },
1869         removeTorrents: function( torrents ) {
1870                 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1871                 var tr = this;
1872                 this.remote.removeTorrents( torrent_ids, function(){ tr.refreshTorrents() } );
1873         },
1875         removeTorrentsAndData: function( torrents ) {
1876                 this.remote.removeTorrentsAndData( torrents );
1877         },
1879         verifySelectedTorrents: function() {
1880                 this.verifyTorrents( this.getSelectedTorrents( ) );
1881         },
1883         startSelectedTorrents: function( ) {
1884                 this.startTorrents( this.getSelectedTorrents( ) );
1885         },
1886         startAllTorrents: function( ) {
1887                 this.startTorrents( this.getAllTorrents( ) );
1888         },
1889         startTorrent: function( torrent ) {
1890                 this.startTorrents( [ torrent ] );
1891         },
1892         startTorrents: function( torrents ) {
1893                 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1894                 var tr = this;
1895                 this.remote.startTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
1896         },
1897         verifyTorrent: function( torrent ) {
1898                 this.verifyTorrents( [ torrent ] );
1899         },
1900         verifyTorrents: function( torrents ) {
1901                 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1902                 var tr = this;
1903                 this.remote.verifyTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
1904         },
1906         stopSelectedTorrents: function( ) {
1907                 this.stopTorrents( this.getSelectedTorrents( ) );
1908         },
1909         stopAllTorrents: function( ) {
1910                 this.stopTorrents( this.getAllTorrents( ) );
1911         },
1912         stopTorrent: function( torrent ) {
1913                 this.stopTorrents( [ torrent ] );
1914         },
1915         stopTorrents: function( torrents ) {
1916                 var torrent_ids = jQuery.map(torrents, function(t) { return t.id(); } );
1917                 var tr = this;
1918                 this.remote.stopTorrents( torrent_ids,  function(){ tr.refreshTorrents(torrent_ids )} );
1919         },
1920         changeFileCommand: function(command, torrent, file) {
1921                 this.remote.changeFileCommand(command, torrent, file)
1922         },
1924         hideiPhoneAddressbar: function(timeInSeconds) {
1925                 var tr = this;
1926                 if( iPhone ) {
1927                         var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
1928                         // not currently supported on iPhone
1929                         if(/*document.body.scrollTop!=1 && */scroll_timeout==null) {
1930                                 scroll_timeout = setTimeout(function(){ tr.doToolbarHide(); }, delayLength);
1931                         }
1932                 }
1933         },
1934         doToolbarHide: function() {
1935                 window.scrollTo(0,1);
1936                 scroll_timeout=null;
1937         },
1939         /***
1940         ****
1941         ***/
1943         refilter: function()
1944         {
1945                 // decide which torrents to keep showing
1946                 var allTorrents = this.getAllTorrents( );
1947                 var keep = [ ];
1948                 for( var i=0, t; t=allTorrents[i]; ++i )
1949                         if( t.test( this[Prefs._FilterMode], this._current_search ) )
1950                                 keep.push( t );
1952                 // sort the keepers
1953                 Torrent.sortTorrents( keep, this[Prefs._SortMethod],
1954                                             this[Prefs._SortDirection] );
1956                 // make a backup of the selection
1957                 var sel = this.getSelectedTorrents( );
1958                 this.deselectAll( );
1960                 // hide the ones we're not keeping
1961                 for( var i=keep.length, e; e=this._rows[i]; ++i ) {
1962                         delete e._torrent;
1963                         e[0].style.display = 'none';
1964                 }
1966                 // show the ones we're keeping
1967                 sel.sort( Torrent.compareById );
1968                 for( var i=0, len=keep.length; i<len; ++i ) {
1969                         var e = this._rows[i];
1970                         e[0].style.display = 'block';
1971                         var t = keep[i];
1972                         t.setElement( e );
1973                         if( Torrent.indexOf( sel, t.id() ) != -1 )
1974                                 this.selectElement( e );
1975                 }
1977                 // sync gui
1978                 this.setTorrentBgColors( );
1979                 this.updateStatusbar( );
1980                 this.selectionChanged( );
1981                 this.refreshDisplay( );
1982         },
1984         setEnabled: function( key, flag )
1985         {
1986                 $(key).toggleClass( 'disabled', !flag );
1987         },
1989         updateButtonStates: function()
1990         {
1991                 var showing_dialog = new RegExp("(prefs_showing|dialog_showing|open_showing)").test(document.body.className);
1992                 this._toolbar_buttons.toggleClass( 'disabled', showing_dialog );
1994                 if (!showing_dialog)
1995                 {
1996                         var torrents = this.getVisibleTorrents( );
1997                         var haveSelection = false;
1998                         var haveActive = false;
1999                         var haveActiveSelection = false;
2000                         var havePaused = false;
2001                         var havePausedSelection = false;
2003                         for( var i=0, len=torrents.length; !haveSelection && i<len; ++i ) {
2004                                 var isActive = torrents[i].isActive( );
2005                                 var isSelected = torrents[i].isSelected( );
2006                                 if( isActive ) haveActive = true;
2007                                 if( !isActive ) havePaused = true;
2008                                 if( isSelected ) haveSelection = true;
2009                                 if( isSelected && isActive ) haveActiveSelection = true;
2010                                 if( isSelected && !isActive ) havePausedSelection = true;
2011                         }
2013                         this.setEnabled( this._toolbar_pause_button, haveActiveSelection );
2014                         this.setEnabled( this._context_pause_button, haveActiveSelection );
2015                         this.setEnabled( this._toolbar_start_button, havePausedSelection );
2016                         this.setEnabled( this._context_start_button, havePausedSelection );
2017                         this.setEnabled( this._toolbar_remove_button, haveSelection );
2018                         this.setEnabled( this._toolbar_pause_all_button, haveActive );
2019                         this.setEnabled( this._toolbar_start_all_button, havePaused );
2020                 }
2021         }