diff --git a/web/index.html b/web/index.html index 3bf0ca381..e6cb77df6 100755 --- a/web/index.html +++ b/web/index.html @@ -26,6 +26,8 @@ + + Transmission Web Interface @@ -194,12 +196,12 @@ diff --git a/web/javascript/common.js b/web/javascript/common.js index d8be8497c..807473677 100644 --- a/web/javascript/common.js +++ b/web/javascript/common.js @@ -13,7 +13,7 @@ var resizeTimer = null; // actually 523.10.3). We need 3.1 for CSS animation (dialog sheets) but as it // degrades gracefully let's not worry too much. var Safari3 = testSafari3(); -var iPhone = RegExp("(iPhone|iPod)").test(navigator.userAgent); +var iPhone = RegExp("(iPhone|iPod|Android)").test(navigator.userAgent); if (iPhone) var scroll_timeout; if(!Array.indexOf){ diff --git a/web/javascript/file-row.js b/web/javascript/file-row.js new file mode 100644 index 000000000..11505c7c7 --- /dev/null +++ b/web/javascript/file-row.js @@ -0,0 +1,175 @@ +/* + * Copyright © Jordan Lee + * This code is licensed under the GPL version 2. + * + */ + +function FileRow( controller, torrent, i ) +{ + this.initialize( controller, torrent, i ); +} + +FileRow.prototype = +{ + initialize: function( controller, torrent, i ) + { + this._torrent = torrent; + this._index = i; + this.createRow( torrent, i ); + }, + + getTorrent: function( ) + { + return this._torrent; + }, + getIndex: function( ) + { + return this._index; + }, + + readAttributes: function(file) + { + if( file.index !== undefined && file.index !== this._index ) { + this._index = file.index; + this._dirty = true; + } + if( file.bytesCompleted !== undefined && file.bytesCompleted !== this._done ) { + this._done = file.bytesCompleted; + this._dirty = true; + } + if( file.length !== undefined && file.length !== this._size ) { + this._size = file.length; + this._dirty = true; + } + if( file.priority !== undefined && file.priority !== this._prio ) { + this._prio = file.priority; + this._dirty = true; + } + if( file.wanted !== undefined && file.wanted !== this._wanted ) { + this._wanted = file.wanted; + this._dirty = true; + } + }, + + refreshWantedHTML: function() { + var e = this.getElement(); + var c = [ e.classNameConst ]; + if(!this._wanted) { c.push( 'skip' ); } + if(this.isDone()) { c.push( 'complete' ); } + e.className = c.join(' '); + }, + refreshPriorityHTML: function() { + var e = this._priority_control; + var c = [ e.classNameConst ]; + switch( this._prio ) { + case 1 : c.push( 'high' ); break; + case -1 : c.push( 'low' ); break; + default : c.push( 'normal' ); break; + } + e.className = c.join(' '); + }, + refreshProgressHTML: function() { + var pct = 100 * (this._size ? ( this._done / this._size ) : 1.0); + var c = [ Transmission.fmt.size(this._done), + ' of ', + Transmission.fmt.size(this._size), + ' (', + Transmission.fmt.percentString(pct), + '%)' ].join(''); + setInnerHTML(this._progress[0], c); + }, + refreshHTML: function() { + if( this._dirty ) { + this._dirty = false; + this.refreshProgressHTML(); + this.refreshWantedHTML(); + this.refreshPriorityHTML(); + } + }, + refresh: function( ) + { + var i = this.getIndex( ); + var t = this.getTorrent( ); + this.readAttributes( t._file_model[i] ); + this.refreshHTML(); + }, + + isDone: function () { + return this._done >= this._size; + }, + isEditable: function () { + return (this.getTorrent()._file_model.length>1) && !this.isDone(); + }, + + createRow: function( torrent, i ) + { + var me = this; + var file = torrent._file_model[i]; + var name = file.name.substring (file.name.lastIndexOf('/')+1); + + var root = document.createElement('li'); + root.id = 't' + this._torrent.id() + 'f' + this._index; + root.classNameConst = 'inspector_torrent_file_list_entry ' + ((i%2)?'odd':'even'); + root.className = root.classNameConst; + + var wanted_div = document.createElement('div'); + wanted_div.className = "file_wanted_control"; + $(wanted_div).bind('click',function(e){ me.fireWantedChanged( !me._wanted ); }); + + var pri_div = document.createElement('div'); + pri_div.classNameConst = "file_priority_control"; + pri_div.className = pri_div.classNameConst; + $(pri_div).bind('click',function(ev){ + var x = ev.pageX; + var e = ev.target; + while (e !== null) { + x -= e.offsetLeft; + e = e.offsetParent; + } + var prio; + if(iPhone) { + if( x < 8 ) prio = -1; + else if( x < 27 ) prio = 0; + else prio = 1; + } else { + if( x < 12 ) prio = -1; + else if( x < 23 ) prio = 0; + else prio = 1; + } + me.firePriorityChanged( prio ); + }); + + var file_div = document.createElement('div'); + file_div.className = "inspector_torrent_file_list_entry_name"; + file_div.innerHTML = name.replace(/([\/_\.])/g, "$1​"); + + var prog_div = document.createElement('div'); + prog_div.className = "inspector_torrent_file_list_entry_progress"; + + root.appendChild(wanted_div); + root.appendChild(pri_div); + root.appendChild(file_div); + root.appendChild(prog_div); + + this._element = root; + this._priority_control = pri_div; + this._progress = $(prog_div); + + this.refresh(); + return root; + }, + + getElement: function( ) + { + return this._element; + }, + + fireWantedChanged: function( do_want ) + { + $(this).trigger('wantedToggled',[ this, do_want ]); + }, + firePriorityChanged: function( priority ) + { + $(this).trigger('priorityToggled',[ this, priority ]); + } +}; diff --git a/web/javascript/torrent-renderer.js b/web/javascript/torrent-renderer.js new file mode 100644 index 000000000..ffe2ae5cf --- /dev/null +++ b/web/javascript/torrent-renderer.js @@ -0,0 +1,370 @@ +/* + * Copyright © Jordan Lee + * This code is licensed under the GPL version 2. + * + */ + +/**** +***** +***** +****/ + +function TorrentRendererHelper() +{ +} + +TorrentRendererHelper.getProgressInfo = function( controller, t ) +{ + var seed_ratio_limit = t.seedRatioLimit( controller ); + + var pct = 0; + if( t.needsMetaData( ) ) + pct = t._metadataPercentComplete * 100; + else if( !t.isDone( ) ) + pct = Math.round( t.getPercentDone() * 100 ); + else if( seed_ratio_limit > 0 ) + pct = Math.round( t._upload_ratio * 100 / seed_ratio_limit ); + else + pct = 100; + + var extra; + if( t.isStopped( ) ) + extra = 'paused'; + else if( t.isSeeding( ) ) + extra = 'seeding'; + else if( t.needsMetaData( ) ) + extra = 'magnet'; + else + extra = 'leeching'; + + return { + percent: pct, + complete: [ 'torrent_progress_bar', 'complete', extra ].join(' '), + incomplete: [ 'torrent_progress_bar', 'incomplete', extra ].join(' ') + }; +} + +TorrentRendererHelper.renderProgressbar = function( controller, t, complete, incomplete ) +{ + var info = TorrentRendererHelper.getProgressInfo( controller, t ); + var e; + e = complete; + e.style.width = '' + info.percent + "%"; + e.className = info.complete; + e.style.display = info.percent<=0 ? 'none' : 'block'; + e = incomplete; + e.className = info.incomplete; + e.style.display = info.percent>=100 ? 'none' : 'block'; +} + +TorrentRendererHelper.formatUL = function( t ) +{ + return 'UL: ' + Transmission.fmt.speedBps( t.uploadSpeed( ) ); +} + +TorrentRendererHelper.formatDL = function( t ) +{ + return 'DL: ' + Transmission.fmt.speedBps( t.downloadSpeed( ) ); +} + +/**** +***** +***** +****/ + +function TorrentRendererFull() +{ +} +TorrentRendererFull.prototype = +{ + createRow: function( ) + { + var root = document.createElement( 'li' ); + root.className = 'torrent'; + + var name = document.createElement( 'div' ); + name.className = 'torrent_name'; + + var peers = document.createElement( 'div' ); + peers.className = 'torrent_peer_details'; + + var complete = document.createElement( 'div' ); + complete.className = 'torrent_progress_bar complete'; + var incomplete = document.createElement( 'div' ); + incomplete.className = 'torrent_progress_bar incomplete'; + var progressbar = document.createElement( 'div' ); + progressbar.className = 'torrent_progress_bar_container full'; + progressbar.appendChild( complete ); + progressbar.appendChild( incomplete ); + + var details = document.createElement( 'div' ); + details.className = 'torrent_progress_details'; + + var image = document.createElement( 'div' ); + var button = document.createElement( 'a' ); + button.appendChild( image ); + + root.appendChild( name ); + root.appendChild( peers ); + root.appendChild( button ); + root.appendChild( progressbar ); + root.appendChild( details ); + + root._name_container = name; + root._peer_details_container = peers; + root._progress_details_container = details; + root._progress_complete_container = complete; + root._progress_incomplete_container = incomplete; + root._pause_resume_button_image = image; + root._toggle_running_button = button; + + return root; + }, + + getPeerDetails: function( t ) + { + var err; + if(( err = t.getErrorMessage())) + return err; + + if( t.isDownloading( ) ) + return [ 'Downloading from', + t.peersSendingToUs(), + 'of', + t._peers_connected, + 'peers', + '-', + TorrentRendererHelper.formatDL(t), + TorrentRendererHelper.formatUL(t) ].join(' '); + + if( t.isSeeding( ) ) + return [ 'Seeding to', + t.peersGettingFromUs(), + 'of', + t._peers_connected, + 'peers', + '-', + TorrentRendererHelper.formatUL(t) ].join(' '); + + if( t.state() === Torrent._StatusCheck ) + return [ 'Verifying local data (', + Transmission.fmt.percentString( 100.0 * t._recheckProgress ), + '% tested)' ].join(''); + + return t.stateStr( ); + }, + + getProgressDetails: function( controller, t ) + { + if( t.needsMetaData() ) { + var percent = 100 * t._metadataPercentComplete; + return [ "Magnetized transfer - retrieving metadata (", + Transmission.fmt.percentString( percent ), + "%)" ].join(''); + } + + var c; + var is_done = ( t.isDone( ) ) + || ( t.state() === Torrent._StatusSeeding ); + if( is_done ) { + if( t._size == t._sizeWhenDone ) // seed: '698.05 MiB' + c = [ Transmission.fmt.size( t._size ) ]; + else // partial seed: '127.21 MiB of 698.05 MiB (18.2%)' + c = [ Transmission.fmt.size( t._sizeWhenDone ), + ' of ', + Transmission.fmt.size( t._size ), + ' (', t.getPercentDoneStr, '%)' ]; + // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)' + c.push( ', uploaded ', + Transmission.fmt.size( t._upload_total ), + ' (Ratio ', + Transmission.fmt.ratioString( t._upload_ratio ), + ')' ); + } else { // not done yet + c = [ Transmission.fmt.size( t._sizeWhenDone - t._leftUntilDone ), + ' of ', Transmission.fmt.size( t._sizeWhenDone ), + ' (', t.getPercentDoneStr(), '%)' ]; + } + + // maybe append eta + if( t.isActive() && ( !is_done || t.seedRatioLimit(controller)>0 ) ) { + c.push(' - '); + if (t._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining ) + c.push( 'remaining time unknown' ); + else + c.push( Transmission.fmt.timeInterval(t._eta), + ' remaining' ); + } + + return c.join(''); + }, + + render: function( controller, t, root ) + { + // name + setInnerHTML( root._name_container, t.name() ); + + // progressbar + TorrentRendererHelper.renderProgressbar( + controller, t, + root._progress_complete_container, + root._progress_incomplete_container ); + + // peer details + var has_error = t._error !== Torrent._ErrNone; + var e = root._peer_details_container; + $(e).toggleClass('error',has_error); + setInnerHTML( e, this.getPeerDetails( t ) ); + + // progress details + e = root._progress_details_container; + setInnerHTML( e, this.getProgressDetails( controller, t ) ); + + // pause/resume button + var is_stopped = t.state() === Torrent._StatusStopped; + e = root._pause_resume_button_image; + e.alt = is_stopped ? 'Resume' : 'Pause'; + e.className = is_stopped ? 'torrent_resume' : 'torrent_pause'; + } +}; + +/**** +***** +***** +****/ + +function TorrentRendererCompact() +{ +} +TorrentRendererCompact.prototype = +{ + createRow: function( ) + { + var complete = document.createElement( 'div' ); + complete.className = 'torrent_progress_bar complete'; + var incomplete = document.createElement( 'div' ); + incomplete.className = 'torrent_progress_bar incomplete'; + var progressbar = document.createElement( 'div' ); + progressbar.className = 'torrent_progress_bar_container compact'; + progressbar.appendChild( complete ); + progressbar.appendChild( incomplete ); + + var details = document.createElement( 'div' ); + details.className = 'torrent_peer_details compact'; + + var name = document.createElement( 'div' ); + name.className = 'torrent_name'; + + var root = document.createElement( 'li' ); + root.appendChild( progressbar ); + root.appendChild( details ); + root.appendChild( name ); + root.className = 'torrent compact'; + root._progress_complete_container = complete; + root._progress_incomplete_container = incomplete; + root._details_container = details; + root._name_container = name; + return root; + }, + + getPeerDetails: function( t ) + { + var c; + if(( c = t.getErrorMessage())) + return c; + if( t.isDownloading( ) ) + return [ TorrentRendererHelper.formatDL(t), + TorrentRendererHelper.formatUL(t) ].join(' '); + if( t.isSeeding( ) ) + return TorrentRendererHelper.formatUL(t); + return t.stateStr( ); + }, + + render: function( controller, t, root ) + { + // name + var is_stopped = t.state() === Torrent._StatusStopped; + var e = root._name_container; + $(e).toggleClass( 'paused', is_stopped ); + setInnerHTML( e, t.name() ); + + // peer details + var has_error = t._error !== Torrent._ErrNone; + e = root._details_container; + $(e).toggleClass('error', has_error ); + setInnerHTML( e, this.getPeerDetails( t ) ); + + // progressbar + TorrentRendererHelper.renderProgressbar( + controller, t, + root._progress_complete_container, + root._progress_incomplete_container ); + } +}; + +/**** +***** +***** +****/ + +function TorrentRow( controller, generator ) +{ + this.initialize( controller, generator ); +} +TorrentRow.prototype = +{ + initialize: function( controller, generator ) { + this._generator = generator; + var root = generator.createRow( ); + this._element = root; + $(root).bind('dblclick', function(e) { controller.toggleInspector(); }); + + }, + getElement: function( ) { + return this._element; + }, + render: function( controller ) { + var tor = this.getTorrent( ); + if( tor !== null ) + this._generator.render( controller, tor, this.getElement( ) ); + }, + isSelected: function( ) { + return this.getElement().className.indexOf('selected') != -1; + }, + setSelected: function( flag ) { + $(this.getElement()).toggleClass( 'selected', flag ); + }, + + getToggleRunningButton: function( ) { + return this.getElement()._toggle_running_button; + }, + + setVisible: function( visible ) { + this.getElement().style.display = visible ? 'block' : 'none'; + + if( !visible ) + this.setSelected( false ); + }, + isVisible: function( visible ) { + return this.getElement().style.display === 'block'; + }, + + setTorrent: function( controller, t ) { + if( this._torrent !== t ) { + if( this._torrent ) + $(this).unbind('dataChanged.renderer'); + if(( this._torrent = t )) + $(this).bind('dataChanged.renderer',this.render(controller)); + } + }, + getTorrent: function() { + return this._torrent; + }, + isEven: function() { + return this.getElement().className.indexOf('even') != -1; + }, + setEven: function( even ) { + if( this.isEven() != even ) + $(this.getElement()).toggleClass('even', even); + } +}; diff --git a/web/javascript/torrent.js b/web/javascript/torrent.js index 6f8a13d8c..77c22932b 100644 --- a/web/javascript/torrent.js +++ b/web/javascript/torrent.js @@ -6,8 +6,8 @@ * Class Torrent */ -function Torrent( transferListParent, fileListParent, controller, data) { - this.initialize( transferListParent, fileListParent, controller, data); +function Torrent( controller, data) { + this.initialize( controller, data); } // Constants @@ -19,13 +19,6 @@ Torrent._StatusDownload = 4; /* downloading */ Torrent._StatusSeedWait = 5; /* queeud to seed */ Torrent._StatusSeed = 6; /* seeding */ -/* -Torrent._StatusWaitingToCheck = 1; -Torrent._StatusChecking = 2; -Torrent._StatusDownloading = 4; -Torrent._StatusSeeding = 8; -*/ - Torrent._InfiniteTimeRemaining = 215784000; // 999 Hours - may as well be infinite Torrent._RatioUseGlobal = 0; @@ -85,106 +78,16 @@ Torrent.prototype = /* * Constructor */ - initialize: function( transferListParent, fileListParent, controller, data) { + initialize: function( controller, data) { this._id = data.id; this._hashString = data.hashString; this._sizeWhenDone = data.sizeWhenDone; this._trackerStats = this.buildTrackerStats(data.trackerStats); this._file_model = [ ]; - this._file_view = [ ]; this.initMetaData( data ); - // Create a new
  • element - var top_e = document.createElement( 'li' ); - top_e.className = 'torrent'; - top_e.id = 'torrent_' + data.id; - top_e._torrent = this; - var element = $(top_e); - $(element).bind('dblclick', function(e) { transmission.toggleInspector(); }); - element._torrent = this; - element._id = this._id; - this._element = element; - this._controller = controller; - controller._rows.push( element ); - - // Create the 'name'
    - var e = document.createElement( 'div' ); - e.className = 'torrent_name'; - top_e.appendChild( e ); - element._name_container = e; - - // Create the 'peer details'
    - e = document.createElement( 'div' ); - e.className = 'torrent_peer_details'; - top_e.appendChild( e ); - element._peer_details_container = e; - - //Create a progress bar container - top_a = document.createElement( 'div' ); - top_a.className = 'torrent_progress_bar_container'; - element._progress_bar_container = top_a; - - // Create the 'in progress' bar - e = document.createElement( 'div' ); - e.className = 'torrent_progress_bar incomplete'; - e.style.width = '0%'; - top_a.appendChild( e ); - element._progress_complete_container = e; - - // Create the 'incomplete' bar (initially hidden) - e = document.createElement( 'div' ); - e.className = 'torrent_progress_bar incomplete'; - e.style.display = 'none'; - top_a.appendChild( e ); - element._progress_incomplete_container = e; - - //Add the progress bar container to the torrent - top_e.appendChild(top_a); - - // Add the pause/resume button - don't specify the - // image or alt text until the 'refresh()' function - // (depends on torrent state) - var image = document.createElement( 'div' ); - image.className = 'torrent_pause'; - e = document.createElement( 'a' ); - e.appendChild( image ); - top_e.appendChild( e ); - element._pause_resume_button_image = image; - if (!iPhone) $(e).bind('click', function(e) { element._torrent.clickPauseResumeButton(e); }); - - // Create the 'progress details'
    - e = document.createElement( 'div' ); - e.className = 'torrent_progress_details'; - top_e.appendChild( e ); - element._progress_details_container = e; - - // Set the torrent click observer - element.bind('click', function(e){ element._torrent.clickTorrent(e) }); - - // Safari hack - first torrent needs to be moved down for some reason. Seems to be ok when - // using
  • 's in straight html, but adding through the DOM gets a bit odd. - if ($.browser.safari) - this._element.css('margin-top', '7px'); - - this.initializeTorrentFilesInspectorGroup( fileListParent ); - // Update all the labels etc this.refresh(data); - - // insert the element - transferListParent.appendChild(top_e); - }, - - initializeTorrentFilesInspectorGroup: function( fileListParent ) { - var e = document.createElement( 'ul' ); - e.className = 'inspector_torrent_file_list inspector_group'; - e.style.display = 'none'; - fileListParent.appendChild( e ); - this._fileList = e; - }, - - fileList: function() { - return $(this._fileList); }, buildTrackerStats: function(trackerStats) { @@ -221,19 +124,7 @@ Torrent.prototype = * *--------------------------------------------*/ - /* Return the DOM element for this torrent (a
  • element) */ - element: function() { - return this._element; - }, - - setElement: function( element ) { - this._element = element; - element._torrent = this; - element[0]._torrent = this; - this.refreshHTML( ); - }, - - activity: function() { return this._download_speed + this._upload_speed; }, + activity: function() { return this.downloadSpeed() + this.uploadSpeed(); }, comment: function() { return this._comment; }, completed: function() { return this._completed; }, creator: function() { return this._creator; }, @@ -246,14 +137,14 @@ Torrent.prototype = || this.peersSendingToUs() > 0 || this.webseedsSendingToUs() > 0 || this.state() == Torrent._StatusCheck; }, + isStopped: function() { return this.state() === Torrent._StatusStopped; }, isActive: function() { return this.state() != Torrent._StatusStopped; }, isDownloading: function() { return this.state() == Torrent._StatusDownload; }, isFinished: function() { return this._isFinishedSeeding; }, + isDone: function() { return this._leftUntilDone < 1; }, isSeeding: function() { return this.state() == Torrent._StatusSeed; }, name: function() { return this._name; }, queuePosition: function() { return this._queue_position; }, - isQueued: function() { return ( this.state() == Torrent._StatusSeedWait ) - || ( this.state() == Torrent._StatusDownloadWait ); }, webseedsSendingToUs: function() { return this._webseeds_sending_to_us; }, peersSendingToUs: function() { return this._peers_sending_to_us; }, peersGettingFromUs: function() { return this._peers_getting_from_us; }, @@ -283,96 +174,23 @@ Torrent.prototype = trackerStats: function() { return this._trackerStats; }, uploadSpeed: function() { return this._upload_speed; }, uploadTotal: function() { return this._upload_total; }, - showFileList: function() { - if(this.fileList().is(':visible')) - return; - this.ensureFileListExists(); - this.refreshFileView(); - this.fileList().show(); - }, - hideFileList: function() { this.fileList().hide(); }, - seedRatioLimit: function(){ + seedRatioLimit: function(controller){ switch( this._seed_ratio_mode ) { - case Torrent._RatioUseGlobal: return this._controller.seedRatioLimit(); + case Torrent._RatioUseGlobal: return controller.seedRatioLimit(); case Torrent._RatioUseLocal: return this._seed_ratio_limit; default: return -1; } }, + getErrorMessage: function() { + if( this._error == Torrent._ErrTrackerWarning ) + return 'Tracker returned a warning: ' + this._error_string; + if( this._error == Torrent._ErrTrackerError ) + return 'Tracker returned an error: ' + this._error_string; + if( this._error == Torrent._ErrLocalError ) + return 'Error: ' + this._error_string; + return null; + }, - /*-------------------------------------------- - * - * E V E N T F U N C T I O N S - * - *--------------------------------------------*/ - - /* - * Process a click event on this torrent - */ - clickTorrent: function( event ) - { - // Prevents click carrying to parent element - // which deselects all on click - event.stopPropagation(); - // but still hide the context menu if it is showing - $('#jqContextMenu').hide(); - - var torrent = this; - - // 'Apple' button emulation on PC : - // Need settable meta-key and ctrl-key variables for mac emulation - var meta_key = event.metaKey; - var ctrl_key = event.ctrlKey; - if (event.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) { - meta_key = true; - ctrl_key = false; - } - - // Shift-Click - Highlight a range between this torrent and the last-clicked torrent - if (iPhone) { - if ( torrent.isSelected() ) - torrent._controller.showInspector(); - torrent._controller.setSelectedTorrent( torrent, true ); - - } else if (event.shiftKey) { - torrent._controller.selectRange( torrent, true ); - // Need to deselect any selected text - window.focus(); - - // Apple-Click, not selected - } else if (!torrent.isSelected() && meta_key) { - torrent._controller.selectTorrent( torrent, true ); - - // Regular Click, not selected - } else if (!torrent.isSelected()) { - torrent._controller.setSelectedTorrent( torrent, true ); - - // Apple-Click, selected - } else if (torrent.isSelected() && meta_key) { - torrent._controller.deselectTorrent( torrent, true ); - - // Regular Click, selected - } else if (torrent.isSelected()) { - torrent._controller.setSelectedTorrent( torrent, true ); - } - - torrent._controller.setLastTorrentClicked(torrent); - }, - - /* - * Process a click event on the pause/resume button - */ - clickPauseResumeButton: function( event ) - { - // prevent click event resulting in selection of torrent - event.stopPropagation(); - - // either stop or start the torrent - var torrent = this; - if( torrent.isActive( ) ) - torrent._controller.stopTorrent( torrent ); - else - torrent._controller.startTorrent( torrent ); - }, /*-------------------------------------------- * @@ -380,22 +198,18 @@ Torrent.prototype = * *--------------------------------------------*/ - refreshMetaData: function(data) { + fireDataChanged: function() + { + $(this).trigger('dataChanged',[]); + }, + + refreshMetaData: function(data) + { this.initMetaData( data ); - this.ensureFileListExists(); - this.refreshFileView(); - this.refreshHTML( ); + this.fireDataChanged(); }, - refresh: function(data) { - this.refreshData( data ); - this.refreshHTML( ); - }, - - /* - * Refresh display - */ - refreshData: function(data) + refresh: function(data) { if( this.needsMetaData() && ( data.metadataPercentComplete >= 1 ) ) transmission.refreshMetaData( [ this._id ] ); @@ -430,6 +244,8 @@ Torrent.prototype = if (data.fileStats) this.refreshFileModel( data ); + + this.fireDataChanged(); }, refreshFileModel: function(data) { @@ -444,275 +260,6 @@ Torrent.prototype = } }, - getErrorMessage: function() - { - if( this._error == Torrent._ErrTrackerWarning ) - return 'Tracker returned a warning: ' + this._error_string; - if( this._error == Torrent._ErrTrackerError ) - return 'Tracker returned an error: ' + this._error_string; - if( this._error == Torrent._ErrLocalError ) - return 'Error: ' + this._error_string; - return null; - }, - - formatUL: function() { - return 'UL: ' + Transmission.fmt.speedBps(this._upload_speed); - }, - formatDL: function() { - return 'DL: ' + Transmission.fmt.speedBps(this._download_speed); - }, - - getPeerDetails: function() - { - var c; - - var compact_mode = this._controller[Prefs._CompactDisplayState]; - if(( c = this.getErrorMessage( ))) - return c; - - var st = this.state( ); - switch( st ) - { - case Torrent._StatusStopped: - case Torrent._StatusCheckWait: - case Torrent._StatusDownloadWait: - case Torrent._StatusSeedWait: - c = this.stateStr( ); - break; - - case Torrent._StatusDownload: - var a = [ ]; - if(!compact_mode) - a.push( 'Downloading from', this.peersSendingToUs(), 'of', this._peers_connected, 'peers', '-' ); - a.push( this.formatDL(), this.formatUL() ); - c = a.join(' '); - break; - - case Torrent._StatusSeed: - if(compact_mode){ - c = this.formatUL(); - } else { - // 'Seeding to 13 of 22 peers - UL: 36.2 KiB/s' - c = [ 'Seeding to', this.peersGettingFromUs(), 'of', this._peers_connected, 'peers', '-', this.formatUL() ].join(' '); - } - break; - - case Torrent._StatusCheck: - // 'Verifying local data (40% tested)' - c = [ 'Verifying local data (', Transmission.fmt.percentString( 100.0 * this._recheckProgress ), '% tested)' ].join(''); - break; - } - return c; - }, - - refreshHTML: function() { - var c; - var e; - var progress_details; - var root = this._element; - var MaxBarWidth = 100; // reduce this to make the progress bar shorter (%) - var compact_mode = this._controller[Prefs._CompactDisplayState]; - var compact = ''; - if(compact_mode){ - compact = ' compact'; - root._peer_details_container.style.display = 'none'; - } else { - root._peer_details_container.style.display = 'block'; - } - - root._progress_details_container.className = 'torrent_progress_details'+compact - root._progress_bar_container.className = 'torrent_progress_bar_container'+compact; - root._name_container.className = 'torrent_name'+compact; - - setInnerHTML( root._name_container, this._name ); - - // Add the progress bar - var notDone = this._leftUntilDone > 0; - - // Fix for situation - // when a verifying/downloading torrent gets state seeding - if( this._state === Torrent._StatusSeeding ) - notDone = false ; - - if( this.needsMetaData() ){ - var metaPercentComplete = this._metadataPercentComplete * 100; - progress_details = [ "Magnetized transfer - retrieving metadata (", - Transmission.fmt.percentString( metaPercentComplete ), - "%)" ].join(''); - - var empty = ""; - if(metaPercentComplete == 0) - empty = "empty"; - - root._progress_complete_container.style.width = metaPercentComplete + "%"; - root._progress_complete_container.className = 'torrent_progress_bar in_progress meta ' + empty+compact; - root._progress_incomplete_container.style.width = 100 - metaPercentComplete + "%" - root._progress_incomplete_container.className = 'torrent_progress_bar incomplete compact meta'+compact; - root._progress_incomplete_container.style.display = 'block'; - } - else if( notDone ) - { - // Create the 'progress details' label - // Eg: '101 MiB of 631 MiB (16.02%) - 2 hr remaining' - - c = [ Transmission.fmt.size( this._sizeWhenDone - this._leftUntilDone ), - ' of ', Transmission.fmt.size( this._sizeWhenDone ), - ' (', this.getPercentDoneStr(), '%)' ]; - if( this.isActive( ) ) { - c.push( ' - ' ); - if (this._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining ) - c.push( 'remaining time unknown' ); - else - c.push( Transmission.fmt.timeInterval(this._eta) + ' remaining' ); - } - progress_details = c.join(''); - - // Figure out the percent completed - var css_completed_width = ( this.getPercentDone() * MaxBarWidth ).toTruncFixed( 2 ); - - // Update the 'in progress' bar - e = root._progress_complete_container; - c = [ 'torrent_progress_bar'+compact, - this.isActive() ? 'in_progress' : 'incomplete_stopped' ]; - if(css_completed_width === 0) { c.push( 'empty' ); } - e.className = c.join(' '); - e.style.width = css_completed_width + '%'; - - // Update the 'incomplete' bar - e = root._progress_incomplete_container; - e.className = 'torrent_progress_bar incomplete' - e.style.width = (MaxBarWidth - css_completed_width) + '%'; - e.style.display = 'block'; - } - else - { - // Create the 'progress details' label - - if( this._size == this._sizeWhenDone ) - { - // seed: '698.05 MiB' - c = [ Transmission.fmt.size( this._size ) ]; - } - else - { - // partial seed: '127.21 MiB of 698.05 MiB (18.2%)' - c = [ Transmission.fmt.size( this._sizeWhenDone ), ' of ', Transmission.fmt.size( this._size ), - ' (', Transmission.fmt.percentString( 100.0 * this._sizeWhenDone / this._size ), '%)' ]; - } - - // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)' - c.push( ', uploaded ', Transmission.fmt.size( this._upload_total ), - ' (Ratio ', Transmission.fmt.ratioString( this._upload_ratio ), ')' ); - - // maybe append remaining time - if( this.isActive( ) && this.seedRatioLimit( ) > 0 ) - { - c.push(' - '); - - if (this._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining ) - c.push( 'remaining time unknown' ); - else - c.push( Transmission.fmt.timeInterval(this._eta), ' remaining' ); - } - - progress_details = c.join(''); - - var status = this.isActive() ? 'complete' : 'complete_stopped'; - - if(this.isActive() && this.seedRatioLimit() > 0){ - status = 'complete seeding' - var seedRatioRatio = this._upload_ratio / this.seedRatioLimit(); - var seedRatioPercent = Math.round( seedRatioRatio * 100 * MaxBarWidth ) / 100; - - // Set progress to percent seeded - root._progress_complete_container.style.width = seedRatioPercent + '%'; - - // Update the 'incomplete' bar - root._progress_incomplete_container.className = 'torrent_progress_bar incomplete seeding' - root._progress_incomplete_container.style.display = 'block'; - root._progress_incomplete_container.style.width = MaxBarWidth - seedRatioPercent + '%'; - } - else - { - // Hide the 'incomplete' bar - root._progress_incomplete_container.className = 'torrent_progress_bar incomplete' - root._progress_incomplete_container.style.display = 'none'; - - // Set progress to maximum - root._progress_complete_container.style.width = MaxBarWidth + '%'; - } - - // Update the 'in progress' bar - e = root._progress_complete_container; - e.className = 'torrent_progress_bar ' + status; - } - - var hasError = this.getErrorMessage( ) != undefined; - // Update the progress details - if(compact_mode){ - progress_details = this.getPeerDetails(); - $(root._progress_details_container).toggleClass('error',hasError); - } else { - $(root._peer_details_container).toggleClass('error',hasError); - } - setInnerHTML( root._progress_details_container, progress_details ); - - if( compact ){ - var width = root._progress_details_container.offsetLeft - root._name_container.offsetLeft; - root._name_container.style.width = width + 'px'; - } - else { - root._name_container.style.width = '100%'; - } - - // Update the peer details and pause/resume button - e = root._pause_resume_button_image; - if ( this.state() === Torrent._StatusStopped ) { - e.alt = 'Resume'; - e.className = "torrent_resume"+compact; - } else { - e.alt = 'Pause'; - e.className = "torrent_pause"+compact; - } - - setInnerHTML( root._peer_details_container, this.getPeerDetails( ) ); - - this.refreshFileView( ); - }, - - refreshFileView: function() { - if( this._file_view.length ) - for( var i=0; i= this._size; - }, - - isEditable: function () { - return (this._torrent._file_model.length>1) && !this.isDone(); - }, - - readAttributes: function(file_data) { - if( file_data.index !== undefined && file_data.index !== this._index ) { - this._index = file_data.index; - this._dirty = true; - } - if( file_data.bytesCompleted !== undefined && file_data.bytesCompleted !== this._done ) { - this._done = file_data.bytesCompleted; - this._dirty = true; - } - if( file_data.length !== undefined && file_data.length !== this._size ) { - this._size = file_data.length; - this._dirty = true; - } - if( file_data.priority !== undefined && file_data.priority !== this._prio ) { - this._prio = file_data.priority; - this._dirty = true; - } - if( file_data.wanted !== undefined && file_data.wanted !== this._wanted ) { - this._wanted = file_data.wanted; - this._dirty = true; - } - }, - - element: function() { - return $(this._element); - }, - - domElement: function() { - return this._element; - }, - - setPriority: function(prio) { - if (this.isEditable()) { - var cmd; - switch( prio ) { - case 1: cmd = 'priority-high'; break; - case -1: cmd = 'priority-low'; break; - default: cmd = 'priority-normal'; break; - } - this._prio = prio; - this._dirty = true; - this._torrent._controller.changeFileCommand( cmd, this._torrent, this ); - } - }, - - setWanted: function(wanted, process) { - this._dirty = true; - this._wanted = wanted; - if(!iPhone) - this.element().toggleClass( 'skip', !wanted ); - if (process) { - var command = wanted ? 'files-wanted' : 'files-unwanted'; - this._torrent._controller.changeFileCommand(command, this._torrent, this); - } - }, - - toggleWanted: function() { - if (this.isEditable()) - this.setWanted( !this._wanted, true ); - }, - - refreshHTML: function() { - if( this._dirty ) { - this._dirty = false; - this.refreshProgressHTML(); - this.refreshWantedHTML(); - this.refreshPriorityHTML(); - } - }, - - refreshProgressHTML: function() { - var c = [ Transmission.fmt.size(this._done), - ' of ', - Transmission.fmt.size(this._size), - ' (', - this._size ? Transmission.fmt.percentString(100 * this._done / this._size) : '100', - '%)' ].join(''); - setInnerHTML(this._progress[0], c); - }, - - refreshWantedHTML: function() { - var e = this.domElement(); - var c = [ e.classNameConst ]; - if(!this._wanted) { c.push( 'skip' ); } - if(this.isDone()) { c.push( 'complete' ); } - e.className = c.join(' '); - }, - - refreshPriorityHTML: function() { - var e = this._priority_control; - var c = [ e.classNameConst ]; - switch( this._prio ) { - case 1 : c.push( 'high' ); break; - case -1 : c.push( 'low' ); break; - default : c.push( 'normal' ); break; - } - e.className = c.join(' '); - }, - - fileWantedControlClicked: function(event) { - this.toggleWanted(); - }, - - filePriorityControlClicked: function(event, element) { - var x = event.pageX; - while (element !== null) { - x = x - element.offsetLeft; - element = element.offsetParent; - } - - var prio; - if(iPhone) - { - if( x < 8 ) prio = -1; - else if( x < 27 ) prio = 0; - else prio = 1; - } - else - { - if( x < 12 ) prio = -1; - else if( x < 23 ) prio = 0; - else prio = 1; - } - this.setPriority( prio ); - } -}; diff --git a/web/javascript/transmission.js b/web/javascript/transmission.js index 67bbbaa21..2b7afd3f0 100644 --- a/web/javascript/transmission.js +++ b/web/javascript/transmission.js @@ -51,8 +51,6 @@ Transmission.prototype = $('#block_update_button').bind('click', function(e){ tr.blocklistUpdateClicked(e); return false; }); $('#stats_close_button').bind('click', function(e){ tr.closeStatsClicked(e); return false; }); $('.inspector_tab').bind('click', function(e){ tr.inspectorTabClicked(e, this); }); - $('.file_wanted_control').live('click', function(e){ tr.fileWantedClicked(e, this); }); - $('.file_priority_control').live('click', function(e){ tr.filePriorityClicked(e, this); }); $('#files_select_all').live('click', function(e){ tr.filesSelectAllClicked(e, this); }); $('#files_deselect_all').live('click', function(e){ tr.filesDeselectAllClicked(e, this); }); $('#open_link').bind('click', function(e){ tr.openTorrentClicked(e); }); @@ -224,6 +222,15 @@ Transmission.prototype = }); }, + setCompactMode: function( is_compact ) + { + this.torrentRenderer = is_compact ? new TorrentRendererCompact( ) + : new TorrentRendererFull( ); + $('ul.torrent_list li').remove(); + this._rows = []; + this.refilter(); + }, + /* * Load the clutch prefs and init the GUI according to those prefs */ @@ -251,6 +258,8 @@ Transmission.prototype = if( !iPhone && this[Prefs._CompactDisplayState] ) $('#compact_view').selectMenuItem(); + + this.setCompactMode( this[Prefs._CompactDisplayState] ); }, /* @@ -362,9 +371,13 @@ Transmission.prototype = boundingRightPad: 20, boundingBottomPad: 5, onContextMenu: function(e) { - var closestRow = $(e.target).closest('.torrent')[0]._torrent; - if(!closestRow.isSelected()) - tr.setSelectedTorrent( closestRow, true ); + var closest_row = $(e.target).closest('.torrent')[0]; + for( var i=0, row; row = tr._rows[i]; ++i ) { + if( row.getElement() === closest_row ) { + tr.setSelectedRow( row ); + break; + } + } return true; } }); @@ -421,45 +434,56 @@ Transmission.prototype = { var torrents = [ ]; for( var i=0, row; row=this._rows[i]; ++i ) - if( row._torrent && ( row[0].style.display != 'none' ) ) - torrents.push( row._torrent ); + if( row.isVisible( ) ) + torrents.push( row.getTorrent( ) ); return torrents; }, + getSelectedRows: function() + { + var s = [ ]; + + for( var i=0, row; row=this._rows[i]; ++i ) + if( row.isSelected( ) ) + s.push( row ); + + return s; + }, + getSelectedTorrents: function() { - var v = this.getVisibleTorrents( ); - var s = [ ]; - for( var i=0, row; row=v[i]; ++i ) - if( row.isSelected( ) ) - s.push( row ); + var s = this.getSelectedRows( ); + + for( var i=0, row; row=s[i]; ++i ) + s[i] = s[i].getTorrent(); + return s; }, - getDeselectedTorrents: function() { - var visible_torrent_ids = jQuery.map(this.getVisibleTorrents(), function(t) { return t.id(); } ); - var s = [ ]; - jQuery.each( this.getAllTorrents( ), function() { - var visible = (-1 != jQuery.inArray(this.id(), visible_torrent_ids)); - if (!this.isSelected() || !visible) - s.push( this ); - } ); - return s; + getDeselectedTorrents: function() + { + var ret = { }; + for( var key in this._torrents ) + ret[ key ] = this._torrents[key]; + var sel = this.getSelectedTorrents( ); + for( var i=0, tor; tor=sel[i]; ++i ) + delete ret[ tor.id() ]; + return ret; }, getVisibleRows: function() { var rows = [ ]; for( var i=0, row; row=this._rows[i]; ++i ) - if( row[0].style.display != 'none' ) + if( row.isVisible( ) ) rows.push( row ); return rows; }, - getTorrentIndex: function( rows, torrent ) + getRowIndex: function( rows, row ) { - for( var i=0, row; row=rows[i]; ++i ) - if( row._torrent == torrent ) + for( var i=0, r; r=rows[i]; ++i ) + if( r === row ) return i; return null; }, @@ -479,8 +503,8 @@ Transmission.prototype = var scrollTop = container.scrollTop( ); var innerHeight = container.innerHeight( ); - var offsetTop = e[0].offsetTop; - var offsetHeight = e.outerHeight( ); + var offsetTop = e.offsetTop; + var offsetHeight = $(e).outerHeight( ); if( offsetTop < scrollTop ) container.scrollTop( offsetTop ); @@ -501,72 +525,57 @@ Transmission.prototype = * *--------------------------------------------*/ - setSelectedTorrent: function( torrent, doUpdate ) { - this.deselectAll( ); - this.selectTorrent( torrent, doUpdate ); + setSelectedRow: function( row ) { + var rows = this.getSelectedRows( ); + for( var i=0, r; r=rows[i]; ++i ) + this.deselectRow( r ); + this.selectRow( row ); }, - selectElement: function( e, doUpdate ) { - e.addClass('selected'); - if( doUpdate ) - this.selectionChanged( ); - }, - selectRow: function( rowIndex, doUpdate ) { - this.selectElement( this._rows[rowIndex], doUpdate ); - }, - selectTorrent: function( torrent, doUpdate ) { - if( torrent._element ) - this.selectElement( torrent._element, doUpdate ); + selectRow: function( row ) { + row.setSelected( true ); + this.callSelectionChangedSoon(); }, - deselectElement: function( e, doUpdate ) { - e.removeClass('selected'); - if( doUpdate ) - this.selectionChanged( ); - }, - deselectTorrent: function( torrent, doUpdate ) { - if( torrent._element ) - this.deselectElement( torrent._element, doUpdate ); + deselectRow: function( row ) { + row.setSelected( false ); + this.callSelectionChangedSoon(); }, - selectAll: function( doUpdate ) { + selectAll: function( ) { var tr = this; for( var i=0, row; row=tr._rows[i]; ++i ) - tr.selectElement( row ); - if( doUpdate ) - tr.selectionChanged(); + tr.selectRow( row ); + this.callSelectionChangedSoon(); }, - deselectAll: function( doUpdate ) { - var tr = this; - for( var i=0, row; row=tr._rows[i]; ++i ) - tr.deselectElement( row ); - tr._last_torrent_clicked = null; - if( doUpdate ) - tr.selectionChanged( ); + deselectAll: function( ) { + for( var i=0, row; row=this._rows[i]; ++i ) + this.deselectRow( row ); + this.callSelectionChangedSoon(); + this._last_row_clicked = null; }, /* * Select a range from this torrent to the last clicked torrent */ - selectRange: function( torrent, doUpdate ) + selectRange: function( row ) { - if( !this._last_torrent_clicked ) + if( this._last_row_clicked === null ) { - this.selectTorrent( torrent ); + this.selectRow( row ); } else // select the range between the prevous & current { var rows = this.getVisibleRows( ); - var i = this.getTorrentIndex( rows, this._last_torrent_clicked ); - var end = this.getTorrentIndex( rows, torrent ); + var i = this.getRowIndex( rows, this._last_row_clicked ); + var end = this.getRowIndex( rows, row ); var step = i < end ? 1 : -1; for( ; i!=end; i+=step ) - this.selectRow( i ); - this.selectRow( i ); + this.selectRow( this._rows[i] ); + this.selectRow( this._rows[i] ); } - if( doUpdate ) - this.selectionChanged( ); + this.callSelectionChangedSoon( ); }, selectionChanged: function() @@ -574,6 +583,13 @@ Transmission.prototype = this.updateButtonStates(); this.updateInspector(); this.updateSelectedData(); + this.selectionChangedTimer = null; + }, + + callSelectionChangedSoon: function() + { + if( this.selectionChangedTimer === null ) + this.selectionChangedTimer = setTimeout(function(o) { o.selectionChanged(); }, 200, this); }, /*-------------------------------------------- @@ -588,28 +604,28 @@ Transmission.prototype = keyDown: function(event) { var tr = this; - var sel = tr.getSelectedTorrents( ); + var sel = tr.getSelectedRows( ); var rows = tr.getVisibleRows( ); var i = -1; if( event.keyCode == 40 ) // down arrow { - var t = sel.length ? sel[sel.length-1] : null; - i = t==null ? null : tr.getTorrentIndex(rows,t)+1; + var r = sel.length ? sel[sel.length-1] : null; + i = r==null ? null : tr.getRowIndex(rows,r)+1; if( i == rows.length || i == null ) i = 0; } else if( event.keyCode == 38 ) // up arrow { - var t = sel.length ? sel[0] : null - i = t==null ? null : tr.getTorrentIndex(rows,t)-1; + var r = sel.length ? sel[0] : null + i = r==null ? null : tr.getRowIndex(rows,r)-1; if( i == -1 || i == null ) i = rows.length - 1; } if( 0<=i && i 1 ) { + var command = wanted ? 'files-wanted' : 'files-unwanted'; + this.changeFileCommand( command, rows ); } - return files_list; }, toggleFilterClicked: function(event) { @@ -1237,7 +1222,7 @@ Transmission.prototype = // Figure out which menu has been clicked switch ($element.parent()[0].id) { - // Display the preferences dialog + // Display the preferences dialog case 'footer_super_menu': if ($element[0].id == 'preferences') { $('div#prefs_container div#pref_error').hide(); @@ -1255,7 +1240,7 @@ Transmission.prototype = $element.selectMenuItem(); else $element.deselectMenuItem(); - this.refreshDisplay( ); + this.setCompactMode( this[Prefs._CompactDisplayState] ); } else if ($element[0].id == 'homepage') { window.open('http://www.transmissionbt.com/'); @@ -1336,11 +1321,6 @@ Transmission.prototype = return false; // to prevent the event from bubbling up }, - setLastTorrentClicked: function( torrent ) - { - this._last_torrent_clicked = torrent; - }, - /* * Update the inspector with the latest data for the selected torrents */ @@ -1407,7 +1387,7 @@ Transmission.prototype = setInnerHTML( tab.creator, na ); setInnerHTML( tab.download_dir, na ); setInnerHTML( tab.error, na ); - this.updateVisibleFileLists(); + this.updateFileList(); this.updatePeersLists(); this.updateTrackersLists(); $("#torrent_inspector_size, .inspector_row > div:contains('N/A')").css('color', '#666'); @@ -1491,27 +1471,67 @@ Transmission.prototype = this.updatePeersLists(); this.updateTrackersLists(); $(".inspector_row > div:contains('N/A')").css('color', '#666'); - this.updateVisibleFileLists(); + this.updateFileList(); }, - fileListIsVisible: function() { - return this._inspector_tab_files.className.indexOf('selected') != -1; + onFileWantedToggled: function( row, want ) { + var command = want ? 'files-wanted' : 'files-unwanted'; + this.changeFileCommand( command, [ row ] ); }, - - updateVisibleFileLists: function() { - if( this.fileListIsVisible( ) === true ) { - var selected = this.getSelectedTorrents(); - jQuery.each( selected, function() { this.showFileList(); } ); - jQuery.each( this.getDeselectedTorrents(), function() { this.hideFileList(); } ); - // Check if we need to display the select all buttions - if ( !selected.length ) { - if ( $("#select_all_button_container").is(':visible') ) - $("#select_all_button_container").hide(); - } else { - if ( !$("#select_all_button_container").is(':visible') ) - $("#select_all_button_container").show(); - } + onFilePriorityToggled: function( row, priority ) { + var command; + switch( priority ) { + case -1: command = 'priority-low'; break; + case 1: command = 'priority-high'; break; + default: command = 'priority-normal'; break; } + this.changeFileCommand( command, [ row ] ); + }, + clearFileList: function() { + $(this._inspector_file_list).empty(); + delete this._files_torrent; + delete this._files; + }, + updateFileList: function() { + + // if the file list is hidden, clear the list + if( this._inspector_tab_files.className.indexOf('selected') == -1 ) { + this.clearFileList( ); + return; + } + + // if not torrent is selected, clear the list + var selected_torrents = this.getSelectedTorrents( ); + if( selected_torrents.length != 1 ) { + this.clearFileList( ); + return; + } + + // if the active torrent hasn't changed, noop + var torrent = selected_torrents[0]; + if( this._files_torrent === torrent ) + return; + + // build the file list + this.clearFileList( ); + this._files_torrent = torrent; + var n = torrent._file_model.length; + this._files = new Array( n ); + var fragment = document.createDocumentFragment( ); + var tr = this; + for( var i=0; i 0) tr.remote.loadTorrentFiles( refresh_files_for ); @@ -1796,16 +1819,14 @@ Transmission.prototype = }, updateTorrentsFileData: function( torrents ){ - var tr = this; - var listIsVisible = tr.fileListIsVisible( ); - jQuery.each( torrents, function() { - var t = tr._torrents[this.id]; - if (t) { - t.refreshFileModel(this); - if( listIsVisible && t.isSelected()) - t.refreshFileView(); + for( var i=0, o; o=torrents[i]; ++i ) { + var t = this._torrents[o.id]; + if( t !== null ) { + t.refreshFileModel( o ); + if( t === this._files_torrent ) + this.refreshFileView(); } - } ); + } }, initializeAllTorrents: function(){ @@ -1813,19 +1834,63 @@ Transmission.prototype = this.remote.getInitialDataFor( null ,function(torrents) { tr.addTorrents(torrents); } ); }, + onRowClicked: function( ev, row ) + { + // Prevents click carrying to parent element + // which deselects all on click + event.stopPropagation(); + // but still hide the context menu if it is showing + $('#jqContextMenu').hide(); + + // 'Apple' button emulation on PC : + // Need settable meta-key and ctrl-key variables for mac emulation + var meta_key = event.metaKey; + var ctrl_key = event.ctrlKey; + if (event.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) { + meta_key = true; + ctrl_key = false; + } + + // Shift-Click - selects a range from the last-clicked row to this one + if (iPhone) { + if ( row.isSelected() ) + this.showInspector(); + this.setSelectedRow( row, true ); + + } else if (event.shiftKey) { + this.selectRange( row, true ); + // Need to deselect any selected text + window.focus(); + + // Apple-Click, not selected + } else if (!row.isSelected() && meta_key) { + this.selectRow( row, true ); + + // Regular Click, not selected + } else if (!row.isSelected()) { + this.setSelectedRow( row, true ); + + // Apple-Click, selected + } else if (row.isSelected() && meta_key) { + this.deselectRow( row ); + + // Regular Click, selected + } else if (row.isSelected()) { + this.setSelectedRow( row, true ); + } + + this._last_row_clicked = row; + }, + addTorrents: function( new_torrents ) { - var transferFragment = document.createDocumentFragment( ); - var fileFragment = document.createDocumentFragment( ); + var tr = this; for( var i=0, row; row=new_torrents[i]; ++i ) { - var new_torrent = new Torrent( transferFragment, fileFragment, this, row ); - this._torrents[new_torrent.id()] = new_torrent; + var t = new Torrent( this, row ); + this._torrents[t.id()] = t; } - this._inspector_file_list.appendChild( fileFragment ); - this._torrent_list.appendChild( transferFragment ); - this.refilter( ); }, @@ -1839,7 +1904,7 @@ Transmission.prototype = if(torrent) { removedAny = true; - var e = torrent.element(); + var e = torrent.view; if( e ) { var row_index; for( var i=0, row; row = tr._rows[i]; ++i ) { @@ -1855,8 +1920,6 @@ Transmission.prototype = e.remove(); } - torrent.hideFileList(); - torrent.deleteFiles(); delete tr._torrents[torrent.id()]; } }); @@ -1866,9 +1929,9 @@ Transmission.prototype = refreshDisplay: function( ) { - var torrents = this.getVisibleTorrents(); - for( var i=0; torrents[i]; ++i ) - torrents[i].refreshHTML(); + var rows = this.getVisibleRows( ); + for( var i=0, row; row=rows[i]; ++i ) + row.render( this ); }, /* @@ -1877,12 +1940,8 @@ Transmission.prototype = setTorrentBgColors: function( ) { var rows = this.getVisibleRows( ); - for( var i=0, row; row=rows[i]; ++i ) { - var wasEven = row[0].className.indexOf('even') != -1; - var isEven = ((i+1) % 2 == 0); - if( wasEven != isEven ) - row.toggleClass('even', isEven); - } + for( var i=0, row; row=rows[i]; ++i ) + row.setEven((i+1) % 2 == 0); }, updateStatusbar: function() @@ -2068,8 +2127,8 @@ Transmission.prototype = var tr = this; this.remote.stopTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} ); }, - changeFileCommand: function(command, torrent, file) { - this.remote.changeFileCommand(command, torrent, file) + changeFileCommand: function(command, rows) { + this.remote.changeFileCommand(command, rows); }, hideiPhoneAddressbar: function(timeInSeconds) { @@ -2114,6 +2173,16 @@ Transmission.prototype = **** ***/ + onToggleRunningClicked: function( ev ) + { + var torrent = ev.data.r.getTorrent( ); + + if( torrent.isStopped( ) ) + this.startTorrent( torrent ); + else + this.stopTorrent( torrent ); + }, + refilter: function() { // decide which torrents to keep showing @@ -2129,23 +2198,39 @@ Transmission.prototype = // make a backup of the selection var sel = this.getSelectedTorrents( ); - this.deselectAll( ); - // hide the ones we're not keeping + // add rows it there aren't enough + if( this._rows.length < keep.length ) { + var tr = this; + var fragment = document.createDocumentFragment( ); + while( this._rows.length < keep.length ) { + var row = new TorrentRow( this, this.torrentRenderer ); + if( !iPhone ) { + var b = row.getToggleRunningButton( ); + if( b !== null ) { + $(b).bind('click', {r:row}, function(e) { tr.onToggleRunningClicked(e); }); + } + } + $(row.getElement()).bind('click',{r: row}, function(ev){ tr.onRowClicked(ev,ev.data.r);}); + fragment.appendChild( row.getElement() ); + this._rows.push( row ); + } + this._torrent_list.appendChild(fragment); + } + + // hide rows if there are too many for( var i=keep.length, e; e=this._rows[i]; ++i ) { delete e._torrent; - e[0].style.display = 'none'; + e.setVisible(false); } // show the ones we're keeping - sel.sort( Torrent.compareById ); for( var i=0, len=keep.length; i