Changeset 12716


Ignore:
Timestamp:
Aug 24, 2011, 2:04:35 AM (10 years ago)
Author:
jordan
Message:

(trunk web) A lot of web cleanup, refactoring, bugfixes, and probably new breakage. Filterbar/statusbar has been reworked. Added filter-by-tracker. Compact mode now works in "iPhone" mode.

Location:
trunk/web
Files:
1 deleted
13 edited

Legend:

Unmodified
Added
Removed
  • trunk/web/index.html

    r12712 r12716  
    1010                <link rel="apple-touch-icon" href="./images/webclip-icon.png"/>
    1111                <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
     12                <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>
     13                <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" type="text/css" media="all" />
     14<!--
     15                <link media="screen" href="./stylesheets/iphone.css" type= "text/css" rel="stylesheet" />
     16-->
    1217                <link media="only screen and (max-device-width: 480px)" href="./stylesheets/iphone.css" type= "text/css" rel="stylesheet" />
    1318                <link media="screen and (min-device-width: 481px)" href="./stylesheets/common.css" type="text/css" rel="stylesheet" />
     
    1621                        <link media="screen" href="./stylesheets/ieAll.css" type="text/css" rel="stylesheet" />
    1722                <![endif]-->
    18                 <!--[if IE 6]><link media="screen" href="./stylesheets/ie6.css" type="text/css" rel="stylesheet" /><![endif]-->
    1923                <!--[if IE 7]><link media="screen" href="./stylesheets/ie7.css" type="text/css" rel="stylesheet" /><![endif]-->
    2024                <script type="text/javascript" src="./javascript/jquery/jquery.transmenu.min.js"></script>
     
    3539        <body id="transmission_body">
    3640
    37                 <div class="torrent_global_menu">
     41                <div id="toolbar">
    3842                        <ul>
    3943                                <li id="open"><div id="open_link"><div class="toolbar_image"></div>Open</div></li>
     
    4650                                <li id="resume_all" class="disabled"><div id="resume_all_link"><div class="toolbar_image"></div>Resume All</div></li>
    4751                                <li id="inspector"><div id="inspector_link"><div class="toolbar_image"></div>Inspector</div></li>
    48                                 <li id="filter"><div id="filter_toggle_link"><div class="toolbar_image"></div>Filter</div></li>
    4952                        </ul>
    5053                </div>
    5154
    52                 <div class="torrent_global_details">
    53                         <div id="torrent_global_transfer">0 Transfers</div>
    54                         <div id="torrent_global_upload">0 B/s</div>
    55                         <div id="torrent_global_download">0 B/s</div>
    56                 </div>
    57 
    58                 <div id="torrent_filter_bar">
    59                         <ul>
    60                                 <li><a href="#all" id="filter_all_link" class="active">All</a></li>
    61                                 <li><a href="#active" id="filter_active_link">Active</a></li>
    62                                 <li><a href="#downloading" id="filter_downloading_link">Downloading</a></li>
    63                                 <li><a href="#seeding" id="filter_seeding_link">Seeding</a></li>
    64                                 <li><a href="#paused" id="filter_paused_link">Paused</a></li>
    65                                 <li><a href="#finished" id="filter_finished_link" class="finished">Finished</a></li>
    66                         </ul>
     55                <div id="statusbar">
    6756                        <input type="search" id="torrent_search" placeholder="Filter" results="0" />
     57                        <div id='filter-button'></div>
     58                        <div id='speed-info'>
     59                                <span id="speed-up-label">0 B/s</span>
     60                                <span id="speed-dn-label">0 B/s</span>
     61                        </div>
     62                </div>
     63
     64                <div id="filter-popup" style="display:none;">
     65                        <div id='filter-popup-workarea'>
     66                                <div id='filter-by-state'></div>
     67                                <div id='filter-by-tracker'></div>
     68                        </div>
    6869                </div>
    6970
     
    540541                                </li>
    541542                        </ul>
     543                        <div id="compact-button">&nbsp;</div>
    542544                        <div id="turtle_button">&nbsp;</div>
    543545                        <div style="clear: both; visibility: hidden;"></div>
  • trunk/web/javascript/common.js

    r12706 r12716  
    99var transmission;
    1010var dialog;
    11 var resizeTimer = null;
    1211// Test for a Webkit build that supports box-shadow: 521+ (release Safari 3 is
    1312// actually 523.10.3). We need 3.1 for CSS animation (dialog sheets) but as it
     
    1716if (iPhone) var scroll_timeout;
    1817
    19 if(!Array.indexOf){
     18if (!Array.indexOf){
    2019        Array.prototype.indexOf = function(obj){
    2120                var i, len;
    22                 for(i=0, len=this.length; i<len; i++)
    23                         if(this[i]==obj)
     21                for (i=0, len=this.length; i<len; i++)
     22                        if (this[i]==obj)
    2423                                return i;
    2524                return -1;
     
    3029{
    3130        var minimum = new Array(521,0);
    32         var webKitFields = RegExp("( AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
     31        var webKitFields = RegExp("(AppleWebKit/)([^ ]+)").exec(navigator.userAgent);
    3332        if (!webKitFields || webKitFields.length < 3) return false;
    3433        var version = webKitFields[2].split(".");
     
    4443};
    4544
    46 $(document).ready( function() {
     45$(document).ready(function() {
    4746        // Initialise the dialog controller
    4847        dialog = new Dialog();
     
    8483        if (iPhone){
    8584                window.onload = function(){ setTimeout(function() { window.scrollTo(0,1); },500); };
    86                 window.onorientationchange = function(){ setTimeout( function() { window.scrollTo(0,1); },100); };
    87                 if(window.navigator.standalone)
     85                window.onorientationchange = function(){ setTimeout(function() { window.scrollTo(0,1); },100); };
     86                if (window.navigator.standalone)
    8887                        // Fix min height for iPhone when run in full screen mode from home screen
    8988                        // so the footer appears in the right place
     
    9190                $("label[for=torrent_upload_url]").text("URL: ");
    9291        }
    93 
    94         // Add resize event handler with a timeout to handle browsers that fire a
    95         // resize event for every pixel changed
    96         $(window).bind('resize', function() {
    97                 if (resizeTimer) clearTimeout(resizeTimer);
    98                 resizeTimer = setTimeout('transmission.refreshDisplay()', 50)
    99                 });
    10092});
    10193
     
    114106 * changed since the last update, so even this simple test helps a lot.
    115107 */
    116 function setInnerHTML( e, html )
    117 {
    118         if( e == undefined )
     108function setInnerHTML(e, html)
     109{
     110        if (e == undefined)
    119111                return;
    120112
     
    122114         * For example, "&infin;" gets changed to "∞" somewhere down the line.
    123115         * So, let's use an arbitrary  different field to test our state... */
    124         if( e.currentHTML != html )
     116        if (e.currentHTML != html)
    125117        {
    126118                e.currentHTML = html;
     
    132124 *   Given a numerator and denominator, return a ratio string
    133125 */
    134 Math.ratio = function( numerator, denominator ) {
     126Math.ratio = function(numerator, denominator) {
    135127        var result = Math.floor(100 * numerator / denominator) / 100;
    136128
    137129        // check for special cases
    138         if(result==Number.POSITIVE_INFINITY || result==Number.NEGATIVE_INFINITY) result = -2;
    139         else if(isNaN(result)) result = -1;
     130        if (result==Number.POSITIVE_INFINITY || result==Number.NEGATIVE_INFINITY) result = -2;
     131        else if (isNaN(result)) result = -1;
    140132
    141133        return result;
     
    151143 */
    152144Math.truncateWithPrecision = function(floatnum, precision) {
    153         return Math.floor( floatnum * Math.pow ( 10, precision ) ) / Math.pow( 10, precision );
     145        return Math.floor(floatnum * Math.pow (10, precision)) / Math.pow(10, precision);
    154146};
    155147
     
    158150 *   places
    159151 */
    160 Number.prototype.toTruncFixed = function( place ) {
    161         var ret = Math.truncateWithPrecision( this, place );
    162         return ret.toFixed( place );
     152Number.prototype.toTruncFixed = function(place) {
     153        var ret = Math.truncateWithPrecision(this, place);
     154        return ret.toFixed(place);
    163155}
    164156
     
    173165 * @brief strcmp()-style compare useful for sorting
    174166 */
    175 String.prototype.compareTo = function( that ) {
     167String.prototype.compareTo = function(that) {
    176168        // FIXME: how to fold these two comparisons together?
    177         if( this < that ) return -1;
    178         if( this > that ) return 1;
     169        if (this < that) return -1;
     170        if (this > that) return 1;
    179171        return 0;
    180172}
     
    184176 */
    185177function changeTab(tab, id) {
    186         for ( var x = 0, node; tab.parentNode.childNodes[x]; x++ ) {
     178        for (var x = 0, node; tab.parentNode.childNodes[x]; x++) {
    187179                node = tab.parentNode.childNodes[x];
    188180                if (node == tab) {
     
    192184                }
    193185        }
    194         for ( x = 0; tab.parentNode.parentNode.childNodes[x]; x++ ) {
     186        for (x = 0; tab.parentNode.parentNode.childNodes[x]; x++) {
    195187                node = tab.parentNode.parentNode.childNodes[x];
    196188                if (node.tagName == "DIV") {
     
    213205Prefs._RefreshRate        = 'refresh_rate';
    214206Prefs._SessionRefreshRate = 'session_refresh_rate';
    215 
    216 Prefs._ShowFilter         = 'show_filter';
    217207
    218208Prefs._ShowInspector      = 'show_inspector';
     
    258248 * Set a preference option
    259249 */
    260 Prefs.setValue = function( key, val )
    261 {
    262         if( Prefs._Defaults[key] == undefined )
    263                 console.warn( "unrecognized preference key '%s'", key );
     250Prefs.setValue = function(key, val)
     251{
     252        if (Prefs._Defaults[key] == undefined)
     253                console.warn("unrecognized preference key '%s'", key);
    264254
    265255        var days = 30;
     
    275265 * @param fallback if the option isn't set, return this instead
    276266 */
    277 Prefs.getValue = function( key, fallback )
     267Prefs.getValue = function(key, fallback)
    278268{
    279269        var val;
    280270
    281         if( Prefs._Defaults[key] == undefined )
    282                 console.warn( "unrecognized preference key '%s'", key );
    283 
    284         var lines = document.cookie.split( ';' );
    285         for( var i=0, len=lines.length; !val && i<len; ++i ) {
    286                 var line = lines[i].trim( );
    287                 var delim = line.indexOf( '=' );
    288                 if( ( delim == key.length ) && line.indexOf( key ) == 0 )
    289                         val = line.substring( delim + 1 );
     271        if (Prefs._Defaults[key] == undefined)
     272                console.warn("unrecognized preference key '%s'", key);
     273
     274        var lines = document.cookie.split(';');
     275        for (var i=0, len=lines.length; !val && i<len; ++i) {
     276                var line = lines[i].trim();
     277                var delim = line.indexOf('=');
     278                if ((delim == key.length) && line.indexOf(key) == 0)
     279                        val = line.substring(delim + 1);
    290280        }
    291281
    292282        // FIXME: we support strings and booleans... add number support too?
    293         if( !val ) val = fallback;
    294         else if( val == 'true' ) val = true;
    295         else if( val == 'false' ) val = false;
     283        if (!val) val = fallback;
     284        else if (val == 'true') val = true;
     285        else if (val == 'false') val = false;
    296286        return val;
    297287};
     
    302292 * @pararm o object to be populated (optional)
    303293 */
    304 Prefs.getClutchPrefs = function( o )
    305 {
    306         if( !o )
     294Prefs.getClutchPrefs = function(o)
     295{
     296        if (!o)
    307297                o = { };
    308         for( var key in Prefs._Defaults )
    309                 o[key] = Prefs.getValue( key, Prefs._Defaults[key] );
     298        for (var key in Prefs._Defaults)
     299                o[key] = Prefs.getValue(key, Prefs._Defaults[key]);
    310300        return o;
    311301};
     
    335325        });
    336326}
     327
     328
     329/**
     330 * http://blog.stevenlevithan.com/archives/parseuri
     331 *
     332 * parseUri 1.2.2
     333 * (c) Steven Levithan <stevenlevithan.com>
     334 * MIT License
     335 */
     336function parseUri (str) {
     337        var     o   = parseUri.options,
     338                m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
     339                uri = {},
     340                i   = 14;
     341
     342        while (i--) uri[o.key[i]] = m[i] || "";
     343
     344        uri[o.q.name] = {};
     345        uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
     346                if ($1) uri[o.q.name][$1] = $2;
     347        });
     348
     349        return uri;
     350};
     351
     352parseUri.options = {
     353        strictMode: false,
     354        key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
     355        q:   {
     356                name:   "queryKey",
     357                parser: /(?:^|&)([^&=]*)=?([^&]*)/g
     358        },
     359        parser: {
     360                strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
     361                loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
     362        }
     363};
  • trunk/web/javascript/dialog.js

    r12096 r12716  
    3030               
    3131                // Observe the buttons
    32                 this._cancel_button.bind('click', {dialog: this}, this.onCancelClicked );
    33                 this._confirm_button.bind('click', {dialog: this}, this.onConfirmClicked );
     32                this._cancel_button.bind('click', {dialog: this}, this.onCancelClicked);
     33                this._confirm_button.bind('click', {dialog: this}, this.onConfirmClicked);
    3434        },
    3535
     
    4444     *--------------------------------------------*/
    4545
    46         hideDialog: function( )
     46        hideDialog: function()
    4747        {
    4848                $('body.dialog_showing').removeClass('dialog_showing');
     
    5757        },
    5858
    59         onCancelClicked: function( event )
     59        onCancelClicked: function(event)
    6060        {
    61                 event.data.dialog.hideDialog( );
     61                event.data.dialog.hideDialog();
    6262        },
    6363
    64         onConfirmClicked: function( event )
     64        onConfirmClicked: function(event)
    6565        {
    6666                var dialog = event.data.dialog;
    67                 eval( dialog._callback_function + "(dialog._callback_data)" );
    68                 dialog.hideDialog( );
     67                eval(dialog._callback_function + "(dialog._callback_data)");
     68                dialog.hideDialog();
    6969        },
    7070
     
    8787                        $('.dialog_container').hide();
    8888                }
    89                 setInnerHTML( this._heading[0], dialog_heading );
    90                 setInnerHTML( this._message[0], dialog_message );
    91                 setInnerHTML( this._cancel_button[0], (cancel_button_label == null) ? 'Cancel' : cancel_button_label );
    92                 setInnerHTML( this._confirm_button[0], confirm_button_label );
     89                setInnerHTML(this._heading[0], dialog_heading);
     90                setInnerHTML(this._message[0], dialog_message);
     91                setInnerHTML(this._cancel_button[0], (cancel_button_label == null) ? 'Cancel' : cancel_button_label);
     92                setInnerHTML(this._confirm_button[0], confirm_button_label);
    9393                this._confirm_button.show();
    9494                this._callback_function = callback_function;
     
    116116                        $('.dialog_container').hide();
    117117                }
    118                 setInnerHTML( this._heading[0], dialog_heading );
    119                 setInnerHTML( this._message[0], dialog_message );
     118                setInnerHTML(this._heading[0], dialog_heading);
     119                setInnerHTML(this._message[0], dialog_message);
    120120                // jquery::hide() doesn't work here in Safari for some odd reason
    121121                this._confirm_button.css('display', 'none');
    122                 setInnerHTML( this._cancel_button[0], cancel_button_label );
     122                setInnerHTML(this._cancel_button[0], cancel_button_label);
    123123                // Just in case
    124124                if (!iPhone && Safari3) {
  • trunk/web/javascript/file-row.js

    r12702 r12716  
    55 */
    66
    7 function FileRow( controller, torrent, i )
     7function FileRow(torrent, i)
    88{
    9         this.initialize( controller, torrent, i );
     9        this.initialize(torrent, i);
    1010}
    1111
    1212FileRow.prototype =
    1313{
    14         initialize: function( controller, torrent, i )
     14        initialize: function(torrent, i)
    1515        {
    1616                this._torrent = torrent;
    1717                this._index = i;
    18                 this.createRow( torrent, i );
     18                this.createRow(torrent, i);
    1919        },
    2020
    21         getTorrent: function( )
     21        getTorrent: function()
    2222        {
    2323                return this._torrent;
    2424        },
    25         getIndex: function( )
     25        getIndex: function()
    2626        {
    2727                return this._index;
     
    3030        readAttributes: function(file)
    3131        {
    32                 if( file.index !== undefined && file.index !== this._index ) {
     32                if (file.index !== undefined && file.index !== this._index) {
    3333                        this._index = file.index;
    3434                        this._dirty = true;
    3535                }
    36                 if( file.bytesCompleted !== undefined && file.bytesCompleted !== this._done ) {
     36                if (file.bytesCompleted !== undefined && file.bytesCompleted !== this._done) {
    3737                        this._done   = file.bytesCompleted;
    3838                        this._dirty = true;
    3939                }
    40                 if( file.length !== undefined && file.length !== this._size ) {
     40                if (file.length !== undefined && file.length !== this._size) {
    4141                        this._size   = file.length;
    4242                        this._dirty = true;
    4343                }
    44                 if( file.priority !== undefined && file.priority !== this._prio ) {
     44                if (file.priority !== undefined && file.priority !== this._prio) {
    4545                        this._prio   = file.priority;
    4646                        this._dirty = true;
    4747                }
    48                 if( file.wanted !== undefined && file.wanted !== this._wanted ) {
     48                if (file.wanted !== undefined && file.wanted !== this._wanted) {
    4949                        this._wanted = file.wanted;
    5050                        this._dirty = true;
     
    5555                var e = this.getElement();
    5656                var c = [ e.classNameConst ];
    57                 if(!this._wanted) { c.push( 'skip' ); }
    58                 if(this.isDone()) { c.push( 'complete' ); }
     57                if (!this._wanted) { c.push('skip'); }
     58                if (this.isDone()) { c.push('complete'); }
    5959                e.className = c.join(' ');
    6060        },
     
    6262                var e = this._priority_control;
    6363                var c = [ e.classNameConst ];
    64                 switch( this._prio ) {
    65                         case 1  : c.push( 'high'   ); break;
    66                         case -1 : c.push( 'low'    ); break;
    67                         default : c.push( 'normal' ); break;
     64                switch(this._prio) {
     65                        case -1 : c.push('low'); break;
     66                        case 1  : c.push('high'); break;
     67                        default : c.push('normal'); break;
    6868                }
    6969                e.className = c.join(' ');
    7070        },
    7171        refreshProgressHTML: function() {
    72                 var pct = 100 * (this._size ? ( this._done / this._size ) : 1.0);
     72                var pct = 100 * (this._size ? (this._done / this._size) : 1.0);
    7373                var c = [ Transmission.fmt.size(this._done),
    7474                          ' of ',
     
    8080        },
    8181        refreshHTML: function() {
    82                 if( this._dirty ) {
     82                if (this._dirty) {
    8383                        this._dirty = false;
    8484                        this.refreshProgressHTML();
     
    8787                }
    8888        },
    89         refresh: function( )
     89        refresh: function()
    9090        {
    91                 var i = this.getIndex( );
    92                 var t = this.getTorrent( );
    93                 this.readAttributes( t._files[i] );
     91                var i = this.getIndex();
     92                var t = this.getTorrent();
     93                this.readAttributes(t._files[i]);
    9494                this.refreshHTML();
    9595        },
     
    102102        },
    103103
    104         createRow: function( torrent, i )
     104        createRow: function(torrent, i)
    105105        {
    106106                var me = this;
     
    115115                var wanted_div = document.createElement('div');
    116116                wanted_div.className = "file_wanted_control";
    117                 $(wanted_div).bind('click',function(e){ me.fireWantedChanged( !me._wanted ); });
     117                $(wanted_div).bind('click',function(){ me.fireWantedChanged(!me._wanted); });
    118118
    119119                var pri_div = document.createElement('div');
     
    123123                        var x = ev.pageX;
    124124                        var e = ev.target;
    125                         while (e !== null) {
     125                        while (e) {
    126126                                x -= e.offsetLeft;
    127127                                e = e.offsetParent;
    128128                        }
    129129                        var prio;
    130                         if(iPhone) {
    131                                 if( x < 8 ) prio = -1;
    132                                 else if( x < 27 ) prio = 0;
     130                        if (iPhone) {
     131                                if (x < 8) prio = -1;
     132                                else if (x < 27) prio = 0;
    133133                                else prio = 1;
    134134                        } else {
    135                                 if( x < 12 ) prio = -1;
    136                                 else if( x < 23 ) prio = 0;
     135                                if (x < 12) prio = -1;
     136                                else if (x < 23) prio = 0;
    137137                                else prio = 1;
    138138                        }
    139                         me.firePriorityChanged( prio );
     139                        me.firePriorityChanged(prio);
    140140                });
    141141
     
    160160        },
    161161
    162         getElement: function( )
     162        getElement: function()
    163163        {
    164164                return this._element;
    165165        },
    166166
    167         fireWantedChanged: function( do_want )
     167        fireWantedChanged: function(do_want)
    168168        {
    169169                $(this).trigger('wantedToggled',[ this, do_want ]);
    170170        },
    171         firePriorityChanged: function( priority )
     171        firePriorityChanged: function(priority)
    172172        {
    173173                $(this).trigger('priorityToggled',[ this, priority ]);
  • trunk/web/javascript/formatter.js

    r11709 r12716  
    3131        return {
    3232
    33                 updateUnits: function( u )
    34                 {
     33                updateUnits: function(u)
     34                {
     35/*
    3536                        speed_K     = u['speed-bytes'];
    3637                        speed_K_str = u['speed-units'][0];
     
    5051                        mem_G_str = u['memory-units'][2];
    5152                        mem_T_str = u['memory-units'][3];
     53*/
    5254                },
    5355
     
    5557                 *   Format a percentage to a string
    5658                 */
    57                 percentString: function( x ) {
    58                         if( x < 10.0 )
    59                                 return x.toTruncFixed( 2 );
    60                         else if( x < 100.0 )
    61                                 return x.toTruncFixed( 1 );
    62                         else
    63                                 return x.toTruncFixed( 0 );
     59                percentString: function(x) {
     60                        if (x < 10.0)
     61                                return x.toTruncFixed(2);
     62                        else if (x < 100.0)
     63                                return x.toTruncFixed(1);
     64                        else
     65                                return x.toTruncFixed(0);
    6466                },
    6567
     
    6769                 *   Format a ratio to a string
    6870                 */
    69                 ratioString: function( x ) {
    70                         if( x ==  -1 )
     71                ratioString: function(x) {
     72                        if (x ==  -1)
    7173                                return "None";
    72                         else if( x == -2 )
     74                        else if (x == -2)
    7375                                return '&infin;';
    7476                        else
    75                                 return this.percentString( x );
     77                                return this.percentString(x);
    7678                },
    7779
     
    8183                 * @return {String} human-readable string
    8284                 */
    83                 mem: function( bytes )
    84                 {
    85                         if( bytes < mem_K )
     85                mem: function(bytes)
     86                {
     87                        if (bytes < mem_K)
    8688                                return [ bytes, mem_B_str ].join(' ');
    8789
     
    8991                        var unit;
    9092
    91                         if( bytes < Math.pow( mem_K, 2 ) )
     93                        if (bytes < Math.pow(mem_K, 2))
    9294                        {
    9395                                convertedSize = bytes / mem_K;
    9496                                unit = mem_K_str;
    9597                        }
    96                         else if( bytes < Math.pow( mem_K, 3 ) )
    97                         {
    98                                 convertedSize = bytes / Math.pow( mem_K, 2 );
     98                        else if (bytes < Math.pow(mem_K, 3))
     99                        {
     100                                convertedSize = bytes / Math.pow(mem_K, 2);
    99101                                unit = mem_M_str;
    100102                        }
    101                         else if( bytes < Math.pow( mem_K, 4 ) )
    102                         {
    103                                 convertedSize = bytes / Math.pow( mem_K, 3 );
     103                        else if (bytes < Math.pow(mem_K, 4))
     104                        {
     105                                convertedSize = bytes / Math.pow(mem_K, 3);
    104106                                unit = mem_G_str;
    105107                        }
    106108                        else
    107109                        {
    108                                 convertedSize = bytes / Math.pow( mem_K, 4 );
     110                                convertedSize = bytes / Math.pow(mem_K, 4);
    109111                                unit = mem_T_str;
    110112                        }
     
    120122                 * @return {String} human-readable string
    121123                 */
    122                 size: function( bytes )
    123                 {
    124                         if( bytes < size_K )
     124                size: function(bytes)
     125                {
     126                        if (bytes < size_K)
    125127                                return [ bytes, size_B_str ].join(' ');
    126128
     
    128130                        var unit;
    129131
    130                         if( bytes < Math.pow( size_K, 2 ) )
     132                        if (bytes < Math.pow(size_K, 2))
    131133                        {
    132134                                convertedSize = bytes / size_K;
    133135                                unit = size_K_str;
    134136                        }
    135                         else if( bytes < Math.pow( size_K, 3 ) )
    136                         {
    137                                 convertedSize = bytes / Math.pow( size_K, 2 );
     137                        else if (bytes < Math.pow(size_K, 3))
     138                        {
     139                                convertedSize = bytes / Math.pow(size_K, 2);
    138140                                unit = size_M_str;
    139141                        }
    140                         else if( bytes < Math.pow( size_K, 4 ) )
    141                         {
    142                                 convertedSize = bytes / Math.pow( size_K, 3 );
     142                        else if (bytes < Math.pow(size_K, 4))
     143                        {
     144                                convertedSize = bytes / Math.pow(size_K, 3);
    143145                                unit = size_G_str;
    144146                        }
    145147                        else
    146148                        {
    147                                 convertedSize = bytes / Math.pow( size_K, 4 );
     149                                convertedSize = bytes / Math.pow(size_K, 4);
    148150                                unit = size_T_str;
    149151                        }
     
    154156                },
    155157
    156                 speedBps: function( Bps )
    157                 {
    158                         return this.speed( this.toKBps( Bps ) );
    159                 },
    160 
    161                 toKBps: function( Bps )
    162                 {
    163                         return Math.floor( Bps / speed_K );
    164                 },
    165 
    166                 speed: function( KBps )
     158                speedBps: function(Bps)
     159                {
     160                        return this.speed(this.toKBps(Bps));
     161                },
     162
     163                toKBps: function(Bps)
     164                {
     165                        return Math.floor(Bps / speed_K);
     166                },
     167
     168                speed: function(KBps)
    167169                {
    168170                        var speed = KBps;
    169171
    170                         if (speed <= 999.95) // 0 KBps to 999.9 K
    171                                 return [ speed.toTruncFixed(1), speed_K_str ].join(' ');
     172                        if (speed <= 999.95) // 0 KBps to 999 K
     173                                return [ speed.toTruncFixed(0), speed_K_str ].join(' ');
    172174
    173175                        speed /= speed_K;
     
    183185                },
    184186
    185                 timeInterval: function( seconds )
     187                timeInterval: function(seconds)
    186188                {
    187189                        var result;
     
    209211                },
    210212
    211                 timestamp: function( seconds )
    212                 {
    213                         if( !seconds )
     213                timestamp: function(seconds)
     214                {
     215                        if (!seconds)
    214216                                return 'N/A';
    215217
     
    224226
    225227                        var dateDiff = now.getDate() - myDate.getDate();
    226                         if(sameYear && sameMonth && Math.abs(dateDiff) <= 1){
    227                                 if(dateDiff == 0){
     228                        if (sameYear && sameMonth && Math.abs(dateDiff) <= 1){
     229                                if (dateDiff == 0){
    228230                                        date = "Today";
    229231                                }
    230                                 else if(dateDiff == 1){
     232                                else if (dateDiff == 1){
    231233                                        date = "Yesterday";
    232234                                }
     
    241243                        var hours = myDate.getHours();
    242244                        var period = "AM";
    243                         if(hours > 12){
     245                        if (hours > 12){
    244246                                hours = hours - 12;
    245247                                period = "PM";
    246248                        }
    247                         if(hours == 0){
     249                        if (hours == 0){
    248250                                hours = 12;
    249251                        }
    250                         if(hours < 10){
     252                        if (hours < 10){
    251253                                hours = "0" + hours;
    252254                        }
    253255                        var minutes = myDate.getMinutes();
    254                         if(minutes < 10){
     256                        if (minutes < 10){
    255257                                minutes = "0" + minutes;
    256258                        }
    257259                        var seconds = myDate.getSeconds();
    258                                 if(seconds < 10){
     260                                if (seconds < 10){
    259261                                        seconds = "0" + seconds;
    260262                        }
     
    265267                },
    266268
    267                 plural: function( i, word )
     269                plural: function(i, word)
    268270                {
    269271                        return [ i, ' ', word, (word==1?'':'s') ].join('');
  • trunk/web/javascript/torrent-row.js

    r12713 r12716  
    1414}
    1515
    16 TorrentRendererHelper.getProgressInfo = function( controller, t )
    17 {
    18         var seed_ratio_limit = t.seedRatioLimit( controller );
     16TorrentRendererHelper.getProgressInfo = function(controller, t)
     17{
     18        var seed_ratio_limit = t.seedRatioLimit(controller);
    1919
    2020        var pct = 0;
    21         if( t.needsMetaData( ) )
     21        if (t.needsMetaData())
    2222                pct = t.getMetadataPercentComplete() * 100;
    23         else if( !t.isDone( ) )
    24                 pct = Math.round( t.getPercentDone() * 100 );
    25         else if( seed_ratio_limit > 0 )
    26                 pct = Math.round( t.getUploadRatio() * 100 / seed_ratio_limit );
     23        else if (!t.isDone())
     24                pct = Math.round(t.getPercentDone() * 100);
     25        else if (seed_ratio_limit > 0)
     26                pct = Math.round(t.getUploadRatio() * 100 / seed_ratio_limit);
    2727        else
    2828                pct = 100;
    2929
    3030        var extra;
    31         if( t.isStopped( ) )
     31        if (t.isStopped())
    3232                extra = 'paused';
    33         else if( t.isSeeding( ) )
     33        else if (t.isSeeding())
    3434                extra = 'seeding';
    35         else if( t.needsMetaData( ) )
     35        else if (t.needsMetaData())
    3636                extra = 'magnet';
    3737        else
     
    4343                incomplete: [ 'torrent_progress_bar', 'incomplete', extra ].join(' ')
    4444        };
    45 }
    46 
    47 TorrentRendererHelper.createProgressbar = function( classes )
    48 {
    49         var complete = document.createElement( 'div' );
     45};
     46
     47TorrentRendererHelper.createProgressbar = function(classes)
     48{
     49        var complete = document.createElement('div');
    5050        complete.className = 'torrent_progress_bar complete';
    51         var incomplete = document.createElement( 'div' );
     51        var incomplete = document.createElement('div');
    5252        incomplete.className = 'torrent_progress_bar incomplete';
    53         var progressbar = document.createElement( 'div' );
     53        var progressbar = document.createElement('div');
    5454        progressbar.className = 'torrent_progress_bar_container ' + classes;
    55         progressbar.appendChild( complete );
    56         progressbar.appendChild( incomplete );
    57         return { 'element': progressbar, 'complete': complete, 'incomplete': incomplete }
    58 }
    59 
    60 TorrentRendererHelper.renderProgressbar = function( controller, t, progressbar )
    61 {
    62         var info = TorrentRendererHelper.getProgressInfo( controller, t );
     55        progressbar.appendChild(complete);
     56        progressbar.appendChild(incomplete);
     57        return { 'element': progressbar, 'complete': complete, 'incomplete': incomplete };
     58};
     59
     60TorrentRendererHelper.renderProgressbar = function(controller, t, progressbar)
     61{
     62        var info = TorrentRendererHelper.getProgressInfo(controller, t);
    6363        var e;
    6464        e = progressbar.complete;
     
    6969        e.className = info.incomplete;
    7070        e.style.display = info.percent>=100 ? 'none' : 'block';
    71 }
    72 
    73 TorrentRendererHelper.formatUL = function( t )
    74 {
    75         return 'UL: ' + Transmission.fmt.speedBps( t.getUploadSpeed( ) );
    76 }
    77 
    78 TorrentRendererHelper.formatDL = function( t )
    79 {
    80         return 'DL: ' + Transmission.fmt.speedBps( t.getDownloadSpeed( ) );
    81 }
     71};
     72
     73TorrentRendererHelper.formatUL = function(t)
     74{
     75        return '&uarr; ' + Transmission.fmt.speedBps(t.getUploadSpeed());
     76};
     77
     78TorrentRendererHelper.formatDL = function(t)
     79{
     80        return '&darr; ' + Transmission.fmt.speedBps(t.getDownloadSpeed());
     81};
    8282
    8383/****
     
    9191TorrentRendererFull.prototype =
    9292{
    93         createRow: function( )
    94         {
    95                 var root = document.createElement( 'li' );
     93        createRow: function()
     94        {
     95                var root = document.createElement('li');
    9696                root.className = 'torrent';
    9797
    98                 var name = document.createElement( 'div' );
     98                var name = document.createElement('div');
    9999                name.className = 'torrent_name';
    100100
    101                 var peers = document.createElement( 'div' );
     101                var peers = document.createElement('div');
    102102                peers.className = 'torrent_peer_details';
    103103
    104                 var progressbar = TorrentRendererHelper.createProgressbar( 'full' );
    105 
    106                 var details = document.createElement( 'div' );
     104                var progressbar = TorrentRendererHelper.createProgressbar('full');
     105
     106                var details = document.createElement('div');
    107107                details.className = 'torrent_progress_details';
    108108
    109                 var image = document.createElement( 'div' );
    110                 var button = document.createElement( 'a' );
    111                 button.appendChild( image );
    112 
    113                 root.appendChild( name );
    114                 root.appendChild( peers );
    115                 root.appendChild( button );
    116                 root.appendChild( progressbar.element );
    117                 root.appendChild( details );
     109                var image = document.createElement('div');
     110                var button = document.createElement('a');
     111                button.appendChild(image);
     112
     113                root.appendChild(name);
     114                root.appendChild(peers);
     115                root.appendChild(button);
     116                root.appendChild(progressbar.element);
     117                root.appendChild(details);
    118118
    119119                root._name_container = name;
     
    127127        },
    128128
    129         getPeerDetails: function( t )
     129        getPeerDetails: function(t)
    130130        {
    131131                var err;
    132                 if(( err = t.getErrorMessage()))
     132                if ((err = t.getErrorMessage()))
    133133                        return err;
    134134
    135                 if( t.isDownloading( ) )
     135                if (t.isDownloading())
    136136                        return [ 'Downloading from',
    137137                                 t.getPeersSendingToUs(),
     
    143143                                 TorrentRendererHelper.formatUL(t) ].join(' ');
    144144
    145                 if( t.isSeeding( ) )
     145                if (t.isSeeding())
    146146                        return [ 'Seeding to',
    147147                                 t.getPeersGettingFromUs(),
     
    152152                                 TorrentRendererHelper.formatUL(t) ].join(' ');
    153153
    154                 if( t.isChecking( ) )
     154                if (t.isChecking())
    155155                        return [ 'Verifying local data (',
    156                                  Transmission.fmt.percentString( 100.0 * t.getRecheckProgress() ),
     156                                 Transmission.fmt.percentString(100.0 * t.getRecheckProgress()),
    157157                                 '% tested)' ].join('');
    158158
    159                 return t.getStateString( );
    160         },
    161 
    162         getProgressDetails: function( controller, t )
    163         {
    164                 if( t.needsMetaData() ) {
     159                return t.getStateString();
     160        },
     161
     162        getProgressDetails: function(controller, t)
     163        {
     164                if (t.needsMetaData()) {
    165165                        var percent = 100 * t.getMetadataPercentComplete();
    166166                        return [ "Magnetized transfer - retrieving metadata (",
    167                                  Transmission.fmt.percentString( percent ),
     167                                 Transmission.fmt.percentString(percent),
    168168                                 "%)" ].join('');
    169169                }
    170170
    171171                var c;
    172                 var sizeWhenDone = t.getSizeWhenDone()
    173                 var totalSize = t.getTotalSize()
    174                 var is_done = ( t.isDone( ) ) || ( t.isSeeding() )
    175 
    176                 if( is_done ) {
    177                         if( totalSize == sizeWhenDone ) // seed: '698.05 MiB'
    178                                 c = [ Transmission.fmt.size( totalSize ) ];
     172                var sizeWhenDone = t.getSizeWhenDone();
     173                var totalSize = t.getTotalSize();
     174                var is_done = t.isDone() || t.isSeeding();
     175
     176                if (is_done) {
     177                        if (totalSize == sizeWhenDone) // seed: '698.05 MiB'
     178                                c = [ Transmission.fmt.size(totalSize) ];
    179179                        else // partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
    180                                 c = [ Transmission.fmt.size( sizeWhenDone ),
     180                                c = [ Transmission.fmt.size(sizeWhenDone),
    181181                                      ' of ',
    182                                       Transmission.fmt.size( t.getTotalSize() ),
     182                                      Transmission.fmt.size(t.getTotalSize()),
    183183                                      ' (', t.getPercentDoneStr(), '%)' ];
    184184                        // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
    185                         c.push( ', uploaded ',
    186                                 Transmission.fmt.size( t.getUploadedEver() ),
     185                        c.push(', uploaded ',
     186                                Transmission.fmt.size(t.getUploadedEver()),
    187187                                ' (Ratio ',
    188                                 Transmission.fmt.ratioString( t.getUploadRatio() ),
    189                                 ')' );
     188                                Transmission.fmt.ratioString(t.getUploadRatio()),
     189                                ')');
    190190                } else { // not done yet
    191                         c = [ Transmission.fmt.size( sizeWhenDone - t.getLeftUntilDone() ),
    192                               ' of ', Transmission.fmt.size( sizeWhenDone ),
     191                        c = [ Transmission.fmt.size(sizeWhenDone - t.getLeftUntilDone()),
     192                              ' of ', Transmission.fmt.size(sizeWhenDone),
    193193                              ' (', t.getPercentDoneStr(), '%)' ];
    194194                }
    195195
    196196                // maybe append eta
    197                 if( !t.isStopped() && ( !is_done || t.seedRatioLimit(controller)>0 ) ) {
     197                if (!t.isStopped() && (!is_done || t.seedRatioLimit(controller)>0)) {
    198198                        c.push(' - ');
    199                         var eta = t.getETA()
    200                         if (eta < 0 || eta >= (999*60*60) /* arbitrary */ )
    201                                 c.push( 'remaining time unknown' );
     199                        var eta = t.getETA();
     200                        if (eta < 0 || eta >= (999*60*60) /* arbitrary */)
     201                                c.push('remaining time unknown');
    202202                        else
    203                                 c.push( Transmission.fmt.timeInterval(t.getETA()),
    204                                         ' remaining' );
     203                                c.push(Transmission.fmt.timeInterval(t.getETA()),
     204                                        ' remaining');
    205205                }
    206206
     
    208208        },
    209209
    210         render: function( controller, t, root )
     210        render: function(controller, t, root)
    211211        {
    212212                // name
    213                 setInnerHTML( root._name_container, t.getName() );
     213                setInnerHTML(root._name_container, t.getName());
    214214
    215215                // progressbar
    216                 TorrentRendererHelper.renderProgressbar( controller, t, root._progressbar );
     216                TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
    217217
    218218                // peer details
     
    220220                var e = root._peer_details_container;
    221221                $(e).toggleClass('error',has_error);
    222                 setInnerHTML( e, this.getPeerDetails( t ) );
     222                setInnerHTML(e, this.getPeerDetails(t));
    223223
    224224                // progress details
    225225                e = root._progress_details_container;
    226                 setInnerHTML( e, this.getProgressDetails( controller, t ) );
     226                setInnerHTML(e, this.getProgressDetails(controller, t));
    227227
    228228                // pause/resume button
    229                 var is_stopped = t.isStopped()
    230                 e = root._pause_resume_button_image
    231                 e.alt = is_stopped ? 'Resume' : 'Pause'
    232                 e.className = is_stopped ? 'torrent_resume' : 'torrent_pause'
     229                var is_stopped = t.isStopped();
     230                e = root._pause_resume_button_image;
     231                e.alt = is_stopped ? 'Resume' : 'Pause';
     232                e.className = is_stopped ? 'torrent_resume' : 'torrent_pause';
    233233        }
    234234};
     
    244244TorrentRendererCompact.prototype =
    245245{
    246         createRow: function( )
    247         {
    248                 var progressbar = TorrentRendererHelper.createProgressbar( 'compact' );
    249 
    250                 var details = document.createElement( 'div' );
     246        createRow: function()
     247        {
     248                var progressbar = TorrentRendererHelper.createProgressbar('compact');
     249
     250                var details = document.createElement('div');
    251251                details.className = 'torrent_peer_details compact';
    252252
    253                 var name = document.createElement( 'div' );
     253                var name = document.createElement('div');
    254254                name.className = 'torrent_name compact';
    255255
    256                 var root = document.createElement( 'li' );
    257                 root.appendChild( progressbar.element );
    258                 root.appendChild( details );
    259                 root.appendChild( name );
     256                var root = document.createElement('li');
     257                root.appendChild(progressbar.element);
     258                root.appendChild(details);
     259                root.appendChild(name);
    260260                root.className = 'torrent compact';
    261261                root._progressbar = progressbar;
     
    265265        },
    266266
    267         getPeerDetails: function( t )
     267        getPeerDetails: function(t)
    268268        {
    269269                var c;
    270                 if(( c = t.getErrorMessage()))
     270                if ((c = t.getErrorMessage()))
    271271                        return c;
    272                 if( t.isDownloading( ) )
     272                if (t.isDownloading())
    273273                        return [ TorrentRendererHelper.formatDL(t),
    274274                                 TorrentRendererHelper.formatUL(t) ].join(' ');
    275                 if( t.isSeeding( ) )
     275                if (t.isSeeding())
    276276                        return TorrentRendererHelper.formatUL(t);
    277                 return t.getStateString( );
    278         },
    279 
    280         render: function( controller, t, root )
     277                return t.getStateString();
     278        },
     279
     280        render: function(controller, t, root)
    281281        {
    282282                // name
    283                 var is_stopped = t.isStopped()
     283                var is_stopped = t.isStopped();
    284284                var e = root._name_container;
    285                 $(e).toggleClass( 'paused', is_stopped );
    286                 setInnerHTML( e, t.getName() );
     285                $(e).toggleClass('paused', is_stopped);
     286                setInnerHTML(e, t.getName());
    287287
    288288                // peer details
    289289                var has_error = t.getError() !== Torrent._ErrNone;
    290290                e = root._details_container;
    291                 $(e).toggleClass('error', has_error );
    292                 setInnerHTML( e, this.getPeerDetails( t ) );
     291                $(e).toggleClass('error', has_error);
     292                setInnerHTML(e, this.getPeerDetails(t));
    293293
    294294                // progressbar
    295                 TorrentRendererHelper.renderProgressbar( controller, t, root._progressbar );
     295                TorrentRendererHelper.renderProgressbar(controller, t, root._progressbar);
    296296        }
    297297};
     
    302302****/
    303303
    304 function TorrentRow( view )
    305 {
    306         this.initialize( view );
     304function TorrentRow(view, controller, torrent, selected)
     305{
     306        this.initialize(view, controller, torrent, selected);
    307307}
    308308TorrentRow.prototype =
    309309{
    310         initialize: function( view ) {
     310        initialize: function(view, controller, torrent, selected) {
    311311                this._view = view;
    312                 this._element = view.createRow( );
    313 
    314         },
    315         getElement: function( ) {
     312                this._element = view.createRow();
     313                this.setTorrent(controller, torrent);
     314                if (selected)
     315                        this.setSelected(selected);
     316                this.render(controller);
     317
     318        },
     319        getElement: function() {
    316320                return this._element;
    317321        },
    318         render: function( controller ) {
    319                 var tor = this.getTorrent( );
    320                 if( tor !== null )
    321                         this._view.render( controller, tor, this.getElement( ) );
    322         },
    323         isSelected: function( ) {
    324                 return this.getElement().className.indexOf('selected') != -1;
    325         },
    326         setSelected: function( flag ) {
    327                 $(this.getElement()).toggleClass( 'selected', flag );
    328         },
    329 
    330         getToggleRunningButton: function( ) {
     322        render: function(controller) {
     323                var tor = this.getTorrent();
     324                if (tor)
     325                        this._view.render(controller, tor, this.getElement());
     326        },
     327        isSelected: function() {
     328                return this.getElement().className.indexOf('selected') !== -1;
     329        },
     330        setSelected: function(flag) {
     331                $(this.getElement()).toggleClass('selected', flag);
     332        },
     333
     334        getToggleRunningButton: function() {
    331335                return this.getElement()._toggle_running_button;
    332336        },
    333337
    334         setVisible: function( visible ) {
    335                 this.getElement().style.display = visible ? 'block' : 'none';
    336 
    337                 if( !visible )
    338                         this.setSelected( false );
    339         },
    340         isVisible: function( visible ) {
    341                 return this.getElement().style.display === 'block';
    342         },
    343 
    344         setTorrent: function( controller, t ) {
    345                 if( this._torrent !== t ) {
    346                         var row = this
     338        setTorrent: function(controller, t) {
     339                if (this._torrent !== t) {
     340                        var row = this;
    347341                        var key = 'dataChanged.torrentRowListener';
    348                         if(this._torrent)
     342                        if (this._torrent)
    349343                                $(this._torrent).unbind(key);
    350                         if((this._torrent = t))
    351                                 $(this._torrent).bind(key,function(){row.render(controller)})
     344                        if ((this._torrent = t))
     345                                $(this._torrent).bind(key,function(){row.render(controller);});
    352346                }
    353347        },
     
    358352                return this.getElement().className.indexOf('even') != -1;
    359353        },
    360         setEven: function( even ) {
    361                 if( this.isEven() != even )
     354        setEven: function(even) {
     355                if (this.isEven() != even)
    362356                        $(this.getElement()).toggleClass('even', even);
    363357        }
  • trunk/web/javascript/torrent.js

    r12703 r12716  
    88
    99
    10 function Torrent(controller, data)
    11 {
    12         this.initialize(controller, data)
     10function Torrent(data)
     11{
     12        this.initialize(data);
    1313}
    1414
     
    2020
    2121// Torrent.fields.status
    22 Torrent._StatusStopped         = 0 
    23 Torrent._StatusCheckWait       = 1
    24 Torrent._StatusCheck           = 2
    25 Torrent._StatusDownloadWait    = 3
    26 Torrent._StatusDownload        = 4
    27 Torrent._StatusSeedWait        = 5
    28 Torrent._StatusSeed            = 6
     22Torrent._StatusStopped         = 0;
     23Torrent._StatusCheckWait       = 1;
     24Torrent._StatusCheck           = 2;
     25Torrent._StatusDownloadWait    = 3;
     26Torrent._StatusDownload        = 4;
     27Torrent._StatusSeedWait        = 5;
     28Torrent._StatusSeed            = 6;
    2929
    3030// Torrent.fields.seedRatioMode
    31 Torrent._RatioUseGlobal        = 0
    32 Torrent._RatioUseLocal         = 1
    33 Torrent._RatioUnlimited        = 2
     31Torrent._RatioUseGlobal        = 0;
     32Torrent._RatioUseLocal         = 1;
     33Torrent._RatioUnlimited        = 2;
    3434
    3535// Torrent.fields.error
    36 Torrent._ErrNone               = 0
    37 Torrent._ErrTrackerWarning     = 1
    38 Torrent._ErrTrackerError       = 2
    39 Torrent._ErrLocalError         = 3
     36Torrent._ErrNone               = 0;
     37Torrent._ErrTrackerWarning     = 1;
     38Torrent._ErrTrackerError       = 2;
     39Torrent._ErrLocalError         = 3;
    4040
    4141// TrackerStats' announceState
    42 Torrent._TrackerInactive       = 0
    43 Torrent._TrackerWaiting        = 1
    44 Torrent._TrackerQueued         = 2
    45 Torrent._TrackerActive         = 3
     42Torrent._TrackerInactive       = 0;
     43Torrent._TrackerWaiting        = 1;
     44Torrent._TrackerQueued         = 2;
     45Torrent._TrackerActive         = 3;
    4646
    4747
    4848// fields whose values never change and are always known
    4949Torrent._StaticFields = [
    50         'hashString', 'id' ]
     50        'hashString', 'id' ];
    5151
    5252// fields whose values never change and are known upon constructon OR
     
    5454Torrent._MetaDataFields = [
    5555        'addedDate', 'comment', 'creator', 'dateCreated',
    56         'isPrivate', 'name', 'totalSize', 'pieceCount', 'pieceSize' ]
     56        'isPrivate', 'name', 'totalSize', 'pieceCount', 'pieceSize' ];
    5757
    5858// torrent fields whose values change all the time
     
    6464        'rateDownload', 'rateUpload', 'recheckProgress', 'seedRatioLimit',
    6565        'seedRatioMode', 'sizeWhenDone', 'status', 'trackerStats',
    66         'uploadedEver', 'uploadRatio', 'webseedsSendingToUs' ]
     66        'uploadedEver', 'uploadRatio', 'webseedsSendingToUs' ];
    6767
    6868/***
     
    7474Torrent.prototype =
    7575{
    76         initialize: function(controller, data)
    77         {
    78                 this.fields = {}
    79                 this._files = []
     76        initialize: function(data)
     77        {
     78                this.fields = {};
     79                this._files = [];
    8080
    8181                // these fields are set in the ctor and never change
    82                 for(var i=0, key; key=Torrent._StaticFields[i]; ++i)
    83                         if(key in data)
    84                                 this.fields[key] = data[key]
    85 
    86                 this.initMetaData(data)
    87                 this._trackerStats = this.buildTrackerStats(data.trackerStats)
    88                 this.refresh(data)
     82                for (var i=0, key; key=Torrent._StaticFields[i]; ++i) {
     83                        if (key in data) {
     84                                this.fields[key] = data[key];
     85                        }
     86                }
     87
     88                this.initMetaData(data);
     89                this._trackerStats = this.buildTrackerStats(data.trackerStats);
     90                this.refresh(data);
    8991        },
    9092
    9193        buildTrackerStats: function(trackerStats) {
    92                 result = []
    93                 for(var i=0, tracker; tracker=trackerStats[i]; ++i) {
    94                         tier = result[tracker.tier] || []
    95                         tier.push(tracker)
    96                         result[tracker.tier] = tier
    97                 }
    98                 return result
     94                var announce = [];
     95                var result = [];
     96                for (var i=0, tracker; tracker=trackerStats[i]; ++i) {
     97                        var tier = result[tracker.tier] || [];
     98                        tier.push(tracker);
     99                        result[tracker.tier] = tier;
     100                        announce.push(tracker.announce);
     101                }
     102                this.fields.collatedTrackers = announce.join('\t');
     103                return result;
    99104        },
    100105
    101106        initMetaData: function(data) {
    102107
    103                 var f = this.fields
     108                var f = this.fields;
     109                var changed = false;
    104110
    105111                // populate the metadata fields
    106                 for(var i=0, key; key=Torrent._MetaDataFields[i]; ++i) {
    107                         if(key in data) {
    108                                 f[key] = data[key]
    109                                 if(key === 'name')
    110                                         f.collatedName = data.name.toLowerCase()
     112                for (var i=0, key; key=Torrent._MetaDataFields[i]; ++i) {
     113                        if (key in data) {
     114                                if (f[key] !== data[key]) {
     115                                        f[key] = data[key];
     116                                        if (key === 'name')
     117                                                f.collatedName = data.name.toLowerCase();
     118                                        changed = true;
     119                                }
    111120                        }
    112121                }
    113122
    114123                // populate the files array
    115                 if(data.files) {
    116                         for(var i=0, row; row=data.files[i]; ++i) {
     124                if (data.files) {
     125                        for (var i=0, row; row=data.files[i]; ++i) {
    117126                                this._files[i] = {
    118127                                        'index': i,
     
    120129                                        'length': row.length,
    121130                                        'name': row.name
     131                                };
     132                        }
     133                }
     134
     135                return changed;
     136        },
     137
     138        refreshMetaData: function(data)
     139        {
     140                var changed = this.initMetaData(data);
     141                if (changed)
     142                        this.fireDataChanged();
     143                return changed;
     144        },
     145
     146        refresh: function(data)
     147        {
     148                var changed = false;
     149
     150                // FIXME: unnecessary coupling... this should be handled by transmission.js
     151                if (this.needsMetaData() && (data.metadataPercentComplete >= 1))
     152                        changed |= transmission.refreshMetaData([ this.getId() ]);
     153
     154                var f = this.fields;
     155
     156                // refresh the dynamic fields
     157                for (var i=0, key; key=Torrent._DynamicFields[i]; ++i) {
     158                        if (key in data) {
     159                                if (f[key] !== data[key]) {
     160                                        f[key] = data[key];
     161                                        changed = true;
    122162                                }
    123163                        }
    124164                }
    125         },
    126 
    127         refreshMetaData: function(data)
    128         {
    129                 this.initMetaData(data)
    130                 this.fireDataChanged()
    131         },
    132 
    133         refresh: function(data)
    134         {
    135                 // FIXME: unnecessary coupling... this should be handled by transmission.js
    136                 if(this.needsMetaData() && (data.metadataPercentComplete >= 1))
    137                         transmission.refreshMetaData([ this.getId() ])
    138 
    139                 var f = this.fields
    140 
    141                 // refresh the dynamic fields
    142                 for(var i=0, key; key=Torrent._DynamicFields[i]; ++i)
    143                         if(key in data)
    144                                 f[key] = data[key]
    145 
    146                 this._trackerStats = this.buildTrackerStats(data.trackerStats)
     165
     166                this._trackerStats = this.buildTrackerStats(data.trackerStats);
    147167
    148168                if (data.fileStats)
    149                         this.refreshFiles(data)
    150 
    151                 this.fireDataChanged()
     169                        changed |= this.refreshFiles(data);
     170
     171                if (changed)
     172                        this.fireDataChanged();
    152173        },
    153174
    154175        refreshFiles: function(data) {
    155                 for(var i=0; i<data.fileStats.length; ++i) {
    156                         var src = data.fileStats[i]
    157                         var tgt = this._files[i]
    158                         if(!tgt)
    159                                 tgt = this._files[i] = { }
    160                         tgt.wanted = src.wanted
    161                         tgt.priority = src.priority
    162                         tgt.bytesCompleted = src.bytesCompleted
    163                 }
     176                var changed = false;
     177                for (var i=0; i<data.fileStats.length; ++i) {
     178                        var src = data.fileStats[i];
     179                        var tgt = this._files[i];
     180                        if (!tgt) {
     181                                changed = true;
     182                                tgt = this._files[i] = { };
     183                        }
     184                        if (tgt.wanted !== src.wanted) {
     185                                tgt.wanted = src.wanted;
     186                                changed = true;
     187                        }
     188                        if (tgt.priority !== src.priority) {
     189                                tgt.priority = src.priority;
     190                                changed = true;
     191                        }
     192                        if (tgt.bytesCompleted !== src.bytesCompleted) {
     193                                tgt.bytesCompleted = src.bytesCompleted;
     194                                changed = true;
     195                        }
     196                }
     197                return changed;
    164198        },
    165199
    166200        fireDataChanged: function()
    167201        {
    168                 $(this).trigger('dataChanged',[])
     202                $(this).trigger('dataChanged');
    169203        },
    170204
     
    174208
    175209        // simple accessors
    176         getCollatedName: function() { return this.fields.collatedName },
    177         getComment: function() { return this.fields.comment },
    178         getCreator: function() { return this.fields.creator },
    179         getDateAdded: function() { return this.fields.addedDate },
    180         getDateCreated: function() { return this.fields.dateCreated },
    181         getDesiredAvailable: function() { return this.fields.desiredAvailable },
    182         getDownloadDir: function() { return this.fields.downloadDir },
    183         getDownloadSpeed: function() { return this.fields.rateDownload },
    184         getDownloadedEver: function() { return this.fields.downloadedEver },
    185         getError: function() { return this.fields.error },
    186         getErrorString: function() { return this.fields.errorString },
    187         getETA: function() { return this.fields.eta },
    188         getHashString: function() { return this.fields.hashString },
    189         getHaveValid: function() { return this.fields.haveValid },
    190         getHave: function() { return this.getHaveValid() + this.fields.haveUnchecked },
    191         getId: function() { return this.fields.id },
    192         getLeftUntilDone: function() { return this.fields.leftUntilDone },
    193         getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete },
    194         getName: function() { return this.fields.name },
    195         getPeers: function() { return this.fields.peers },
    196         getPeersConnected: function() { return this.fields.peersConnected },
    197         getPeersGettingFromUs: function() { return this.fields.peersGettingFromUs },
    198         getPeersSendingToUs: function() { return this.fields.peersSendingToUs },
    199         getPieceCount: function() { return this.fields.pieceCount },
    200         getPieceCount: function() { return this.fields.pieceCount },
    201         getPieceSize: function() { return this.fields.pieceSize },
    202         getPrivateFlag: function() { return this.fields.isPrivate },
    203         getQueuePosition: function() { return this.fields.queuePosition },
    204         getRecheckProgress: function() { return this.fields.recheckProgress },
    205         getSeedRatioLimit: function() { return this.fields.seedRatioLimit },
    206         getSeedRatioMode: function() { return this.fields.seedRatioMode },
    207         getSizeWhenDone: function() { return this.fields.sizeWhenDone },
    208         getStatus: function() { return this.fields.status },
    209         getTotalSize: function() { return this.fields.totalSize },
    210         getUploadSpeed: function() { return this.fields.rateUpload },
    211         getUploadRatio: function() { return this.fields.uploadRatio },
    212         getUploadedEver: function() { return this.fields.uploadedEver },
    213         getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs },
    214         isFinished: function() { return this.fields.isFinished },
     210        getCollatedName: function() { return this.fields.collatedName; },
     211        getCollatedTrackers: function() { return this.fields.collatedTrackers; },
     212        getComment: function() { return this.fields.comment; },
     213        getCreator: function() { return this.fields.creator; },
     214        getDateAdded: function() { return this.fields.addedDate; },
     215        getDateCreated: function() { return this.fields.dateCreated; },
     216        getDesiredAvailable: function() { return this.fields.desiredAvailable; },
     217        getDownloadDir: function() { return this.fields.downloadDir; },
     218        getDownloadSpeed: function() { return this.fields.rateDownload; },
     219        getDownloadedEver: function() { return this.fields.downloadedEver; },
     220        getError: function() { return this.fields.error; },
     221        getErrorString: function() { return this.fields.errorString; },
     222        getETA: function() { return this.fields.eta; },
     223        getHashString: function() { return this.fields.hashString; },
     224        getHaveValid: function() { return this.fields.haveValid; },
     225        getHave: function() { return this.getHaveValid() + this.fields.haveUnchecked; },
     226        getId: function() { return this.fields.id; },
     227        getLeftUntilDone: function() { return this.fields.leftUntilDone; },
     228        getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete; },
     229        getName: function() { return this.fields.name; },
     230        getPeers: function() { return this.fields.peers; },
     231        getPeersConnected: function() { return this.fields.peersConnected; },
     232        getPeersGettingFromUs: function() { return this.fields.peersGettingFromUs; },
     233        getPeersSendingToUs: function() { return this.fields.peersSendingToUs; },
     234        getPieceCount: function() { return this.fields.pieceCount; },
     235        getPieceCount: function() { return this.fields.pieceCount; },
     236        getPieceSize: function() { return this.fields.pieceSize; },
     237        getPrivateFlag: function() { return this.fields.isPrivate; },
     238        getQueuePosition: function() { return this.fields.queuePosition; },
     239        getRecheckProgress: function() { return this.fields.recheckProgress; },
     240        getSeedRatioLimit: function() { return this.fields.seedRatioLimit; },
     241        getSeedRatioMode: function() { return this.fields.seedRatioMode; },
     242        getSizeWhenDone: function() { return this.fields.sizeWhenDone; },
     243        getStatus: function() { return this.fields.status; },
     244        getTotalSize: function() { return this.fields.totalSize; },
     245        getUploadSpeed: function() { return this.fields.rateUpload; },
     246        getUploadRatio: function() { return this.fields.uploadRatio; },
     247        getUploadedEver: function() { return this.fields.uploadedEver; },
     248        getWebseedsSendingToUs: function() { return this.fields.webseedsSendingToUs; },
     249        isFinished: function() { return this.fields.isFinished; },
    215250
    216251        // derived accessors
    217         isSeeding: function() { return this.getStatus() === Torrent._StatusSeed },
    218         isStopped: function() { return this.getStatus() === Torrent._StatusStopped },
    219         isChecking: function() { return this.getStatus() === Torrent._StatusCheck },
    220         isDownloading: function() { return this.getStatus() === Torrent._StatusDownload },
    221         isDone: function() { return this.getLeftUntilDone() < 1 },
    222         needsMetaData: function(){ return this.getMetadataPercentComplete() < 1 },
    223         getActivity: function() { return this.getDownloadSpeed() + this.getUploadSpeed() },
    224         getPercentDoneStr: function() { return Transmission.fmt.percentString(100*this.getPercentDone()) },
     252        isSeeding: function() { return this.getStatus() === Torrent._StatusSeed; },
     253        isStopped: function() { return this.getStatus() === Torrent._StatusStopped; },
     254        isChecking: function() { return this.getStatus() === Torrent._StatusCheck; },
     255        isDownloading: function() { return this.getStatus() === Torrent._StatusDownload; },
     256        isDone: function() { return this.getLeftUntilDone() < 1; },
     257        needsMetaData: function(){ return this.getMetadataPercentComplete() < 1; },
     258        getActivity: function() { return this.getDownloadSpeed() + this.getUploadSpeed(); },
     259        getPercentDoneStr: function() { return Transmission.fmt.percentString(100*this.getPercentDone()); },
    225260        getPercentDone: function() {
    226                 var finalSize = this.getSizeWhenDone()
    227                 if(!finalSize) return 1.0
    228                 var left = this.getLeftUntilDone()
    229                 if(!left) return 1.0
    230                 return (finalSize - left) / finalSize
     261                var finalSize = this.getSizeWhenDone();
     262                if (!finalSize) return 1.0;
     263                var left = this.getLeftUntilDone();
     264                if (!left) return 1.0;
     265                return (finalSize - left) / finalSize;
    231266        },
    232267        getStateString: function() {
    233268                switch(this.getStatus()) {
    234                         case Torrent._StatusStopped:        return this.isFinished() ? 'Seeding complete' : 'Paused'
    235                         case Torrent._StatusCheckWait:      return 'Queued for verification'
    236                         case Torrent._StatusCheck:          return 'Verifying local data'
    237                         case Torrent._StatusDownloadWait:   return 'Queued for download'
    238                         case Torrent._StatusDownload:       return 'Downloading'
    239                         case Torrent._StatusSeedWait:       return 'Queued for seeding'
    240                         case Torrent._StatusSeed:           return 'Seeding'
    241                         default:                            return 'error'
    242                 }
    243         },
    244         trackerStats: function() { return this._trackerStats },
     269                        case Torrent._StatusStopped:        return this.isFinished() ? 'Seeding complete' : 'Paused';
     270                        case Torrent._StatusCheckWait:      return 'Queued for verification';
     271                        case Torrent._StatusCheck:          return 'Verifying local data';
     272                        case Torrent._StatusDownloadWait:   return 'Queued for download';
     273                        case Torrent._StatusDownload:       return 'Downloading';
     274                        case Torrent._StatusSeedWait:       return 'Queued for seeding';
     275                        case Torrent._StatusSeed:           return 'Seeding';
     276                        default:                            return 'error';
     277                }
     278        },
     279        trackerStats: function() { return this._trackerStats; },
    245280        seedRatioLimit: function(controller){
    246281                switch(this.getSeedRatioMode()) {
    247                         case Torrent._RatioUseGlobal: return controller.seedRatioLimit()
    248                         case Torrent._RatioUseLocal:  return this.getSeedRatioLimit()
    249                         default:                      return -1
     282                        case Torrent._RatioUseGlobal: return controller.seedRatioLimit();
     283                        case Torrent._RatioUseLocal:  return this.getSeedRatioLimit();
     284                        default:                      return -1;
    250285                }
    251286        },
    252287        getErrorMessage: function() {
    253                 var str = this.getErrorString()
     288                var str = this.getErrorString();
    254289                switch(this.getError()) {
    255290                        case Torrent._ErrTrackerWarning:
    256                                 return 'Tracker returned a warning: ' + str
     291                                return 'Tracker returned a warning: ' + str;
    257292                        case Torrent._ErrTrackerError:
    258                                 return 'Tracker returned an error: ' + str
     293                                return 'Tracker returned an error: ' + str;
    259294                        case Torrent._ErrLocalError:
    260                                 return 'Error: ' + str
     295                                return 'Error: ' + str;
    261296                        default:
    262                                 return null
     297                                return null;
    263298                }
    264299        },
     
    267302        *****
    268303        ****/
     304
     305        testState: function(state)
     306        {
     307                var s = this.getStatus();
     308
     309                switch(state)
     310                {
     311                        case Prefs._FilterActive:
     312                                return this.getPeersGettingFromUs() > 0
     313                                    || this.getPeersSendingToUs() > 0
     314                                    || this.getWebseedsSendingToUs() > 0
     315                                    || this.isChecking();
     316                        case Prefs._FilterSeeding:
     317                                return (s === Torrent._StatusSeed)
     318                                    || (s === Torrent._StatusSeedWait);
     319                        case Prefs._FilterDownloading:
     320                                return (s === Torrent._StatusDownload)
     321                                    || (s === Torrent._StatusDownloadWait);
     322                        case Prefs._FilterPaused:
     323                                return this.isStopped();
     324                        case Prefs._FilterFinished:
     325                                return this.isFinished();
     326                        default:
     327                                return true;
     328                }
     329        },
    269330
    270331        /**
     
    273334         * @return true if it passes the test, false if it fails
    274335         */
    275         test: function(filter, search)
    276         {
    277                 var pass = false
    278                 var s = this.getStatus()
    279 
    280                 switch(filter)
    281                 {
    282                         case Prefs._FilterActive:
    283                                 pass = this.getPeersGettingFromUs() > 0
    284                                     || this.getPeersSendingToUs() > 0
    285                                     || this.getWebseedsSendingToUs() > 0
    286                                     || this.isChecking()
    287                                 break
    288                         case Prefs._FilterSeeding:
    289                                 pass = (s === Torrent._StatusSeed) || (s === Torrent._StatusSeedWait)
    290                                 break
    291                         case Prefs._FilterDownloading:
    292                                 pass = (s === Torrent._StatusDownload) || (s === Torrent._StatusDownloadWait)
    293                                 break
    294                         case Prefs._FilterPaused:
    295                                 pass = this.isStopped()
    296                                 break
    297                         case Prefs._FilterFinished:
    298                                 pass = this.isFinished()
    299                                 break
    300                         default:
    301                                 pass = true
    302                                 break
    303                 }
    304 
    305                 if(!pass)
    306                         return false
    307 
    308                 if(!search || !search.length)
    309                         return pass
    310 
    311                 return this.getCollatedName().indexOf(search.toLowerCase()) !== -1
     336        test: function(state, search, tracker)
     337        {
     338                // flter by state...
     339                var pass = this.testState(state);
     340
     341                // maybe filter by text...
     342                if (pass && search && search.length)
     343                        pass = this.getCollatedName().indexOf(search.toLowerCase()) !== -1;
     344
     345                // maybe filter by tracker...
     346                if (pass && tracker && tracker.length)
     347                        pass = this.getCollatedTrackers().indexOf(tracker) !== -1;
     348
     349                return pass;
    312350        }
    313 }
     351};
    314352
    315353
     
    322360Torrent.compareById = function(ta, tb)
    323361{
    324         return ta.getId() - tb.getId()
    325 }
     362        return ta.getId() - tb.getId();
     363};
    326364Torrent.compareByName = function(ta, tb)
    327365{
    328366        return ta.getCollatedName().compareTo(tb.getCollatedName())
    329             || Torrent.compareById(ta, tb)
    330 }
     367            || Torrent.compareById(ta, tb);
     368};
    331369Torrent.compareByQueue = function(ta, tb)
    332370{
    333         return ta.getQueuePosition() - tb.getQueuePosition()
    334 }
     371        return ta.getQueuePosition() - tb.getQueuePosition();
     372};
    335373Torrent.compareByAge = function(ta, tb)
    336374{
    337         var a = ta.getDateAdded()
    338         var b = tb.getDateAdded()
    339         return (b - a) || Torrent.compareByQueue(ta, tb)
    340 }
     375        var a = ta.getDateAdded();
     376        var b = tb.getDateAdded();
     377        return (b - a) || Torrent.compareByQueue(ta, tb);
     378};
    341379Torrent.compareByState = function(ta, tb)
    342380{
    343         var a = ta.getStatus()
    344         var b = tb.getStatus()
    345         return (b - a) || Torrent.compareByQueue(ta, tb)
    346 }
     381        var a = ta.getStatus();
     382        var b = tb.getStatus();
     383        return (b - a) || Torrent.compareByQueue(ta, tb);
     384};
    347385Torrent.compareByActivity = function(ta, tb)
    348386{
    349         var a = ta.getActivity()
    350         var b = tb.getActivity()
    351         return (a - b) || Torrent.compareByState(ta, tb)
    352 }
    353 Torrent.compareByRatio = function(a, b)
    354 {
    355         var a = Math.ratio(ta.getUploadedEver(), ta.getDownloadedEver())
    356         var b = Math.ratio(tb.getUploadedEver(), tb.getDownloadedEver())
    357         return (a - b) || Torrent.compareByState(ta, tb)
    358 }
     387        var a = ta.getActivity();
     388        var b = tb.getActivity();
     389        return (b - a) || Torrent.compareByState(ta, tb);
     390};
     391Torrent.compareByRatio = function(ta, tb)
     392{
     393        var a = Math.ratio(ta.getUploadedEver(), ta.getDownloadedEver());
     394        var b = Math.ratio(tb.getUploadedEver(), tb.getDownloadedEver());
     395        return (a - b) || Torrent.compareByState(ta, tb);
     396};
    359397Torrent.compareByProgress = function(ta, tb)
    360398{
    361         var a = ta.getPercentDone()
    362         var b = tb.getPercentDone()
    363         return (a - b) || Torrent.compareByRatio(ta, tb)
    364 }
     399        var a = ta.getPercentDone();
     400        var b = tb.getPercentDone();
     401        return (a - b) || Torrent.compareByRatio(ta, tb);
     402};
    365403
    366404/**
     
    374412        {
    375413                case Prefs._SortByActivity:
    376                         torrents.sort(this.compareByActivity)
    377                         break
     414                        torrents.sort(this.compareByActivity);
     415                        break;
    378416                case Prefs._SortByAge:
    379                         torrents.sort(this.compareByAge)
    380                         break
     417                        torrents.sort(this.compareByAge);
     418                        break;
    381419                case Prefs._SortByQueue:
    382                         torrents.sort(this.compareByQueue)
    383                         break
     420                        torrents.sort(this.compareByQueue);
     421                        break;
    384422                case Prefs._SortByProgress:
    385                         torrents.sort(this.compareByProgress)
    386                         break
     423                        torrents.sort(this.compareByProgress);
     424                        break;
    387425                case Prefs._SortByState:
    388                         torrents.sort(this.compareByState)
    389                         break
    390                 case Prefs._SortByName:
    391                         torrents.sort(this.compareByName)
    392                         break
     426                        torrents.sort(this.compareByState);
     427                        break;
    393428                case Prefs._SortByRatio:
    394                         torrents.sort(this.compareByRatio)
    395                         break
     429                        torrents.sort(this.compareByRatio);
     430                        break;
    396431                default:
    397                         console.warn("unknown sort method: " + sortMethod)
    398                         break
     432                        torrents.sort(this.compareByName);
     433                        break;
    399434        }
    400435
    401         if(sortDirection === Prefs._SortDescending)
    402                 torrents.reverse()
    403 
    404         return torrents
    405 }
     436        if (sortDirection === Prefs._SortDescending)
     437                torrents.reverse();
     438
     439        return torrents;
     440};
  • trunk/web/javascript/transmission.js

    r12715 r12716  
    77 */
    88
    9 function Transmission(){
     9function Transmission()
     10{
    1011        this.initialize();
    1112}
     
    1314Transmission.prototype =
    1415{
    15         /*--------------------------------------------
    16          *
    17          *  C O N S T R U C T O R
    18          *
    19          *--------------------------------------------*/
     16        /****
     17        *****
     18        *****  STARTUP
     19        *****
     20        ****/
    2021
    2122        initialize: function()
     
    3031
    3132                // Initialize the clutch preferences
    32                 Prefs.getClutchPrefs( this );
     33                Prefs.getClutchPrefs(this);
    3334
    3435                this.preloadImages();
     
    3738                var tr = this;
    3839                $(".numberinput").forceNumeric();
    39                 $('#pause_all_link').bind('click', function(e){ tr.stopAllClicked(e); });
    40                 $('#resume_all_link').bind('click', function(e){ tr.startAllClicked(e); });
    41                 $('#pause_selected_link').bind('click', function(e){ tr.stopSelectedClicked(e); } );
    42                 $('#resume_selected_link').bind('click', function(e){ tr.startSelectedClicked(e); });
    43                 $('#remove_link').bind('click',  function(e){ tr.removeClicked(e); });
    44                 $('#filter_all_link').parent().bind('click', function(e){ tr.showAllClicked(e); });
    45                 $('#filter_active_link').parent().bind('click', function(e){ tr.showActiveClicked(e); });
    46                 $('#filter_downloading_link').parent().bind('click', function(e){ tr.showDownloadingClicked(e); });
    47                 $('#filter_seeding_link').parent().bind('click', function(e){ tr.showSeedingClicked(e); });
    48                 $('#filter_paused_link').parent().bind('click', function(e){ tr.showPausedClicked(e); });
    49                 $('#filter_finished_link').parent().bind('click', function(e){ tr.showFinishedClicked(e); });
    50                 $('#prefs_save_button').bind('click', function(e) { tr.savePrefsClicked(e); return false;});
    51                 $('#prefs_cancel_button').bind('click', function(e){ tr.cancelPrefsClicked(e); return false; });
    52                 $('#block_update_button').bind('click', function(e){ tr.blocklistUpdateClicked(e); return false; });
    53                 $('#stats_close_button').bind('click', function(e){ tr.closeStatsClicked(e); return false; });
    54                 $('.inspector_tab').bind('click', function(e){ tr.inspectorTabClicked(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                 $('#prefs_tab_peers_tab').click(function(e){ changeTab(this, 'prefs_tab_peers') });
    64                 $('#prefs_tab_network_tab').click(function(e){ changeTab(this, 'prefs_tab_network');});
    65                 $('#torrent_upload_form').submit(function(){ $('#upload_confirm_button').click(); return false; });
    66                 $('#torrent_container').bind('dragover', function(e){ return tr.dragenter(e); });
    67                 $('#torrent_container').bind('dragenter', function(e){ return tr.dragenter(e); });
    68                 $('#torrent_container').bind('drop', function(e){ return tr.drop(e); });
     40                $('#pause_all_link').click(function(e) { tr.stopAllClicked(e); });
     41                $('#resume_all_link').click(function(e) { tr.startAllClicked(e); });
     42                $('#pause_selected_link').click(function(e) { tr.stopSelectedClicked(e); });
     43                $('#resume_selected_link').click(function(e) { tr.startSelectedClicked(e); });
     44                $('#remove_link').click(function(e) { tr.removeClicked(e); });
     45                $('#prefs_save_button').click(function(e) { tr.savePrefsClicked(e); return false;});
     46                $('#prefs_cancel_button').click(function(e) { tr.cancelPrefsClicked(e); return false; });
     47                $('#block_update_button').click(function(e) { tr.blocklistUpdateClicked(e); return false; });
     48                $('#stats_close_button').click(function(e) { tr.closeStatsClicked(e); return false; });
     49                $('.inspector_tab').click(function(e) { tr.inspectorTabClicked(e, this); });
     50                $('#files_select_all').live('click', function(e) { tr.filesSelectAllClicked(e, this); });
     51                $('#files_deselect_all').live('click', function(e) { tr.filesDeselectAllClicked(e, this); });
     52                $('#open_link').click(function(e) { tr.openTorrentClicked(e); });
     53                $('#upload_confirm_button').click(function(e) { tr.confirmUploadClicked(e); return false;});
     54                $('#upload_cancel_button').click(function(e) { tr.cancelUploadClicked(e); return false; });
     55                $('#turtle_button').click(function() { tr.toggleTurtleClicked(); return false; });
     56                $('#compact-button').click(function() { tr.toggleCompactClicked(); return false; });
     57                $('#prefs_tab_general_tab').click(function() { changeTab(this, 'prefs_tab_general'); });
     58                $('#prefs_tab_speed_tab').click(function() { changeTab(this, 'prefs_tab_speed'); });
     59                $('#prefs_tab_peers_tab').click(function() { changeTab(this, 'prefs_tab_peers'); });
     60                $('#prefs_tab_network_tab').click(function() { changeTab(this, 'prefs_tab_network');});
     61                $('#torrent_upload_form').submit(function() { $('#upload_confirm_button').click(); return false; });
     62                $('#torrent_container').bind('dragover', function(e) { return tr.dragenter(e); });
     63                $('#torrent_container').bind('dragenter', function(e) { return tr.dragenter(e); });
     64                $('#torrent_container').bind('drop', function(e) { return tr.drop(e); });
    6965                // tell jQuery to copy the dataTransfer property from events over if it exists
    7066                jQuery.event.props.push("dataTransfer");
    7167
    72                 $('#torrent_upload_form').submit(function(){ $('#upload_confirm_button').click(); return false; });
     68                $('#torrent_upload_form').submit(function() { $('#upload_confirm_button').click(); return false; });
    7369
    7470                if (iPhone) {
    75                         $('#inspector_close').bind('click', function(e){ tr.hideInspector(); });
    76                         $('#preferences_link').bind('click', function(e){ tr.releaseClutchPreferencesButton(e); });
     71                        $('#inspector_close').bind('click', function() { tr.hideInspector(); });
     72                        $('#preferences_link').bind('click', function(e) { tr.releaseClutchPreferencesButton(e); });
    7773                } else {
    78                         $(document).bind('keydown',  function(e){ tr.keyDown(e); });
    79                         $('#torrent_container').bind('click', function(e){ tr.deselectAll( true ); });
    80                         $('#filter_toggle_link').bind('click', function(e){ tr.toggleFilterClicked(e); });
    81                         $('#inspector_link').bind('click', function(e){ tr.toggleInspectorClicked(e); });
     74                        $(document).bind('keydown',  function(e) { tr.keyDown(e); });
     75                        $('#torrent_container').click(function() { tr.deselectAll(true); });
     76                        $('#inspector_link').click(function(e) { tr.toggleInspectorClicked(e); });
    8277
    8378                        this.setupSearchBox();
     
    9287                this._inspector_trackers_list  = $('#inspector_trackers_list')[0];
    9388                this._inspector_tab_files      = $('#inspector_tab_files')[0];
    94                 this._toolbar_buttons          = $('#torrent_global_menu ul li');
    95                 this._toolbar_pause_button     = $('li#pause_selected')[0];
    96                 this._toolbar_pause_all_button = $('li#pause_all')[0];
    97                 this._toolbar_start_button     = $('li#resume_selected')[0];
    98                 this._toolbar_start_all_button = $('li#resume_all')[0];
    99                 this._toolbar_remove_button    = $('li#remove')[0];
     89                this._toolbar_buttons          = $('#toolbar ul li');
     90                this._toolbar_pause_button     = $('#toolbar #pause_selected')[0];
     91                this._toolbar_pause_all_button = $('#toolbar #pause_all')[0];
     92                this._toolbar_start_button     = $('#toolbar #resume_selected')[0];
     93                this._toolbar_start_all_button = $('#toolbar #resume_all')[0];
     94                this._toolbar_remove_button    = $('#toolbar #remove')[0];
    10095                this._context_pause_button     = $('li#context_pause_selected')[0];
    10196                this._context_start_button     = $('li#context_resume_selected')[0];
     
    132127
    133128                // Setup the prefs gui
    134                 this.initializeSettings( );
     129                this.initializeSettings();
    135130
    136131                // Get preferences & torrents from the daemon
    137                 var tr = this;
    138132                var async = false;
    139                 this.loadDaemonPrefs( async );
    140                 this.loadDaemonStats( async );
     133                this.loadDaemonPrefs(async);
     134                this.loadDaemonStats(async);
    141135                this.initializeAllTorrents();
    142136
    143                 this.togglePeriodicRefresh( true );
    144                 this.togglePeriodicSessionRefresh( true );
    145         },
    146 
    147         loadDaemonPrefs: function( async ){
    148                 var tr = this;
    149                 this.remote.loadDaemonPrefs( function(data){
     137                this.togglePeriodicRefresh(true);
     138                this.togglePeriodicSessionRefresh(true);
     139
     140                this.filterSetup();
     141        },
     142
     143        loadDaemonPrefs: function(async) {
     144                var tr = this;
     145                this.remote.loadDaemonPrefs(function(data) {
    150146                        var o = data.arguments;
    151                         Prefs.getClutchPrefs( o );
    152                         tr.updatePrefs( o );
    153                 }, async );
    154         },
    155 
    156         loadDaemonStats: function( async ){
    157                 var tr = this;
    158                 this.remote.loadDaemonStats( function(data){
     147                        Prefs.getClutchPrefs(o);
     148                        tr.updatePrefs(o);
     149                }, async);
     150        },
     151
     152        loadDaemonStats: function(async) {
     153                var tr = this;
     154                this.remote.loadDaemonStats(function(data) {
    159155                        var o = data.arguments;
    160                         tr.updateStats( o );
    161                 }, async );
    162         },
    163         checkPort: function( async ){
    164                 $('#port_test').text('checking ...')
    165                 var tr = this;
    166                 this.remote.checkPort( function(data){
     156                        tr.updateStats(o);
     157                }, async);
     158        },
     159        checkPort: function(async) {
     160                $('#port_test').text('checking ...');
     161                var tr = this;
     162                this.remote.checkPort(function(data) {
    167163                        var o = data.arguments;
    168                         tr.updatePortStatus( o );
    169                 }, async );             
     164                        tr.updatePortStatus(o);
     165                }, async);             
    170166        },
    171167
     
    200196        },
    201197        loadImages: function() {
    202                 for( var i=0, row; row=arguments[i]; ++i )
     198                for (var i=0, row; row=arguments[i]; ++i)
    203199                        jQuery("<img>").attr("src", row);
    204         },
    205 
    206         setCompactMode: function( is_compact )
    207         {
    208                 this.torrentRenderer = is_compact ? new TorrentRendererCompact( )
    209                                                   : new TorrentRendererFull( );
    210                 $('ul.torrent_list li').remove();
    211                 this._rows = [];
    212                 this.refilter();
    213200        },
    214201
     
    216203         * Load the clutch prefs and init the GUI according to those prefs
    217204         */
    218         initializeSettings: function( )
    219         {
    220                 Prefs.getClutchPrefs( this );
     205        initializeSettings: function()
     206        {
     207                Prefs.getClutchPrefs(this);
    221208
    222209                // iPhone conditions in the section allow us to not
     
    225212                // transmenu refs out of that too.
    226213
    227                 $('#filter_' + this[Prefs._FilterMode] + '_link').parent().addClass('selected');
    228 
    229                 if (!iPhone) $('#sort_by_' + this[Prefs._SortMethod] ).selectMenuItem();
    230 
    231                 if (!iPhone && ( this[Prefs._SortDirection] == Prefs._SortDescending ) )
     214                if (!iPhone) $('#sort_by_' + this[Prefs._SortMethod]).selectMenuItem();
     215
     216                if (!iPhone && (this[Prefs._SortDirection] == Prefs._SortDescending))
    232217                        $('#reverse_sort_order').selectMenuItem();
    233218
    234                 if( this[Prefs._ShowFilter] )
    235                         this.showFilter( );
    236 
    237                 if( !iPhone && this[Prefs._ShowInspector] )
    238                         this.showInspector( );
    239 
    240                 if( !iPhone && this[Prefs._CompactDisplayState] )
    241                         $('#compact_view').selectMenuItem();
    242 
    243                 this.setCompactMode( this[Prefs._CompactDisplayState] );
     219                if (!iPhone && this[Prefs._ShowInspector])
     220                        this.showInspector();
     221
     222                this.initCompactMode();
    244223        },
    245224
     
    251230                var tr = this;
    252231                var search_box = $('#torrent_search');
    253                 search_box.bind('keyup click', function(event) {
    254                         tr.setSearch(this.value);
    255                 });
     232                search_box.bind('keyup click', function() {tr.setSearch(this.value);});
    256233                if (!$.browser.safari)
    257234                {
    258235                        search_box.addClass('blur');
    259236                        search_box[0].value = 'Filter';
    260                         search_box.bind('blur', function(event) {
     237                        search_box.bind('blur', function() {
    261238                                if (this.value == '') {
    262239                                        $(this).addClass('blur');
     
    264241                                        tr.setSearch(null);
    265242                                }
    266                         }).bind('focus', function(event) {
     243                        }).bind('focus', function() {
    267244                                if ($(this).is('.blur')) {
    268245                                        this.value = '';
     
    273250        },
    274251
    275         contextStopSelected: function( ) {
    276                 this.stopSelectedTorrents( );
    277         },
    278         contextStartSelected: function( ) {
    279                 this.startSelectedTorrents( false );
    280         },
    281         contextStartNowSelected: function( ) {
    282                 this.startSelectedTorrents( true );
    283         },
    284         contextRemoveSelected: function( ) {
    285                 this.removeSelectedTorrents( );
    286         },
    287         contextRemoveDataSelected: function( ) {
    288                 this.removeSelectedTorrentsAndData( );
    289         },
    290         contextVerifySelected: function( ) {
    291                 this.verifySelectedTorrents( );
    292         },
    293         contextReannounceSelected: function( ) {
    294                 this.reannounceSelectedTorrents( );
    295         },
    296         contextToggleInspector: function( ) {
    297                 this.toggleInspector( );
    298         },
    299         contextSelectAll: function( ) {
    300                 this.selectAll( true );
    301         },
    302         contextDeselectAll: function( ) {
    303                 this.deselectAll( true );
    304         },
    305 
    306         // Queue
    307 
    308         contextMoveUp: function( ) {
    309                 this.moveUp( );
    310         },
    311         contextMoveDown: function( ) {
    312                 this.moveDown( );
    313         },
    314         contextMoveTop: function( ) {
    315                 this.moveTop( );
    316         },
    317         contextMoveBottom: function( ) {
    318                 this.moveBottom( );
    319         },
    320 
    321252        /*
    322253         * Create the torrent right-click menu
     
    325256                var tr = this;
    326257                var bindings = {
    327                         context_pause_selected:       function(e){ tr.contextStopSelected(e); },
    328                         context_resume_selected:      function(e){ tr.contextStartSelected(e); },
    329                         context_resume_now_selected:  function(e){ tr.contextStartNowSelected(e); },
    330                         context_remove:               function(e){ tr.contextRemoveSelected(e); },
    331                         context_removedata:           function(e){ tr.contextRemoveDataSelected(e); },
    332                         context_verify:               function(e){ tr.contextVerifySelected(e); },
    333                         context_reannounce:           function(e){ tr.contextReannounceSelected(e); },
    334                         context_toggle_inspector:     function(e){ tr.contextToggleInspector(e); },
    335                         context_select_all:           function(e){ tr.contextSelectAll(e); },
    336                         context_deselect_all:         function(e){ tr.contextDeselectAll(e); },
    337                         context_move_top:             function(e){ tr.contextMoveTop(e); },
    338                         context_move_up:              function(e){ tr.contextMoveUp(e); },
    339                         context_move_down:            function(e){ tr.contextMoveDown(e); },
    340                         context_move_bottom:          function(e){ tr.contextMoveBottom(e); }
     258                        context_pause_selected:       function() { tr.stopSelectedTorrents(); },
     259                        context_resume_selected:      function() { tr.startSelectedTorrents(false); },
     260                        context_resume_now_selected:  function() { tr.startSelectedTorrents(true); },
     261                        context_remove:               function() { tr.removeSelectedTorrents(); },
     262                        context_removedata:           function() { tr.removeSelectedTorrentsAndData(); },
     263                        context_verify:               function() { tr.verifySelectedTorrents(); },
     264                        context_reannounce:           function() { tr.reannounceSelectedTorrents(); },
     265                        context_toggle_inspector:     function() { tr.toggleInspector(); },
     266                        context_select_all:           function() { tr.selectAll(); },
     267                        context_deselect_all:         function() { tr.deselectAll(); },
     268                        context_move_top:             function() { tr.moveTop(); },
     269                        context_move_up:              function() { tr.moveUp(); },
     270                        context_move_down:            function() { tr.moveDown(); },
     271                        context_move_bottom:          function() { tr.moveBottom(); }
    341272                };
    342273
     
    354285                        onContextMenu:     function(e) {
    355286                                var closest_row = $(e.target).closest('.torrent')[0];
    356                                 for( var i=0, row; row = tr._rows[i]; ++i ) {
    357                                         if( row.getElement() === closest_row ) {
    358                                                 tr.setSelectedRow( row );
     287                                for (var i=0, row; row = tr._rows[i]; ++i) {
     288                                        if (row.getElement() === closest_row) {
     289                                                tr.setSelectedRow(row);
    359290                                                break;
    360291                                        }
     
    373304                        selected_char: '&#x2714;',
    374305                        direction: 'up',
    375                         onClick: function(e){ return tr.processSettingsMenuEvent(e); }
     306                        onClick: function(e) { return tr.processSettingsMenuEvent(e); }
    376307                });
    377308
     
    382313
    383314        initTurtleDropDowns: function() {
    384                 var i, out, hour, mins, start, end, value, content;
     315                var i, hour, mins, start, end, value, content;
    385316                // Build the list of times
    386                 out = "";
    387317                start = $('#turtle_start_time')[0];
    388318                end = $('#turtle_end_time')[0];
    389319                for (i = 0; i < 24 * 4; i++) {
    390                         hour = parseInt(i / 4);
     320                        hour = parseInt(i / 4, 10);
    391321                        mins = ((i % 4) * 15);
    392322
     
    398328        },
    399329
    400         /*--------------------------------------------
    401          *
    402          *  U T I L I T I E S
    403          *
    404          *--------------------------------------------*/
     330        /****
     331        *****
     332        *****  UTILITIES
     333        *****
     334        ****/
    405335
    406336        getAllTorrents: function()
    407337        {
    408338                var torrents = [];
    409                 for(var key in this._torrents)
     339                for (var key in this._torrents)
    410340                  torrents.push(this._torrents[key]);
    411341                return torrents;
     
    414344        getVisibleTorrents: function()
    415345        {
    416                 var torrents = [ ];
    417                 for( var i=0, row; row=this._rows[i]; ++i )
    418                         if( row.isVisible( ) )
    419                                 torrents.push( row.getTorrent( ) );
     346                var torrents = [];
     347                for (var i=0, row; row=this._rows[i]; ++i)
     348                        torrents.push(row.getTorrent());
    420349                return torrents;
    421350        },
    422351
    423         getSelectedRows: function()
    424         {
    425                 var s = [ ];
    426 
    427                 for( var i=0, row; row=this._rows[i]; ++i )
    428                         if( row.isSelected( ) )
    429                                 s.push( row );
    430 
    431                 return s;
    432         },
    433 
    434         getSelectedTorrents: function()
    435         {
    436                 var s = this.getSelectedRows( );
    437 
    438                 for( var i=0, row; row=s[i]; ++i )
    439                         s[i] = s[i].getTorrent();
    440 
    441                 return s;
    442         },
    443 
    444         getVisibleRows: function()
    445         {
    446                 var rows = [ ];
    447                 for( var i=0, row; row=this._rows[i]; ++i )
    448                         if( row.isVisible( ) )
    449                                 rows.push( row );
    450                 return rows;
    451         },
    452 
    453         getRowIndex: function( rows, row )
    454         {
    455                 for( var i=0, r; r=rows[i]; ++i )
    456                         if( r === row )
    457                                 return i;
    458                 return null;
    459         },
    460 
    461         setPref: function( key, val )
    462         {
    463                 this[key] = val;
    464                 Prefs.setValue( key, val );
    465         },
    466 
    467352        scrollToRow: function(row)
    468353        {
    469                 if( iPhone ) // FIXME: why?
    470                         return
    471 
    472                 var list = $('#torrent_container')
    473                 var scrollTop = list.scrollTop()
    474                 var innerHeight = list.innerHeight()
    475 
    476                 var e = $(row.getElement())
    477                 var offsetTop = e[0].offsetTop
    478                 var offsetHeight = e.outerHeight()
    479 
    480                 if( offsetTop < scrollTop )
    481                         list.scrollTop( offsetTop );
    482                 else if( innerHeight + scrollTop < offsetTop + offsetHeight )
    483                         list.scrollTop( offsetTop + offsetHeight - innerHeight );
    484         },
    485 
    486         seedRatioLimit: function(){
    487                 if(this._prefs && this._prefs['seedRatioLimited'])
     354                if (iPhone) // FIXME: why?
     355                        return;
     356
     357                var list = $('#torrent_container');
     358                var scrollTop = list.scrollTop();
     359                var innerHeight = list.innerHeight();
     360
     361                var e = $(row.getElement());
     362                var offsetTop = e[0].offsetTop;
     363                var offsetHeight = e.outerHeight();
     364
     365                if (offsetTop < scrollTop)
     366                        list.scrollTop(offsetTop);
     367                else if (innerHeight + scrollTop < offsetTop + offsetHeight)
     368                        list.scrollTop(offsetTop + offsetHeight - innerHeight);
     369        },
     370
     371        seedRatioLimit: function() {
     372                if (this._prefs && this._prefs['seedRatioLimited'])
    488373                        return this._prefs['seedRatioLimit'];
    489374                else
     
    491376        },
    492377
    493         /*--------------------------------------------
    494          *
    495          *  S E L E C T I O N
    496          *
    497          *--------------------------------------------*/
    498 
    499         setSelectedRow: function( row ) {
    500                 var rows = this.getSelectedRows( );
    501                 for( var i=0, r; r=rows[i]; ++i )
    502                         this.deselectRow( r );
    503                 this.selectRow( row );
    504         },
    505 
    506         selectRow: function( row ) {
    507                 row.setSelected( true );
     378        setPref: function(key, val)
     379        {
     380                this[key] = val;
     381                Prefs.setValue(key, val);
     382        },
     383
     384        /****
     385        *****
     386        *****  SELECTION
     387        *****
     388        ****/
     389
     390        getSelectedRows: function() {
     391                var s = [];
     392                for (var i=0, row; row=this._rows[i]; ++i)
     393                        if (row.isSelected())
     394                                s.push(row);
     395                return s;
     396        },
     397
     398        getSelectedTorrents: function() {
     399                var s = this.getSelectedRows();
     400                for (var i=0, row; row=s[i]; ++i)
     401                        s[i] = s[i].getTorrent();
     402                return s;
     403        },
     404
     405        getSelectedTorrentIds: function() {
     406                var s = [];
     407                for (var i=0, row; row=this._rows[i]; ++i)
     408                        if (row.isSelected())
     409                                s.push(row.getTorrent().getId());
     410                return s;
     411        },
     412
     413        setSelectedRow: function(row) {
     414                var rows = this.getSelectedRows();
     415                for (var i=0, r; r=rows[i]; ++i)
     416                        this.deselectRow(r);
     417                this.selectRow(row);
     418        },
     419
     420        selectRow: function(row) {
     421                row.setSelected(true);
    508422                this.callSelectionChangedSoon();
    509423        },
    510424
    511         deselectRow: function( row ) {
    512                 row.setSelected( false );
     425        deselectRow: function(row) {
     426                row.setSelected(false);
    513427                this.callSelectionChangedSoon();
    514428        },
    515429
    516         selectAll: function( ) {
    517                 var tr = this;
    518                 for( var i=0, row; row=tr._rows[i]; ++i )
    519                         tr.selectRow( row );
     430        selectAll: function() {
     431                for (var i=0, row; row=this._rows[i]; ++i)
     432                        this.selectRow(row);
    520433                this.callSelectionChangedSoon();
    521434        },
    522         deselectAll: function( ) {
    523                 for( var i=0, row; row=this._rows[i]; ++i )
    524                         this.deselectRow( row );
     435        deselectAll: function() {
     436                for (var i=0, row; row=this._rows[i]; ++i)
     437                        this.deselectRow(row);
    525438                this.callSelectionChangedSoon();
    526                 this._last_row_clicked = null;
    527         },
    528 
    529         /*
    530          * Select a range from this torrent to the last clicked torrent
    531          */
    532         selectRange: function( row )
    533         {
    534                 if( this._last_row_clicked === null )
    535                 {
    536                         this.selectRow( row );
    537                 }
    538                 else // select the range between the prevous & current
    539                 {
    540                         var rows = this.getVisibleRows( );
    541                         var i = this.getRowIndex( rows, this._last_row_clicked );
    542                         var end = this.getRowIndex( rows, row );
    543                         var step = i < end ? 1 : -1;
    544                         for( ; i!=end; i+=step )
    545                                 this.selectRow( this._rows[i] );
    546                         this.selectRow( this._rows[i] );
    547                 }
    548 
    549                 this.callSelectionChangedSoon( );
     439                delete this._last_torrent_clicked;
     440        },
     441
     442        /* Select a range from this torrent to the last clicked torrent */
     443        selectRange: function(row)
     444        {
     445                if (!this._last_torrent_clicked) {
     446                        this.selectRow(row);
     447                } else { // select the range between the prevous & current
     448
     449                        var prev = null;
     450                        var next = null;
     451                        for (var i=0, r; r=this._rows[i]; ++i) {
     452                                if (r.getTorrent().getId() === this._last_torrent_clicked)
     453                                        prev = i;
     454                                if (r === row)
     455                                        next = i;
     456                        }
     457                        if ((prev!==null) && (next!==null)) {
     458                                var min = Math.min(prev, next);
     459                                var max = Math.max(prev, next);
     460                                for (i=min; i<=max; ++i)
     461                                        this.selectRow(this._rows[i]);
     462                        }
     463                }
     464
     465                //this._last_row_clicked = row
     466                this.callSelectionChangedSoon();
    550467        },
    551468
     
    555472                this.updateInspector();
    556473                this.updateSelectedData();
    557                 this.selectionChangedTimer = null;
     474
     475                clearTimeout(this.selectionChangedTimer);
     476                delete this.selectionChangedTimer;
    558477        },
    559478
    560479        callSelectionChangedSoon: function()
    561480        {
    562                 if( this.selectionChangedTimer === null )
    563                         this.selectionChangedTimer = setTimeout(function(o) { o.selectionChanged(); }, 200, this);
     481                if (!this.selectionChangedTimer)
     482                {
     483                        var tr = this;
     484                        this.selectionChangedTimer = setTimeout(function() {tr.selectionChanged();},200);
     485                }
    564486        },
    565487
     
    575497        keyDown: function(ev)
    576498        {
    577                 var up = ev.keyCode === 38 // up key pressed
    578                 var dn = ev.keyCode === 40 // down key pressed
    579 
    580                 if(up || dn)
     499                var up = ev.keyCode === 38; // up key pressed
     500                var dn = ev.keyCode === 40; // down key pressed
     501
     502                if (up || dn)
    581503                {
    582                         var rows = this.getVisibleRows()
     504                        var rows = this._rows;
    583505
    584506                        // find the first selected row 
    585                         for(var i=0, row; row=rows[i]; ++i)
    586                                 if(row.isSelected())
    587                                         break
    588 
    589                         if(i == rows.length) // no selection yet
    590                                 i = 0
    591                         else if(dn)
    592                                 i = (i+1) % rows.length
    593                         else if(up)
    594                                 i = (i || rows.length) - 1
    595 
    596                         this.setSelectedRow(rows[i])
    597                         this.scrollToRow(rows[i])
     507                        for (var i=0, row; row=rows[i]; ++i)
     508                                if (row.isSelected())
     509                                        break;
     510
     511                        if (i == rows.length) // no selection yet
     512                                i = 0;
     513                        else if (dn)
     514                                i = (i+1) % rows.length;
     515                        else if (up)
     516                                i = (i || rows.length) - 1;
     517
     518                        this.setSelectedRow(rows[i]);
     519                        this.scrollToRow(rows[i]);
    598520                }
    599521        },
     
    604526        },
    605527
    606         stopAllClicked: function( event ) {
    607                 var tr = this;
    608                 if( tr.isButtonEnabled( event ) ) {
    609                         tr.stopAllTorrents( );
    610                         tr.hideiPhoneAddressbar( );
    611                 }
    612         },
    613 
    614         stopSelectedClicked: function( event ) {
    615                 var tr = this;
    616                 if( tr.isButtonEnabled( event ) ) {
    617                         tr.stopSelectedTorrents( );
    618                         tr.hideiPhoneAddressbar( );
    619                 }
    620         },
    621 
    622         startAllClicked: function( event ) {
    623                 var tr = this;
    624                 if( tr.isButtonEnabled( event ) ) {
    625                         tr.startAllTorrents( );
    626                         tr.hideiPhoneAddressbar( );
    627                 }
    628         },
    629 
    630         startSelectedClicked: function( event ) {
    631                 var tr = this;
    632                 if( tr.isButtonEnabled( event ) ) {
    633                         tr.startSelectedTorrents( false );
    634                         tr.hideiPhoneAddressbar( );
    635                 }
    636         },
    637 
    638         openTorrentClicked: function( event ) {
    639                 var tr = this;
    640                 if( tr.isButtonEnabled( event ) ) {
     528        stopAllClicked: function(event) {
     529                if (this.isButtonEnabled(event)) {
     530                        this.stopAllTorrents();
     531                        this.hideiPhoneAddressbar();
     532                }
     533        },
     534
     535        stopSelectedClicked: function(event) {
     536                if (this.isButtonEnabled(event)) {
     537                        this.stopSelectedTorrents();
     538                        this.hideiPhoneAddressbar();
     539                }
     540        },
     541
     542        startAllClicked: function(event) {
     543                if (this.isButtonEnabled(event)) {
     544                        this.startAllTorrents();
     545                        this.hideiPhoneAddressbar();
     546                }
     547        },
     548
     549        startSelectedClicked: function(event) {
     550                if (this.isButtonEnabled(event)) {
     551                        this.startSelectedTorrents(false);
     552                        this.hideiPhoneAddressbar();
     553                }
     554        },
     555
     556        openTorrentClicked: function(event) {
     557                if (this.isButtonEnabled(event)) {
    641558                        $('body').addClass('open_showing');
    642                         tr.uploadTorrentFile( );
    643                 }
    644                 tr.updateButtonStates();
    645         },
    646 
    647         dragenter: function( event ) {
    648                 if( event.dataTransfer && event.dataTransfer.types ) {
     559                        this.uploadTorrentFile();
     560                        this.updateButtonStates();
     561                }
     562        },
     563
     564        dragenter: function(event) {
     565                if (event.dataTransfer && event.dataTransfer.types) {
    649566                        var types = ["text/uri-list", "text/plain"];
    650                         for( var i = 0; i < types.length; ++i ) {
    651                                 if( event.dataTransfer.types.contains(types[i]) ) {
     567                        for (var i = 0; i < types.length; ++i) {
     568                                if (event.dataTransfer.types.contains(types[i])) {
    652569                                        // it would be better to actually look at the links here;
    653570                                        // sadly, (at least with Firefox,) trying would throw.
     
    665582        },
    666583
    667         drop: function( event ) {
    668                 if( !event.dataTransfer || !event.dataTransfer.types ) {
     584        drop: function(ev) {
     585                if (!ev.dataTransfer || !ev.dataTransfer.types) {
    669586                        return true;
    670587                }
    671                 event.preventDefault();
     588                ev.preventDefault();
    672589                var uris = null;
    673590                var types = ["text/uri-list", "text/plain"];
    674                 for( var i = 0; i < types.length; ++i ) {
    675                         if( event.dataTransfer.types.contains(types[i]) ) {
    676                                 uris = event.dataTransfer.getData( types[i] ).split("\n");
     591                for (var i = 0; i < types.length; ++i) {
     592                        if (ev.dataTransfer.types.contains(types[i])) {
     593                                uris = ev.dataTransfer.getData(types[i]).split("\n");
    677594                                break;
    678595                        }
    679596                }
    680597                var paused = $('#prefs_form #auto_start')[0].checked;
    681                 for( i = 0; i < uris.length; ++i ) {
     598                for (i = 0; i < uris.length; ++i) {
    682599                        var uri = uris[i];
    683                         if( /^#/.test(uri) ) {
     600                        if (/^#/.test(uri)) {
    684601                                // lines which start with "#" are comments
    685602                                continue;
    686603                        }
    687                         if( /^[a-z-]+:/i.test(uri) ) {
     604                        if (/^[a-z-]+:/i.test(uri)) {
    688605                                // close enough to a url
    689                                 this.remote.addTorrentByUrl( uri, paused );
     606                                this.remote.addTorrentByUrl(uri, paused);
    690607                        }
    691608                }
     
    693610        },
    694611
    695         hideUploadDialog: function( ) {
     612        hideUploadDialog: function() {
    696613                $('body.open_showing').removeClass('open_showing');
    697614                if (!iPhone && Safari3) {
     
    704621        },
    705622
    706         cancelUploadClicked: function(event) {
    707                 this.hideUploadDialog( );
    708         },
    709 
    710         confirmUploadClicked: function(event) {
    711                 this.uploadTorrentFile( true );
    712                 this.hideUploadDialog( );
    713         },
    714 
    715         cancelPrefsClicked: function(event) {
    716                 this.hidePrefsDialog( );
    717         },
    718 
    719         savePrefsClicked: function(event)
     623        cancelUploadClicked: function() {
     624                this.hideUploadDialog();
     625        },
     626
     627        confirmUploadClicked: function() {
     628                this.uploadTorrentFile(true);
     629                this.hideUploadDialog();
     630        },
     631
     632        cancelPrefsClicked: function() {
     633                this.hidePrefsDialog();
     634        },
     635
     636        savePrefsClicked: function()
    720637        {
    721638               
    722639                // handle the clutch prefs locally
    723640                var tr = this;
    724                 var rate = parseInt( $('#prefs_form #refresh_rate')[0].value );
    725                 if( rate != tr[Prefs._RefreshRate] ) {
    726                         tr.setPref( Prefs._RefreshRate, rate );
    727                         tr.togglePeriodicRefresh( false );
    728                         tr.togglePeriodicRefresh( true );
    729                 }
    730 
    731                 var up_bytes        = parseInt( $('#prefs_form #upload_rate'  )[0].value );
    732                 var dn_bytes        = parseInt( $('#prefs_form #download_rate')[0].value );
    733                 var turtle_up_bytes = parseInt( $('#prefs_form #turtle_upload_rate'  )[0].value );
    734                 var turtle_dn_bytes = parseInt( $('#prefs_form #turtle_download_rate')[0].value );
     641                var rate = parseInt ($('#prefs_form #refresh_rate')[0].value, 10);
     642                if (rate != tr[Prefs._RefreshRate]) {
     643                        tr.setPref (Prefs._RefreshRate, rate);
     644                        tr.togglePeriodicRefresh (true);
     645                }
     646
     647                var up_bytes        = parseInt($('#prefs_form #upload_rate'       )[0].value, 10);
     648                var dn_bytes        = parseInt($('#prefs_form #download_rate'     )[0].value, 10);
     649                var turtle_up_bytes = parseInt($('#prefs_form #turtle_upload_rate')[0].value, 10);
     650                var turtle_dn_bytes = parseInt($('#prefs_form #turtle_download_rate')[0].value, 10);
    735651
    736652                // pass the new prefs upstream to the RPC server
    737653                var o = { };
    738654                o[RPC._StartAddedTorrent]    = $('#prefs_form #auto_start')[0].checked;
    739                 o[RPC._PeerPort]             = parseInt( $('#prefs_form #port')[0].value );
     655                o[RPC._PeerPort]             = parseInt($('#prefs_form #port')[0].value, 10);
    740656                o[RPC._UpSpeedLimit]         = up_bytes;
    741657                o[RPC._DownSpeedLimit]       = dn_bytes;
    742658                o[RPC._DownloadDir]          = $('#prefs_form #download_location')[0].value;
    743                 o[RPC._UpSpeedLimited]       = $('#prefs_form #limit_upload'  )[0].checked;
     659                o[RPC._UpSpeedLimited]       = $('#prefs_form #limit_upload')[0].checked;
    744660                o[RPC._DownSpeedLimited]     = $('#prefs_form #limit_download')[0].checked;
    745661                o[RPC._Encryption]           = $('#prefs_form #encryption')[0].checked
     
    749665                o[RPC._TurtleUpSpeedLimit]   = turtle_up_bytes;
    750666                o[RPC._TurtleTimeEnabled]    = $('#prefs_form #turtle_schedule')[0].checked;
    751                 o[RPC._TurtleTimeBegin]      = parseInt( $('#prefs_form #turtle_start_time').val() );
    752                 o[RPC._TurtleTimeEnd]        = parseInt( $('#prefs_form #turtle_end_time').val() );
    753                 o[RPC._TurtleTimeDay]        = parseInt( $('#prefs_form #turtle_days').val() );
    754 
    755 
    756                 o[RPC._PeerLimitGlobal]      = parseInt( $('#prefs_form #conn_global').val() );
    757                 o[RPC._PeerLimitPerTorrent]  = parseInt( $('#prefs_form #conn_torrent').val() );
     667                o[RPC._TurtleTimeBegin]      = parseInt($('#prefs_form #turtle_start_time').val(), 10);
     668                o[RPC._TurtleTimeEnd]        = parseInt($('#prefs_form #turtle_end_time').val(), 10);
     669                o[RPC._TurtleTimeDay]        = parseInt($('#prefs_form #turtle_days').val(), 10);
     670
     671
     672                o[RPC._PeerLimitGlobal]      = parseInt($('#prefs_form #conn_global').val(), 10);
     673                o[RPC._PeerLimitPerTorrent]  = parseInt($('#prefs_form #conn_torrent').val(), 10);
    758674                o[RPC._PexEnabled]           = $('#prefs_form #conn_pex')[0].checked;
    759675                o[RPC._DhtEnabled]           = $('#prefs_form #conn_dht')[0].checked;
     
    761677                o[RPC._BlocklistEnabled]     = $('#prefs_form #block_enable')[0].checked;
    762678                o[RPC._BlocklistURL]         = $('#prefs_form #block_url').val();
    763                 o[RPC._UtpEnabled]                      = $('#prefs_form #network_utp')[0].checked;
    764                 o[RPC._PeerPortRandom]          = $('#prefs_form #port_rand')[0].checked;
     679                o[RPC._UtpEnabled]          = $('#prefs_form #network_utp')[0].checked;
     680                o[RPC._PeerPortRandom]      = $('#prefs_form #port_rand')[0].checked;
    765681                o[RPC._PortForwardingEnabled]= $('#prefs_form #port_forward')[0].checked;
    766682
    767        
    768 
    769                 tr.remote.savePrefs( o );
    770 
    771                 tr.hidePrefsDialog( );
    772         },
    773         blocklistUpdateClicked: function(event){
     683                tr.remote.savePrefs(o);
     684
     685                tr.hidePrefsDialog();
     686        },
     687        blocklistUpdateClicked: function() {
    774688                var tr = this;
    775689                tr.remote.updateBlocklist();   
    776690        },
    777691
    778         closeStatsClicked: function(event) {
    779                 this.hideStatsDialog( );
    780         },
    781 
    782         removeClicked: function( event ) {
    783                 var tr = this;
    784                 if( tr.isButtonEnabled( event ) ) {
    785                         tr.removeSelectedTorrents( );
    786                         tr.hideiPhoneAddressbar( );
    787                 }
    788         },
    789 
    790         toggleInspectorClicked: function( event ) {
    791                 var tr = this;
    792                 if( tr.isButtonEnabled( event ) )
    793                         tr.toggleInspector( );
    794         },
    795 
    796         inspectorTabClicked: function(event, tab) {
    797                 if (iPhone) event.stopPropagation();
    798 
    799                 // Select the clicked tab, unselect the others,
    800                 // and display the appropriate info
    801                 var tab_ids = $(tab).parent('#inspector_tabs').find('.inspector_tab').map(
    802                         function() { return $(this).attr('id'); }
    803                 );
    804                 for( var i=0, row; row=tab_ids[i]; ++i ) {
    805                         if (tab.id == row) {
    806                                 $('#'+row).addClass('selected');
    807                                 $('#'+row+'_container').show();
    808                         } else {
    809                                 $('#'+row).removeClass('selected');
    810                                 $('#'+row+'_container').hide();
    811                         }
    812                 }
     692        closeStatsClicked: function() {
     693                this.hideStatsDialog();
     694        },
     695
     696        removeClicked: function(ev) {
     697                var tr = this;
     698                if (tr.isButtonEnabled(ev)) {
     699                        tr.removeSelectedTorrents();
     700                        tr.hideiPhoneAddressbar();
     701                }
     702        },
     703
     704        toggleInspectorClicked: function(ev) {
     705                var tr = this;
     706                if (tr.isButtonEnabled(ev))
     707                        tr.toggleInspector();
     708        },
     709
     710        inspectorTabClicked: function(ev, tab) {
     711                if (iPhone) ev.stopPropagation();
     712
     713                // select this tab and deselect the others
     714                $(tab).addClass('selected').siblings().removeClass('selected');
     715
     716                // show this tab and hide the others
     717                $('#'+tab.id+'_container').show().siblings('.inspector_container').hide();
     718
    813719                this.hideiPhoneAddressbar();
    814 
    815720                this.updatePeersLists();
    816721                this.updateTrackersLists();
     
    818723        },
    819724
    820         filesSelectAllClicked: function(event) {
     725        filesSelectAllClicked: function() {
    821726                var t = this._files_torrent;
    822                 if (t != null)
     727                if (t)
    823728                        this.toggleFilesWantedDisplay(t, true);
    824729        },
    825         filesDeselectAllClicked: function(event) {
     730        filesDeselectAllClicked: function() {
    826731                var t = this._files_torrent;
    827                 if (t != null)
     732                if (t)
    828733                        this.toggleFilesWantedDisplay(t, false);
    829734        },
    830735        toggleFilesWantedDisplay: function(torrent, wanted) {
    831736                var rows = [ ];
    832                 for( var i=0, row; row=this._files[i]; ++i )
    833                         if( row.isEditable() && (torrent._files[i].wanted !== wanted) )
    834                                 rows.push( row );
    835                 if( rows.length > 1 ) {
     737                for (var i=0, row; row=this._files[i]; ++i)
     738                        if (row.isEditable() && (torrent._files[i].wanted !== wanted))
     739                                rows.push(row);
     740                if (rows.length > 1) {
    836741                        var command = wanted ? 'files-wanted' : 'files-unwanted';
    837                         this.changeFileCommand( command, rows );
    838                 }
    839         },
    840 
    841         toggleFilterClicked: function(event) {
    842                 if (this.isButtonEnabled(event))
    843                         this.toggleFilter();
    844         },
    845         setFilter: function( mode )
    846         {
    847                 // update the radiobuttons
    848                 var c;
    849                 switch( mode ) {
    850                         case Prefs._FilterAll:         c = '#filter_all_link'; break;
    851                         case Prefs._FilterActive:      c = '#filter_active_link'; break;
    852                         case Prefs._FilterSeeding:     c = '#filter_seeding_link'; break;
    853                         case Prefs._FilterDownloading: c = '#filter_downloading_link'; break;
    854                         case Prefs._FilterPaused:      c = '#filter_paused_link'; break;
    855                         case Prefs._FilterFinished:    c = '#filter_finished_link'; break;
    856                 }
    857                 $(c).parent().siblings().removeClass('selected');
    858                 $(c).parent().addClass('selected');
    859 
    860                 // do the filtering
    861                 this.setPref( Prefs._FilterMode, mode );
    862                 this.refilter( );
    863         },
    864         showAllClicked: function( event ) {
    865                 this.setFilter( Prefs._FilterAll );
    866         },
    867         showActiveClicked: function( event ) {
    868                 this.setFilter( Prefs._FilterActive );
    869         },
    870         showDownloadingClicked: function( event ) {
    871                 this.setFilter( Prefs._FilterDownloading );
    872         },
    873         showSeedingClicked: function(event) {
    874                 this.setFilter( Prefs._FilterSeeding );
    875         },
    876         showPausedClicked: function(event) {
    877                 this.setFilter( Prefs._FilterPaused );
    878         },
    879         showFinishedClicked: function(event) {
    880                 this.setFilter( Prefs._FilterFinished );
     742                        this.changeFileCommand(command, rows);
     743                }
    881744        },
    882745
     
    884747         * 'Clutch Preferences' was clicked (iPhone only)
    885748         */
    886         releaseClutchPreferencesButton: function(event) {
     749        releaseClutchPreferencesButton: function() {
    887750                $('div#prefs_container div#pref_error').hide();
    888751                $('div#prefs_container h2.dialog_heading').show();
    889                 this.showPrefsDialog( );
    890         },
    891 
    892         /*
    893          * Turn the periodic ajax torrents refresh on & off
    894          */
    895         togglePeriodicRefresh: function(state) {
    896                 var tr = this;
    897                 if (state && this._periodic_refresh == null) {
    898                         // sanity check
    899                         if( !this[Prefs._RefreshRate] )
    900                              this[Prefs._RefreshRate] = 5;
    901                         remote = this.remote;
    902                         this._periodic_refresh = setInterval(function(){ tr.refreshTorrents(); }, this[Prefs._RefreshRate] * 1000 );
    903                 } else {
    904                         clearInterval(this._periodic_refresh);
    905                         this._periodic_refresh = null;
    906                 }
    907         },
    908 
    909         /*
    910          * Turn the periodic ajax torrents refresh on & off for the selected torrents
    911          */
    912         periodicTorrentUpdate: function( ids ) {
    913                 var tr = this;
    914                 if( ids ) {
    915                         var curIds = this._extra_data_ids;
    916                         if( curIds == null )
    917                                 curIds = [ ];
    918                         if( ids.length == curIds.length ) {
    919                                 var duplicate = true;
    920                                 for(var i = 0; i < ids.length; i++ ) {
    921                                         if( ids[i] != curIds[i] ) {
    922                                                 duplicate = false;
    923                                                 break;
    924                                         }
    925                                 }
    926                                 if( duplicate ) return;
    927                         }
    928                         tr.refreshTorrents(ids);
    929                         clearInterval(this._metadata_refresh);
    930                         // sanity check
    931                         if( !this[Prefs._RefreshRate] ) this[Prefs._RefreshRate] = 5;
    932                         this._metadata_refresh = setInterval(function(){ tr.refreshTorrents(ids); }, this[Prefs._RefreshRate] * 1000 );
     752                this.showPrefsDialog();
     753        },
     754
     755        getIntervalMsec: function(key, min)
     756        {
     757                var interval = this[key];
     758                if (!interval || (interval < min))
     759                        interval = min;
     760                return interval * 1000;
     761        },
     762
     763        /* Turn the periodic ajax torrents refresh on & off */
     764        togglePeriodicRefresh: function (enabled) {
     765                clearInterval (this._periodic_refresh);
     766                delete this._periodic_refresh;
     767                if (enabled) {
     768                        var tr = this;
     769                        var msec = this.getIntervalMsec(Prefs._RefreshRate, 3);
     770                        this._periodic_refresh = setInterval(function() {tr.refreshTorrents();}, msec);
     771                }
     772        },
     773
     774        /* Turn the periodic ajax torrents refresh on & off for the selected torrents */
     775        periodicTorrentUpdate: function(ids) {
     776                clearInterval (this._metadata_refresh);
     777                delete this._metadata_refresh;
     778                delete this._extra_data_ids;
     779                if (ids && ids.length) {
     780                        this.refreshTorrents(ids);
    933781                        this._extra_data_ids = ids;
    934                 } else {
    935                         clearInterval(this._metadata_refresh);
    936                         this._metadata_refresh = null;
    937                         this._extra_data_ids = null;
    938                 }
    939         },
    940 
    941         /*
    942          * Turn the periodic ajax session refresh on & off
    943          */
    944         togglePeriodicSessionRefresh: function(state) {
    945                 var tr = this;
    946                 if (state && this._periodic_session_refresh == null) {
    947                         // sanity check
    948                         if( !this[Prefs._SessionRefreshRate] )
    949                              this[Prefs._SessionRefreshRate] = 5;
    950                         remote = this.remote;
    951                         this._periodic_session_refresh = setInterval(
    952                                 function(){ tr.loadDaemonPrefs(); }, this[Prefs._SessionRefreshRate] * 1000
    953                         );
    954                 } else {
    955                         clearInterval(this._periodic_session_refresh);
    956                         this._periodic_session_refresh = null;
    957                 }
    958         },
    959 
    960         /*
    961          * Turn the periodic ajax stats refresh on & off
    962          */
    963         togglePeriodicStatsRefresh: function(state) {
    964                 var tr = this;
    965                 if (state && this._periodic_stats_refresh == null) {
    966                         // sanity check
    967                         if( !this[Prefs._SessionRefreshRate] )
    968                              this[Prefs._SessionRefreshRate] = 5;
    969                         remote = this.remote;
    970                         this._periodic_stats_refresh = setInterval(
    971                                 function(){ tr.loadDaemonStats(); }, this[Prefs._SessionRefreshRate] * 1000
    972                         );
    973                 } else {
    974                         clearInterval(this._periodic_stats_refresh);
    975                         this._periodic_stats_refresh = null;
    976                 }
    977         },
    978 
    979         toggleTurtleClicked: function() {
    980                 // Toggle the value
    981                 this[Prefs._TurtleState] = !this[Prefs._TurtleState];
    982                 // Store the result
     782                        var tr = this;
     783                        var msec = this.getIntervalMsec(Prefs._RefreshRate, 3);
     784                        this._metadata_refresh = setInterval(function() { tr.refreshTorrents(ids);}, msec);
     785                }
     786        },
     787
     788        /* Turn the periodic ajax session refresh on & off */
     789        togglePeriodicSessionRefresh: function(enabled) {
     790                clearInterval(this._periodic_session_refresh);
     791                delete this._periodic_session_refresh;
     792                if (enabled) {
     793                        var tr = this;
     794                        var msec = this.getIntervalMsec(Prefs._SessionRefreshRate, 5);
     795                        this._periodic_session_refresh = setInterval(function() {tr.loadDaemonPrefs();}, msec);
     796                }
     797        },
     798
     799        /* Turn the periodic ajax stats refresh on & off */
     800        togglePeriodicStatsRefresh: function(enabled) {
     801                clearInterval(this._periodic_stats_refresh);
     802                delete this._periodic_stats_refresh;
     803                if (enabled) {
     804                        var tr = this;
     805                        var msec = this.getIntervalMsec(Prefs._SessionRefreshRate, 5);
     806                        this._periodic_stats_refresh = setInterval(function() {tr.loadDaemonStats();}, msec);
     807                }
     808        },
     809
     810        toggleTurtleClicked: function()
     811        {
     812                // toggle it
     813                var p = Prefs._TurtleState;
     814                this[p] = !this[p];
     815
     816                // send it to the session
    983817                var args = { };
    984                 args[RPC._TurtleState] = this[Prefs._TurtleState];
    985                 this.remote.savePrefs( args );
     818                args[RPC._TurtleState] = this[p];
     819                this.remote.savePrefs(args);
    986820        },
    987821
    988822        updateSelectedData: function()
    989823        {
    990                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.getId(); } );
    991                 if( ids.length > 0 )
    992                         this.periodicTorrentUpdate( ids );
     824                var ids = this.getSelectedTorrentIds();
     825                if (ids.length > 0)
     826                        this.periodicTorrentUpdate(ids);
    993827                else
    994                         this.periodicTorrentUpdate( false );
     828                        this.periodicTorrentUpdate(false);
    995829        },
    996830
     
    998832                var t;
    999833                var w = $('#turtle_button');
    1000                 if ( this[Prefs._TurtleState] ) {
     834                if (this[Prefs._TurtleState]) {
    1001835                        w.addClass('turtleEnabled');
    1002836                        w.removeClass('turtleDisabled');
     
    1007841                        t = [ 'Click to enable Temporary Speed Limits' ];
    1008842                }
    1009                 t.push( '(', Transmission.fmt.speed(this._prefs[RPC._TurtleUpSpeedLimit]), 'up,',
    1010                              Transmission.fmt.speed(this._prefs[RPC._TurtleDownSpeedLimit]), 'down)' );
    1011                 w.attr( 'title', t.join(' ') );
     843                t.push('(', Transmission.fmt.speed(this._prefs[RPC._TurtleUpSpeedLimit]), 'up,',
     844                             Transmission.fmt.speed(this._prefs[RPC._TurtleDownSpeedLimit]), 'down)');
     845                w.attr('title', t.join(' '));
    1012846        },
    1013847
     
    1018852         *--------------------------------------------*/
    1019853
    1020         showPrefsDialog: function( ) {
     854        showPrefsDialog: function() {
    1021855                this.checkPort(true);
    1022856                $('body').addClass('prefs_showing');
    1023857                $('#prefs_container').show();
    1024858                this.hideiPhoneAddressbar();
    1025                 if( Safari3 )
     859                if (Safari3)
    1026860                        setTimeout("$('div#prefs_container div.dialog_window').css('top', '0px');",10);
    1027                 this.updateButtonStates( );
     861                this.updateButtonStates();
    1028862                this.togglePeriodicSessionRefresh(false);
    1029863        },
    1030864
    1031         hidePrefsDialog: function( )
     865        hidePrefsDialog: function()
    1032866        {
    1033867                $('body.prefs_showing').removeClass('prefs_showing');
     
    1041875                        $('#prefs_container').hide();
    1042876                }
    1043                 this.updateButtonStates( );
     877                this.updateButtonStates();
    1044878                this.togglePeriodicSessionRefresh(true);
    1045879        },
     
    1048882         * Process got some new session data from the server
    1049883         */
    1050         updatePrefs: function( prefs )
     884        updatePrefs: function(prefs)
    1051885        {
    1052886                // remember them for later
     
    1060894                var turtle_dn_limit_k = prefs[RPC._TurtleDownSpeedLimit];
    1061895
    1062                 if( prefs.units )
    1063                     Transmission.fmt.updateUnits( prefs.units );
     896                if (prefs.units)
     897                    Transmission.fmt.updateUnits(prefs.units);
    1064898
    1065899                $('div.download_location input')[0].value = prefs[RPC._DownloadDir];
     
    1075909                $('input#turtle_upload_rate')[0].value    = turtle_up_limit_k;
    1076910                $('input#turtle_schedule')[0].checked     = prefs[RPC._TurtleTimeEnabled];
    1077                 $('select#turtle_start_time').val(          prefs[RPC._TurtleTimeBegin] );
    1078                 $('select#turtle_end_time').val(            prefs[RPC._TurtleTimeEnd] );
    1079                 $('select#turtle_days').val(                prefs[RPC._TurtleTimeDay] );
    1080                 $('#transmission_version').text(            prefs[RPC._DaemonVersion] );
    1081 
    1082                 $('#conn_global').val(                                          prefs[RPC._PeerLimitGlobal] );
    1083                 $('#conn_torrent').val(                                         prefs[RPC._PeerLimitPerTorrent] );
    1084                 $('#conn_pex')[0].checked                                 = prefs[RPC._PexEnabled];
    1085                 $('#conn_dht')[0].checked                                 = prefs[RPC._DhtEnabled];
    1086                 $('#conn_lpd')[0].checked                                 = prefs[RPC._LpdEnabled];
    1087                 $('#block_enable')[0].checked                     = prefs[RPC._BlocklistEnabled];
    1088                 $('#block_url').val(                                        prefs[RPC._BlocklistURL]);
    1089                 $('#block_size').text(                                      prefs[RPC._BlocklistSize] + ' IP rules in the list' );
    1090                 $('#network_utp')[0].checked                      = prefs[RPC._UtpEnabled];
    1091                 $('#port_rand')[0].checked                                = prefs[RPC._PeerPortRandom];
    1092                 $('#port_forward')[0].checked                     = prefs[RPC._PortForwardingEnabled];
     911                $('select#turtle_start_time').val(        prefs[RPC._TurtleTimeBegin]);
     912                $('select#turtle_end_time').val(          prefs[RPC._TurtleTimeEnd]);
     913                $('select#turtle_days').val(              prefs[RPC._TurtleTimeDay]);
     914                $('#transmission_version').text(          prefs[RPC._DaemonVersion]);
     915                $('#conn_global').val(                    prefs[RPC._PeerLimitGlobal]);
     916                $('#conn_torrent').val(                   prefs[RPC._PeerLimitPerTorrent]);
     917                $('#conn_pex')[0].checked                 = prefs[RPC._PexEnabled];
     918                $('#conn_dht')[0].checked                 = prefs[RPC._DhtEnabled];
     919                $('#conn_lpd')[0].checked                 = prefs[RPC._LpdEnabled];
     920                $('#block_enable')[0].checked             = prefs[RPC._BlocklistEnabled];
     921                $('#block_url').val(                      prefs[RPC._BlocklistURL]);
     922                $('#block_size').text(                    prefs[RPC._BlocklistSize] + ' IP rules in the list');
     923                $('#network_utp')[0].checked              = prefs[RPC._UtpEnabled];
     924                $('#port_rand')[0].checked                = prefs[RPC._PeerPortRandom];
     925                $('#port_forward')[0].checked             = prefs[RPC._PortForwardingEnabled];
    1093926
    1094927
    1095928                if (!iPhone)
    1096929                {
    1097                         setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(dn_limit_k), ')' ].join('') );
     930                        setInnerHTML($('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(dn_limit_k), ')' ].join(''));
    1098931                        var key = dn_limited ? '#limited_download_rate'
    1099932                                               : '#unlimited_download_rate';
    1100933                        $(key).deselectMenuSiblings().selectMenuItem();
    1101934
    1102                         setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(up_limit_k), ')' ].join('') );
     935                        setInnerHTML($('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(up_limit_k), ')' ].join(''));
    1103936                        key = up_limited ? '#limited_upload_rate'
    1104937                                         : '#unlimited_upload_rate';
     
    1108941                this[Prefs._TurtleState] = prefs[RPC._TurtleState];
    1109942                this.updateTurtleButton();
    1110         },
    1111 
    1112         updatePortStatus: function( status ){
    1113                 if(status['port-is-open'])
     943                this.setCompactMode(prefs[Prefs._CompactDisplayState]);
     944        },
     945
     946        updatePortStatus: function(status) {
     947                if (status['port-is-open'])
    1114948                        $('#port_test').text('Port is open');
    1115949                else
     
    1117951        },
    1118952
    1119         showStatsDialog: function( ) {
     953        showStatsDialog: function() {
    1120954                this.loadDaemonStats();
    1121955                $('body').addClass('stats_showing');
    1122956                $('#stats_container').show();
    1123957                this.hideiPhoneAddressbar();
    1124                 if( Safari3 )
     958                if (Safari3)
    1125959                        setTimeout("$('div#stats_container div.dialog_window').css('top', '0px');",10);
    1126                 this.updateButtonStates( );
     960                this.updateButtonStates();
    1127961                this.togglePeriodicStatsRefresh(true);
    1128962        },
    1129963
    1130         hideStatsDialog: function( ){
     964        hideStatsDialog: function() {
    1131965                $('body.stats_showing').removeClass('stats_showing');
    1132966                if (iPhone) {
     
    1139973                        $('#stats_container').hide();
    1140974                }
    1141                 this.updateButtonStates( );
     975                this.updateButtonStates();
    1142976                this.togglePeriodicStatsRefresh(false);
    1143977        },
     
    1146980         * Process got some new session stats from the server
    1147981         */
    1148         updateStats: function( stats )
     982        updateStats: function(stats)
    1149983        {
    1150984                // can't think of a reason to remember this
     
    1155989                var total = stats["cumulative-stats"];
    1156990
    1157                 setInnerHTML( $('#stats_session_uploaded')[0], fmt.size(session["uploadedBytes"]) );
    1158                 setInnerHTML( $('#stats_session_downloaded')[0], fmt.size(session["downloadedBytes"]) );
    1159                 setInnerHTML( $('#stats_session_ratio')[0], fmt.ratioString(Math.ratio(session["uploadedBytes"],session["downloadedBytes"])));
    1160                 setInnerHTML( $('#stats_session_duration')[0], fmt.timeInterval(session["secondsActive"]) );
    1161                 setInnerHTML( $('#stats_total_count')[0], total["sessionCount"] + " times" );
    1162                 setInnerHTML( $('#stats_total_uploaded')[0], fmt.size(total["uploadedBytes"]) );
    1163                 setInnerHTML( $('#stats_total_downloaded')[0], fmt.size(total["downloadedBytes"]) );
    1164                 setInnerHTML( $('#stats_total_ratio')[0], fmt.ratioString(Math.ratio(total["uploadedBytes"],total["downloadedBytes"])));
    1165                 setInnerHTML( $('#stats_total_duration')[0], fmt.timeInterval(total["secondsActive"]) );
    1166         },
    1167 
    1168         setSearch: function( search ) {
     991                setInnerHTML($('#stats_session_uploaded')[0], fmt.size(session["uploadedBytes"]));
     992                setInnerHTML($('#stats_session_downloaded')[0], fmt.size(session["downloadedBytes"]));
     993                setInnerHTML($('#stats_session_ratio')[0], fmt.ratioString(Math.ratio(session["uploadedBytes"],session["downloadedBytes"])));
     994                setInnerHTML($('#stats_session_duration')[0], fmt.timeInterval(session["secondsActive"]));
     995                setInnerHTML($('#stats_total_count')[0], total["sessionCount"] + " times");
     996                setInnerHTML($('#stats_total_uploaded')[0], fmt.size(total["uploadedBytes"]));
     997                setInnerHTML($('#stats_total_downloaded')[0], fmt.size(total["downloadedBytes"]));
     998                setInnerHTML($('#stats_total_ratio')[0], fmt.ratioString(Math.ratio(total["uploadedBytes"],total["downloadedBytes"])));
     999                setInnerHTML($('#stats_total_duration')[0], fmt.timeInterval(total["secondsActive"]));
     1000        },
     1001
     1002        setSearch: function(search) {
    11691003                this._current_search = search ? search.trim() : null;
    1170                 this.refilter( );
    1171         },
    1172 
    1173         setSortMethod: function( sort_method ) {
    1174                 this.setPref( Prefs._SortMethod, sort_method );
    1175                 this.refilter( );
    1176         },
    1177 
    1178         setSortDirection: function( direction ) {
    1179                 this.setPref( Prefs._SortDirection, direction );
    1180                 this.refilter( );
     1004                this.refilter();
     1005        },
     1006
     1007        setSortMethod: function(sort_method) {
     1008                this.setPref(Prefs._SortMethod, sort_method);
     1009                this.refilter();
     1010        },
     1011
     1012        setSortDirection: function(direction) {
     1013                this.setPref(Prefs._SortDirection, direction);
     1014                this.refilter();
    11811015        },
    11821016
     
    11841018         * Process an event in the footer-menu
    11851019         */
    1186         processSettingsMenuEvent: function(event) {
    1187                 var tr = this;
    1188                 var $element = $(event.target);
     1020        processSettingsMenuEvent: function(ev) {
     1021                var tr = this;
     1022                var $element = $(ev.target);
    11891023
    11901024                // Figure out which menu has been clicked
     
    11961030                                        $('div#prefs_container div#pref_error').hide();
    11971031                                        $('div#prefs_container h2.dialog_heading').show();
    1198                                         tr.showPrefsDialog( );
     1032                                        tr.showPrefsDialog();
    11991033                                }
    12001034                                else if ($element[0].id == 'statistics') {
    12011035                                        $('div#stats_container div#stats_error').hide();
    12021036                                        $('div#stats_container h2.dialog_heading').show();
    1203                                         tr.showStatsDialog( );
     1037                                        tr.showStatsDialog();
    12041038                                }
    12051039                                else if ($element[0].id == 'compact_view') {
    1206                                         this.setPref( Prefs._CompactDisplayState, !this[Prefs._CompactDisplayState])
    1207                                         if(this[Prefs._CompactDisplayState])
    1208                                                 $element.selectMenuItem();
    1209                                         else
    1210                                                 $element.deselectMenuItem();
    1211                                         this.setCompactMode( this[Prefs._CompactDisplayState] );
     1040                                        this.toggleCompactClicked();
    12121041                                }
    12131042                                else if ($element[0].id == 'homepage') {
     
    12261055                                        args[RPC._DownSpeedLimited] = false;
    12271056                                } else {
    1228                                         var rate_str = $element[0].innerHTML
    1229                                         var rate_val = parseInt( rate_str );
    1230                                         setInnerHTML( $('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('') );
     1057                                        var rate_str = $element[0].innerHTML;
     1058                                        var rate_val = parseInt(rate_str, 10);
     1059                                        setInnerHTML($('#limited_download_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join(''));
    12311060                                        $('#limited_download_rate').deselectMenuSiblings().selectMenuItem();
    12321061                                        $('div.preference input#download_rate')[0].value = rate_str;
     
    12351064                                }
    12361065                                $('div.preference input#limit_download')[0].checked = args[RPC._DownSpeedLimited];
    1237                                 tr.remote.savePrefs( args );
     1066                                tr.remote.savePrefs(args);
    12381067                                break;
    12391068
     
    12451074                                        args[RPC._UpSpeedLimited] = false;
    12461075                                } else {
    1247                                         var rate_str = $element[0].innerHTML
    1248                                         var rate_val = parseInt( rate_str );
    1249                                         setInnerHTML( $('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join('')  );
     1076                                        var rate_str = $element[0].innerHTML;
     1077                                        var rate_val = parseInt(rate_str, 10);
     1078                                        setInnerHTML($('#limited_upload_rate')[0], [ 'Limit (', Transmission.fmt.speed(rate_val), ')' ].join(''));
    12501079                                        $('#limited_upload_rate').deselectMenuSiblings().selectMenuItem();
    12511080                                        $('div.preference input#upload_rate')[0].value = rate_str;
     
    12541083                                }
    12551084                                $('div.preference input#limit_upload')[0].checked = args[RPC._UpSpeedLimited];
    1256                                 tr.remote.savePrefs( args );
     1085                                tr.remote.savePrefs(args);
    12571086                                break;
    12581087
     
    12621091                                // The 'reverse sort' option state can be toggled independently of the other options
    12631092                                if ($element.is('#reverse_sort_order')) {
    1264                                         if(!$element.is('#reverse_sort_order.active')) break;
     1093                                        if (!$element.is('#reverse_sort_order.active')) break;
    12651094                                        var dir;
    12661095                                        if ($element.menuItemIsSelected()) {
     
    12711100                                                dir = Prefs._SortDescending;
    12721101                                        }
    1273                                         tr.setSortDirection( dir );
     1102                                        tr.setSortDirection(dir);
    12741103
    12751104                                // Otherwise, deselect all other options (except reverse-sort) and select this one
    12761105                                } else {
    1277                                         $element.parent().find('span.selected').each( function() {
     1106                                        $element.parent().find('span.selected').each(function() {
    12781107                                                if (! $element.parent().is('#reverse_sort_order')) {
    12791108                                                        $element.parent().deselectMenuItem();
     
    12821111                                        $element.selectMenuItem();
    12831112                                        var method = $element[0].id.replace(/sort_by_/, '');
    1284                                         tr.setSortMethod( method );
     1113                                        tr.setSortMethod(method);
    12851114                                }
    12861115                                break;
     
    12951124        updateInspector: function()
    12961125        {
    1297                 if( !this[Prefs._ShowInspector] )
     1126                if (!this[Prefs._ShowInspector])
    12981127                        return;
    12991128
    1300                 var torrents = this.getSelectedTorrents( );
    1301                 if( !torrents.length && iPhone ) {
     1129                var torrents = this.getSelectedTorrents();
     1130                if (!torrents.length && iPhone) {
    13021131                        this.hideInspector();
    13031132                        return;
     
    13201149                var total_download_speed = 0;
    13211150                var total_availability = 0;
    1322                 var total_have = 0;
    13231151                var total_size = 0;
    13241152                var total_state = [ ];
     
    13331161                $("#torrent_inspector_size, .inspector_row div").css('color', '#222');
    13341162
    1335                 if( torrents.length == 0 )
     1163                if (torrents.length == 0)
    13361164                {
    1337                         setInnerHTML( tab.name, 'No Selection' );
    1338                         setInnerHTML( tab.size, na );
    1339                         setInnerHTML( tab.pieces, na );
    1340                         setInnerHTML( tab.hash, na );
    1341                         setInnerHTML( tab.state, na );
    1342                         setInnerHTML( tab.download_speed, na );
    1343                         setInnerHTML( tab.upload_speed, na );
    1344                         setInnerHTML( tab.uploaded, na );
    1345                         setInnerHTML( tab.downloaded, na );
    1346                         setInnerHTML( tab.availability, na );
    1347                         setInnerHTML( tab.ratio, na );
    1348                         setInnerHTML( tab.have, na );
    1349                         setInnerHTML( tab.upload_to, na );
    1350                         setInnerHTML( tab.download_from, na );
    1351                         setInnerHTML( tab.secure, na );
    1352                         setInnerHTML( tab.creator_date, na );
    1353                         setInnerHTML( tab.progress, na );
    1354                         setInnerHTML( tab.comment, na );
    1355                         setInnerHTML( tab.creator, na );
    1356                         setInnerHTML( tab.download_dir, na );
    1357                         setInnerHTML( tab.error, na );
     1165                        setInnerHTML(tab.name, 'No Selection');
     1166                        setInnerHTML(tab.size, na);
     1167                        setInnerHTML(tab.pieces, na);
     1168                        setInnerHTML(tab.hash, na);
     1169                        setInnerHTML(tab.state, na);
     1170                        setInnerHTML(tab.download_speed, na);
     1171                        setInnerHTML(tab.upload_speed, na);
     1172                        setInnerHTML(tab.uploaded, na);
     1173                        setInnerHTML(tab.downloaded, na);
     1174                        setInnerHTML(tab.availability, na);
     1175                        setInnerHTML(tab.ratio, na);
     1176                        setInnerHTML(tab.have, na);
     1177                        setInnerHTML(tab.upload_to, na);
     1178                        setInnerHTML(tab.download_from, na);
     1179                        setInnerHTML(tab.secure, na);
     1180                        setInnerHTML(tab.creator_date, na);
     1181                        setInnerHTML(tab.progress, na);
     1182                        setInnerHTML(tab.comment, na);
     1183                        setInnerHTML(tab.creator, na);
     1184                        setInnerHTML(tab.download_dir, na);
     1185                        setInnerHTML(tab.error, na);
    13581186                        this.updateFileList();
    13591187                        this.updatePeersLists();
     
    13671195                        : torrents.length+' Transfers Selected';
    13681196
    1369                 if( torrents.length == 1 )
     1197                if (torrents.length == 1)
    13701198                {
    1371                         var text
     1199                        var text;
    13721200                        var t = torrents[0];
    1373                         var err = t.getErrorMessage( );
    1374                         if( err )
     1201                        var err = t.getErrorMessage();
     1202                        if (err)
    13751203                                error = err;
    1376                         if(( text = t.getComment()))
    1377                                 comment = text
    1378                         if(( text = t.getCreator()))
    1379                                 creator = text
    1380                         if(( text = t.getDownloadDir()))
    1381                                 download_dir = text
     1204                        if ((text = t.getComment()))
     1205                                comment = text;
     1206                        if ((text = t.getCreator()))
     1207                                creator = text;
     1208                        if ((text = t.getDownloadDir()))
     1209                                download_dir = text;
    13821210
    13831211                        hash = t.getHashString();
    13841212                        pieces = [ t.getPieceCount(), 'pieces @', Transmission.fmt.mem(t.getPieceSize()) ].join(' ');
    1385                         date_created = Transmission.fmt.timestamp( t.getDateCreated() );
    1386                 }
    1387 
    1388                 for( var i=0, t; t=torrents[i]; ++i ) {
    1389                         var sizeWhenDone = t.getSizeWhenDone()
    1390                         var left = t.getLeftUntilDone()
    1391                         sizeWhenDone         += sizeWhenDone
    1392                         sizeDone             += sizeWhenDone - left;
     1213                        date_created = Transmission.fmt.timestamp(t.getDateCreated());
     1214                }
     1215
     1216                for (var i=0, t; t=torrents[i]; ++i) {
     1217                        var l = t.getLeftUntilDone();
     1218                        var d = t.getSizeWhenDone();
     1219                        sizeWhenDone         += d;
     1220                        sizeDone             += d - l;
    13931221                        total_completed      += t.getHave();
    13941222                        total_verified       += t.getHaveValid();
     
    14001228                        total_upload_peers   += t.getPeersGettingFromUs();
    14011229                        total_download_peers += t.getPeersSendingToUs();
    1402                         total_availability   += sizeWhenDone - left + t.getDesiredAvailable();
     1230                        total_availability   += sizeWhenDone - l + t.getDesiredAvailable();
    14031231
    14041232                        var s = t.getStateString();
    1405                         if( total_state.indexOf( s ) == -1 )
    1406                                 total_state.push( s );
    1407 
    1408                         if( t.getPrivateFlag( ) )
     1233                        if (total_state.indexOf(s) == -1)
     1234                                total_state.push(s);
     1235
     1236                        if (t.getPrivateFlag())
    14091237                                have_private = true;
    14101238                        else
     
    14141242                var private_string = '';
    14151243                var fmt = Transmission.fmt;
    1416                 if( have_private && have_public ) private_string = 'Mixed';
    1417                 else if( have_private ) private_string = 'Private Torrent';
    1418                 else if( have_public ) private_string = 'Public Torrent';
    1419 
    1420                 setInnerHTML( tab.name, name );
    1421                 setInnerHTML( tab.size, torrents.length ? fmt.size( total_size ) : na );
    1422                 setInnerHTML( tab.pieces, pieces );
    1423                 setInnerHTML( tab.hash, hash );
    1424                 setInnerHTML( tab.state, total_state.join('/') );
    1425                 setInnerHTML( tab.download_speed, torrents.length ? fmt.speedBps( total_download_speed ) : na );
    1426                 setInnerHTML( tab.upload_speed, torrents.length ? fmt.speedBps( total_upload_speed ) : na );
    1427                 setInnerHTML( tab.uploaded, torrents.length ? fmt.size( total_upload ) : na );
    1428                 setInnerHTML( tab.downloaded, torrents.length ? fmt.size( total_download ) : na );
    1429                 setInnerHTML( tab.availability, torrents.length ? fmt.percentString(Math.ratio( total_availability*100, sizeWhenDone )) + '%' : na );
    1430                 setInnerHTML( tab.ratio, torrents.length ? fmt.ratioString(Math.ratio( total_upload, total_download )) : na );
    1431                 setInnerHTML( tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na );
    1432                 setInnerHTML( tab.upload_to, torrents.length ? total_upload_peers : na );
    1433                 setInnerHTML( tab.download_from, torrents.length ? total_download_peers : na );
    1434                 setInnerHTML( tab.secure, private_string );
    1435                 setInnerHTML( tab.creator_date, date_created );
    1436                 setInnerHTML( tab.progress, torrents.length ? fmt.percentString(Math.ratio( sizeDone*100, sizeWhenDone )) + '%' : na );
    1437                 setInnerHTML( tab.comment, comment );
    1438                 setInnerHTML( tab.creator, creator );
    1439                 setInnerHTML( tab.download_dir, download_dir );
    1440                 setInnerHTML( tab.error, error );
     1244                if (have_private && have_public) private_string = 'Mixed';
     1245                else if (have_private) private_string = 'Private Torrent';
     1246                else if (have_public) private_string = 'Public Torrent';
     1247
     1248                setInnerHTML(tab.name, name);
     1249                setInnerHTML(tab.size, torrents.length ? fmt.size(total_size) : na);
     1250                setInnerHTML(tab.pieces, pieces);
     1251                setInnerHTML(tab.hash, hash);
     1252                setInnerHTML(tab.state, total_state.join('/'));
     1253                setInnerHTML(tab.download_speed, torrents.length ? fmt.speedBps(total_download_speed) : na);
     1254                setInnerHTML(tab.upload_speed, torrents.length ? fmt.speedBps(total_upload_speed) : na);
     1255                setInnerHTML(tab.uploaded, torrents.length ? fmt.size(total_upload) : na);
     1256                setInnerHTML(tab.downloaded, torrents.length ? fmt.size(total_download) : na);
     1257                setInnerHTML(tab.availability, torrents.length ? fmt.percentString(Math.ratio(total_availability*100, sizeWhenDone)) + '%' : na);
     1258                setInnerHTML(tab.ratio, torrents.length ? fmt.ratioString(Math.ratio(total_upload, total_download)) : na);
     1259                setInnerHTML(tab.have, torrents.length ? fmt.size(total_completed) + ' (' + fmt.size(total_verified) + ' verified)' : na);
     1260                setInnerHTML(tab.upload_to, torrents.length ? total_upload_peers : na);
     1261                setInnerHTML(tab.download_from, torrents.length ? total_download_peers : na);
     1262                setInnerHTML(tab.secure, private_string);
     1263                setInnerHTML(tab.creator_date, date_created);
     1264                setInnerHTML(tab.progress, torrents.length ? fmt.percentString(Math.ratio(sizeDone*100, sizeWhenDone)) + '%' : na);
     1265                setInnerHTML(tab.comment, comment);
     1266                setInnerHTML(tab.creator, creator);
     1267                setInnerHTML(tab.download_dir, download_dir);
     1268                setInnerHTML(tab.error, error);
    14411269
    14421270                this.updatePeersLists();
     
    14461274        },
    14471275
    1448         onFileWantedToggled: function( row, want ) {
     1276        onFileWantedToggled: function(row, want) {
    14491277                var command = want ? 'files-wanted' : 'files-unwanted';
    1450                 this.changeFileCommand( command, [ row ] );
    1451         },
    1452         onFilePriorityToggled: function( row, priority ) {
     1278                this.changeFileCommand(command, [ row ]);
     1279        },
     1280        onFilePriorityToggled: function(row, priority) {
    14531281                var command;
    1454                 switch( priority ) {
     1282                switch(priority) {
    14551283                        case -1: command = 'priority-low'; break;
    14561284                        case  1: command = 'priority-high'; break;
    14571285                        default: command = 'priority-normal'; break;
    14581286                }
    1459                 this.changeFileCommand( command, [ row ] );
     1287                this.changeFileCommand(command, [ row ]);
    14601288        },
    14611289        clearFileList: function() {
     
    14671295
    14681296                // if the file list is hidden, clear the list
    1469                 if( this._inspector_tab_files.className.indexOf('selected') == -1 ) {
    1470                         this.clearFileList( );
     1297                if (this._inspector_tab_files.className.indexOf('selected') == -1) {
     1298                        this.clearFileList();
    14711299                        return;
    14721300                }
    14731301
    14741302                // if not torrent is selected, clear the list
    1475                 var selected_torrents = this.getSelectedTorrents( );
    1476                 if( selected_torrents.length != 1 ) {
    1477                         this.clearFileList( );
     1303                var selected_torrents = this.getSelectedTorrents();
     1304                if (selected_torrents.length != 1) {
     1305                        this.clearFileList();
    14781306                        return;
    14791307                }
     
    14811309                // if the active torrent hasn't changed, noop
    14821310                var torrent = selected_torrents[0];
    1483                 if( this._files_torrent === torrent )
     1311                if (this._files_torrent === torrent)
    14841312                        return;
    14851313
    14861314                // build the file list
    1487                 this.clearFileList( );
     1315                this.clearFileList();
    14881316                this._files_torrent = torrent;
    14891317                var n = torrent._files.length;
    1490                 this._files = new Array( n );
    1491                 var fragment = document.createDocumentFragment( );
    1492                 var tr = this;
    1493                 for( var i=0; i<n; ++i ) {
    1494                         var row = new FileRow( this, torrent, i );
    1495                         fragment.appendChild( row.getElement( ) );
     1318                this._files = new Array(n);
     1319                var fragment = document.createDocumentFragment();
     1320                var tr = this;
     1321                for (var i=0; i<n; ++i) {
     1322                        var row = new FileRow(torrent, i);
     1323                        fragment.appendChild(row.getElement());
    14961324                        this._files[i] = row;
    1497                         $(row).bind('wantedToggled',function(e,row,want){tr.onFileWantedToggled(row,want);});
    1498                         $(row).bind('priorityToggled',function(e,row,priority){tr.onFilePriorityToggled(row,priority);});
    1499                 }
    1500                 this._inspector_file_list.appendChild( fragment );
     1325                        $(row).bind('wantedToggled',function(e,row,want) {tr.onFileWantedToggled(row,want);});
     1326                        $(row).bind('priorityToggled',function(e,row,priority) {tr.onFilePriorityToggled(row,priority);});
     1327                }
     1328                this._inspector_file_list.appendChild(fragment);
    15011329        },
    15021330
    15031331        refreshFileView: function() {
    1504                 for( var i=0, row; row=this._files[i]; ++i )
     1332                for (var i=0, row; row=this._files[i]; ++i)
    15051333                        row.refresh();
    15061334        },
    15071335
    15081336        updatePeersLists: function() {
    1509                 var tr = this;
    15101337                var html = [ ];
    15111338                var fmt = Transmission.fmt;
    1512                 var torrents = this.getSelectedTorrents( );
    1513                 if( $(this._inspector_peers_list).is(':visible') ) {
    1514                         for( var k=0, torrent; torrent=torrents[k]; ++k ) {
    1515                                 var peers = torrent.getPeers()
    1516                                 html.push( '<div class="inspector_group">' );
    1517                                 if( torrents.length > 1 ) {
    1518                                         html.push( '<div class="inspector_torrent_label">', torrent.getName(), '</div>' );
     1339                var torrents = this.getSelectedTorrents();
     1340                if ($(this._inspector_peers_list).is(':visible')) {
     1341                        for (var k=0, torrent; torrent=torrents[k]; ++k) {
     1342                                var peers = torrent.getPeers();
     1343                                html.push('<div class="inspector_group">');
     1344                                if (torrents.length > 1) {
     1345                                        html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
    15191346                                }
    1520                                 if( peers.length == 0 ) {
    1521                                         html.push( '<br></div>' ); // firefox won't paint the top border if the div is empty
     1347                                if (peers.length == 0) {
     1348                                        html.push('<br></div>'); // firefox won't paint the top border if the div is empty
    15221349                                        continue;
    15231350                                }
    1524                                 html.push( '<table class="peer_list">',
     1351                                html.push('<table class="peer_list">',
    15251352                                           '<tr class="inspector_peer_entry even">',
    15261353                                           '<th class="encryptedCol"></th>',
     
    15311358                                           '<th class="addressCol">Address</th>',
    15321359                                           '<th class="clientCol">Client</th>',
    1533                                            '</tr>' );
    1534                                 for( var i=0, peer; peer=peers[i]; ++i ) {
     1360                                           '</tr>');
     1361                                for (var i=0, peer; peer=peers[i]; ++i) {
    15351362                                        var parity = ((i+1) % 2 == 0 ? 'even' : 'odd');
    1536                                         html.push( '<tr class="inspector_peer_entry ', parity, '">',
     1363                                        html.push('<tr class="inspector_peer_entry ', parity, '">',
    15371364                                                   '<td>', (peer.isEncrypted ? '<img src="images/graphics/lock_icon.png" alt="Encrypted"/>' : ''), '</td>',
    1538                                                    '<td>', ( peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : '' ), '</td>',
    1539                                                    '<td>', ( peer.rateToClient ? fmt.speedBps(peer.rateToClient) : '' ), '</td>',
     1365                                                   '<td>', (peer.rateToPeer ? fmt.speedBps(peer.rateToPeer) : ''), '</td>',
     1366                                                   '<td>', (peer.rateToClient ? fmt.speedBps(peer.rateToClient) : ''), '</td>',
    15401367                                                   '<td class="percentCol">', Math.floor(peer.progress*100), '%', '</td>',
    15411368                                                   '<td>', peer.flagStr, '</td>',
    15421369                                                   '<td>', peer.address, '</td>',
    15431370                                                   '<td class="clientCol">', peer.clientName, '</td>',
    1544                                                    '</tr>' );
     1371                                                   '</tr>');
    15451372                                }
    1546                                 html.push( '</table></div>' );
     1373                                html.push('</table></div>');
    15471374                        }
    15481375                }
    1549                 setInnerHTML(this._inspector_peers_list, html.join('') );
     1376                setInnerHTML(this._inspector_peers_list, html.join(''));
    15501377        },
    15511378
     
    15561383                var html = [ ];
    15571384                var na = 'N/A';
    1558                 var torrents = this.getSelectedTorrents( );
    1559                 if( $(this._inspector_trackers_list).is(':visible') ) {
    1560                         for( var k=0, torrent; torrent = torrents[k]; ++k ) {
    1561                                 html.push( '<div class="inspector_group">' );
    1562                                 if( torrents.length > 1 ) {
    1563                                         html.push( '<div class="inspector_torrent_label">', torrent.getName(), '</div>' );
     1385                var torrents = this.getSelectedTorrents();
     1386                if ($(this._inspector_trackers_list).is(':visible')) {
     1387                        for (var k=0, torrent; torrent = torrents[k]; ++k) {
     1388                                html.push ('<div class="inspector_group">');
     1389                                if (torrents.length > 1) {
     1390                                        html.push('<div class="inspector_torrent_label">', torrent.getName(), '</div>');
    15641391                                }
    1565                                 for( var i=0, tier; tier=torrent._trackerStats[i]; ++i ) {
    1566                                         html.push( '<div class="inspector_group_label">',
    1567                                                    'Tier ', (i + 1), '</div>',
    1568                                                    '<ul class="tier_list">' );
    1569                                         for( var j=0, tracker; tracker=tier[j]; ++j ) {
     1392                                for (var i=0, tier; tier=torrent._trackerStats[i]; ++i) {
     1393                                        html.push('<div class="inspector_group_label">',
     1394                                                  'Tier ', (i + 1), '</div>',
     1395                                                  '<ul class="tier_list">');
     1396                                        for (var j=0, tracker; tracker=tier[j]; ++j) {
    15701397                                                var lastAnnounceStatusHash = tr.lastAnnounceStatus(tracker);
    15711398                                                var announceState = tr.announceState(tracker);
     
    15741401                                                // Display construction
    15751402                                                var parity = ((j+1) % 2 == 0 ? 'even' : 'odd');
    1576                                                 html.push( '<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
    1577                                                            tracker.host, '</div>',
    1578                                                            '<div class="tracker_activity">',
    1579                                                            '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
    1580                                                            '<div>', announceState, '</div>',
    1581                                                            '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
    1582                                                            '</div><table class="tracker_stats">',
    1583                                                            '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
    1584                                                            '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
    1585                                                            '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
    1586                                                            '</table></li>' );
     1403                                                html.push('<li class="inspector_tracker_entry ', parity, '"><div class="tracker_host" title="', tracker.announce, '">',
     1404                                                          tracker.host, '</div>',
     1405                                                          '<div class="tracker_activity">',
     1406                                                          '<div>', lastAnnounceStatusHash['label'], ': ', lastAnnounceStatusHash['value'], '</div>',
     1407                                                          '<div>', announceState, '</div>',
     1408                                                          '<div>', lastScrapeStatusHash['label'], ': ', lastScrapeStatusHash['value'], '</div>',
     1409                                                          '</div><table class="tracker_stats">',
     1410                                                          '<tr><th>Seeders:</th><td>', (tracker.seederCount > -1 ? tracker.seederCount : na), '</td></tr>',
     1411                                                          '<tr><th>Leechers:</th><td>', (tracker.leecherCount > -1 ? tracker.leecherCount : na), '</td></tr>',
     1412                                                          '<tr><th>Downloads:</th><td>', (tracker.downloadCount > -1 ? tracker.downloadCount : na), '</td></tr>',
     1413                                                          '</table></li>');
    15871414                                        }
    1588                                         html.push( '</ul>' );
     1415                                        html.push('</ul>');
    15891416                                }
    1590                                 html.push( '</div>' );
     1417                                html.push('</div>');
    15911418                        }
    15921419                }
     
    15941421        },
    15951422
    1596         lastAnnounceStatus: function(tracker){
     1423        lastAnnounceStatus: function(tracker) {
    15971424                var lastAnnounceLabel = 'Last Announce';
    15981425                var lastAnnounce = [ 'N/A' ];
     
    16091436        },
    16101437
    1611         announceState: function(tracker){
     1438        announceState: function(tracker) {
    16121439                var announceState = '';
    16131440                switch (tracker.announceState) {
     
    16171444                        case Torrent._TrackerWaiting:
    16181445                                var timeUntilAnnounce = tracker.nextAnnounceTime - ((new Date()).getTime() / 1000);
    1619                                 if(timeUntilAnnounce < 0){
     1446                                if (timeUntilAnnounce < 0) {
    16201447                                        timeUntilAnnounce = 0;
    16211448                                }
     
    16361463        },
    16371464
    1638         lastScrapeStatus: function(tracker){
     1465        lastScrapeStatus: function(tracker) {
    16391466                var lastScrapeLabel = 'Last Scrape';
    16401467                var lastScrape = 'N/A';
     
    16481475                        }
    16491476                }
    1650                 return {'label':lastScrapeLabel, 'value':lastScrape}
     1477                return {'label':lastScrapeLabel, 'value':lastScrape};
    16511478        },
    16521479
     
    16551482         */
    16561483        toggleInspector: function() {
    1657                 if( this[Prefs._ShowInspector] )
    1658                         this.hideInspector( );
     1484                if (this[Prefs._ShowInspector])
     1485                        this.hideInspector();
    16591486                else
    1660                         this.showInspector( );
     1487                        this.showInspector();
    16611488        },
    16621489
     
    16691496                } else {
    16701497                        var w = $('#torrent_inspector').width() + 1 + 'px';
    1671                         $('#torrent_filter_bar')[0].style.right = w;
    16721498                        $('#torrent_container')[0].style.right = w;
    16731499                }
    16741500
    1675                 setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Hide Inspector' );
    1676 
    1677                 this.setPref( Prefs._ShowInspector, true );
    1678                 this.updateInspector( );
    1679                 this.refreshDisplay( );
     1501                setInnerHTML($('ul li#context_toggle_inspector')[0], 'Hide Inspector');
     1502
     1503                this.setPref(Prefs._ShowInspector, true);
     1504                this.updateInspector();
     1505                this.refreshDisplay();
    16801506        },
    16811507
     
    16881514
    16891515                if (iPhone) {
    1690                         this.deselectAll( );
     1516                        this.deselectAll();
    16911517                        $('body.inspector_showing').removeClass('inspector_showing');
    16921518                        $('#inspector_close').hide();
    16931519                        this.hideiPhoneAddressbar();
    16941520                } else {
    1695                         $('#torrent_filter_bar')[0].style.right = '0px';
    16961521                        $('#torrent_container')[0].style.right = '0px';
    1697                         setInnerHTML( $('ul li#context_toggle_inspector')[0], 'Show Inspector' );
    1698                 }
    1699 
    1700                 this.setPref( Prefs._ShowInspector, false );
    1701                 this.refreshDisplay( );
    1702         },
    1703 
    1704         /*
    1705          * Toggle the visibility of the filter bar
    1706          */
    1707         toggleFilter: function() {
    1708                 if( this[Prefs._ShowFilter] )
    1709                         this.hideFilter();
    1710                 else
    1711                         this.showFilter();
    1712         },
    1713 
    1714         showFilter: function( ) {
    1715                 var container_top = parseInt($('#torrent_container').position().top) + $('#torrent_filter_bar').height() + 1;
    1716                 $('#torrent_container').css('top', container_top + 'px');
    1717                 $('#torrent_filter_bar').show();
    1718                 this.setPref( Prefs._ShowFilter, true );
    1719         },
    1720 
    1721         hideFilter: function()
    1722         {
    1723                 var container_top = parseInt($('#torrent_container').css('top')) - $('#torrent_filter_bar').height() - 1;
    1724                 $('#torrent_container').css('top', container_top + 'px');
    1725                 $('#torrent_filter_bar').hide();
    1726                 this.setPref( Prefs._ShowFilter, false );
    1727                 this.setFilter( Prefs._FilterAll );
     1522                        setInnerHTML($('ul li#context_toggle_inspector')[0], 'Show Inspector');
     1523                }
     1524
     1525                this.setPref(Prefs._ShowInspector, false);
     1526                this.refreshDisplay();
    17281527        },
    17291528
    17301529        refreshMetaData: function(ids) {
    17311530                var tr = this;
    1732                 this.remote.getMetaDataFor(ids, function(active, removed){ tr.updateMetaData(active); });
    1733         },
    1734 
    1735         updateMetaData: function( torrents )
     1531                this.remote.getMetaDataFor(ids, function(active) { tr.updateMetaData(active); });
     1532        },
     1533
     1534        updateMetaData: function(torrents)
    17361535        {
    17371536                var tr = this;
    17381537                var refresh_files_for = [ ];
    17391538                var selected_torrents = this.getSelectedTorrents();
    1740                 jQuery.each( torrents, function( ) {
     1539                jQuery.each(torrents, function() {
    17411540                        var t = tr._torrents[ this.id ];
    1742                         if( t ) {
    1743                                 t.refreshMetaData( this );
    1744                                 if( selected_torrents.indexOf(t) != -1 )
    1745                                         refresh_files_for.push( t.getId( ) );
     1541                        if (t) {
     1542                                t.refreshMetaData(this);
     1543                                if (selected_torrents.indexOf(t) != -1)
     1544                                        refresh_files_for.push(t.getId());
    17461545                        }
    1747                 } );
    1748                 if( refresh_files_for.length > 0 )
    1749                         tr.remote.loadTorrentFiles( refresh_files_for );
     1546                });
     1547                if (refresh_files_for.length > 0)
     1548                        tr.remote.loadTorrentFiles(refresh_files_for);
    17501549        },
    17511550
     
    17551554                        ids = 'recently-active';
    17561555
    1757                 this.remote.getUpdatedDataFor(ids, function(active, removed){ tr.updateTorrentsData(active, removed); });
    1758         },
    1759 
    1760         updateTorrentsData: function( updated, removed_ids ) {
     1556                this.remote.getUpdatedDataFor(ids, function(active, removed) { tr.updateTorrentsData(active, removed); });
     1557        },
     1558
     1559        updateTorrentsData: function(updated, removed_ids) {
    17611560                var tr = this;
    17621561                var new_torrent_ids = [];
     
    17641563                var selected_torrents = this.getSelectedTorrents();
    17651564
    1766                 for( var i=0, o; o=updated[i]; ++i ) {
     1565                for (var i=0, o; o=updated[i]; ++i) {
    17671566                        var t = tr._torrents[o.id];
    17681567                        if (t == null)
     
    17701569                        else {
    17711570                                t.refresh(o);
    1772                                 if( selected_torrents.indexOf(t) != -1 )
     1571                                if (selected_torrents.indexOf(t) != -1)
    17731572                                        refresh_files_for.push(t.getId());
    17741573                        }
    17751574                }
    17761575
    1777                 if(refresh_files_for.length > 0)
    1778                         tr.remote.loadTorrentFiles( refresh_files_for );
    1779 
    1780                 if(new_torrent_ids.length > 0)
    1781                         tr.remote.getInitialDataFor(new_torrent_ids, function(torrents){ tr.addTorrents(torrents) } );
    1782 
    1783                 var removedAny = tr.deleteTorrents(removed_ids);
    1784 
    1785                 if( ( new_torrent_ids.length != 0 ) || removedAny ) {
     1576                if (refresh_files_for.length > 0)
     1577                        tr.remote.loadTorrentFiles(refresh_files_for);
     1578
     1579                if (new_torrent_ids.length > 0)
     1580                        tr.remote.getInitialDataFor(new_torrent_ids, function(torrents) {tr.addTorrents(torrents);});
     1581
     1582                tr.deleteTorrents(removed_ids);
     1583
     1584                if (new_torrent_ids.length != 0) {
    17861585                        tr.hideiPhoneAddressbar();
    1787                         tr.deselectAll( true );
    1788                 }
    1789 
    1790                 this.refilter();
    1791         },
    1792 
    1793         updateTorrentsFileData: function( torrents ){
    1794                 for( var i=0, o; o=torrents[i]; ++i ) {
     1586                        tr.deselectAll(true);
     1587                }
     1588        },
     1589
     1590        updateTorrentsFileData: function(torrents) {
     1591                for (var i=0, o; o=torrents[i]; ++i) {
    17951592                        var t = this._torrents[o.id];
    1796                         if( t !== null ) {
    1797                                 t.refreshFiles( o );
    1798                                 if( t === this._files_torrent )
     1593                        if (t) {
     1594                                t.refreshFiles(o);
     1595                                if (t === this._files_torrent)
    17991596                                        this.refreshFileView();
    18001597                        }
     
    18021599        },
    18031600
    1804         initializeAllTorrents: function(){
    1805                 var tr = this;
    1806                 this.remote.getInitialDataFor( null ,function(torrents) { tr.addTorrents(torrents); } );
    1807         },
    1808 
    1809         onRowClicked: function( ev, row )
     1601        initializeAllTorrents: function() {
     1602                var tr = this;
     1603                this.remote.getInitialDataFor(null ,function(torrents) { tr.addTorrents(torrents); });
     1604        },
     1605
     1606        onRowClicked: function(ev, row)
    18101607        {
    18111608                // Prevents click carrying to parent element
     
    18181615                // Need settable meta-key and ctrl-key variables for mac emulation
    18191616                var meta_key = ev.metaKey;
    1820                 var ctrl_key = ev.ctrlKey;
    1821                 if (ev.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) {
     1617                if (ev.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1)
    18221618                        meta_key = true;
    1823                         ctrl_key = false;
    1824                 }
    18251619
    18261620                // Shift-Click - selects a range from the last-clicked row to this one
    18271621                if (iPhone) {
    1828                         if ( row.isSelected() )
     1622                        if (row.isSelected())
    18291623                                this.showInspector();
    1830                         this.setSelectedRow( row, true );
     1624                        this.setSelectedRow(row, true);
    18311625
    18321626                } else if (ev.shiftKey) {
    1833                         this.selectRange( row, true );
     1627                        this.selectRange(row, true);
    18341628                        // Need to deselect any selected text
    18351629                        window.focus();
     
    18371631                // Apple-Click, not selected
    18381632                } else if (!row.isSelected() && meta_key) {
    1839                         this.selectRow( row, true );
     1633                        this.selectRow(row, true);
    18401634
    18411635                // Regular Click, not selected
    18421636                } else if (!row.isSelected()) {
    1843                         this.setSelectedRow( row, true );
     1637                        this.setSelectedRow(row, true);
    18441638
    18451639                // Apple-Click, selected
    18461640                } else if (row.isSelected() && meta_key) {
    1847                         this.deselectRow( row );
     1641                        this.deselectRow(row);
    18481642
    18491643                // Regular Click, selected
    18501644                } else if (row.isSelected()) {
    1851                         this.setSelectedRow( row, true );
     1645                        this.setSelectedRow(row, true);
    18521646                }
    18531647
    1854                 this._last_row_clicked = row;
     1648                this._last_torrent_clicked = row.getTorrent().getId();
    18551649        },
    18561650
    1857         addTorrents: function( new_torrents )
    1858         {
    1859                 var tr = this;
    1860 
    1861                 for( var i=0, row; row=new_torrents[i]; ++i ) {
    1862                         var t = new Torrent( this, row );
     1651        addTorrents: function(new_torrents)
     1652        {
     1653                var tr = this;
     1654                var key = 'dataChanged';
     1655
     1656                for (var i=0, row; row=new_torrents[i]; ++i) {
     1657                        var t = new Torrent(row);
     1658                        $(t).bind(key,function() {tr.refilterSoon();});
    18631659                        this._torrents[t.getId()] = t;
    18641660                }
    18651661
    1866                 this.refilter( );
    1867         },
    1868 
    1869         deleteTorrents: function(torrent_ids){
    1870 
    1871                 if(typeof torrent_ids == 'undefined')
    1872                         return false
    1873 
    1874                 var keep = [ ]
    1875                 var elements = [ ]
    1876                 var removedAny = false
    1877 
    1878                 for(var i=0, row; row=this._rows[i]; ++i) {
    1879                         var tor = row.getTorrent()
    1880                         var tid = tor ? tor.getId() : -1
    1881                         if( torrent_ids.indexOf( tid ) == -1 )
    1882                                 keep.push( row )
    1883                         else {
    1884                                 removedAny = true
    1885                                 delete this._torrents[ tid ]
    1886                                 $(row.getElement()).remove()
    1887                         }
    1888                 }
    1889 
    1890                 this._rows = keep
    1891 
    1892                 return removedAny
    1893         },
    1894 
    1895         refreshDisplay: function( )
    1896         {
    1897                 var rows = this.getVisibleRows( );
    1898                 for( var i=0, row; row=rows[i]; ++i )
    1899                         row.render( this );
     1662                this.refilterSoon();
     1663        },
     1664
     1665        deleteTorrents: function(torrent_ids)
     1666        {
     1667                if (torrent_ids && torrent_ids.length)
     1668                {
     1669                        for (var i=0, id; i=torrent_ids[i]; ++i)
     1670                                delete this._torrents[id];
     1671                        this.refilter();
     1672                }
     1673        },
     1674
     1675        refreshDisplay: function()
     1676        {
     1677                for (var i=0, row; row=this._rows[i]; ++i)
     1678                        row.render(this);
    19001679        },
    19011680
     
    19031682         * Set the alternating background colors for torrents
    19041683         */
    1905         setTorrentBgColors: function( )
    1906         {
    1907                 var rows = this.getVisibleRows( );
    1908                 for( var i=0, row; row=rows[i]; ++i )
     1684        setTorrentBgColors: function()
     1685        {
     1686                for (var i=0, row; row=this._rows[i]; ++i)
    19091687                        row.setEven((i+1) % 2 == 0);
    19101688        },
     
    19121690        updateStatusbar: function()
    19131691        {
     1692                this.refreshFilterButton();
     1693
     1694                // up/down speed
     1695                var u=0, d=0;
    19141696                var torrents = this.getAllTorrents();
    1915                 var torrentCount = torrents.length;
    1916                 var visibleCount = this.getVisibleTorrents().length;
    1917 
    1918                 // calculate the overall speed
    1919                 var upSpeed = 0;
    1920                 var downSpeed = 0;
    1921                 for( var i=0, row; row=torrents[i]; ++i ) {
    1922                         upSpeed += row.getUploadSpeed( );
    1923                         downSpeed += row.getDownloadSpeed( );
    1924                 }
    1925 
    1926                 // update torrent count label
    1927                 var s;
    1928                 if( torrentCount == visibleCount )
    1929                         s = torrentCount + ' Transfers';
    1930                 else
    1931                         s = visibleCount + ' of ' + torrentCount + ' Transfers';
    1932                 setInnerHTML( $('#torrent_global_transfer')[0], s );
    1933 
    1934                 // update the speeds
    1935                 s = Transmission.fmt.speedBps( upSpeed );
    1936                 if( iPhone ) s = 'UL: ' + s;
    1937                 setInnerHTML( $('#torrent_global_upload')[0], s );
    1938 
    1939                 // download speeds
    1940                 s = Transmission.fmt.speedBps( downSpeed );
    1941                 if( iPhone ) s = 'DL: ' + s;
    1942                 setInnerHTML( $('#torrent_global_download')[0], s );
     1697                for (var i=0, row; row=torrents[i]; ++i) {
     1698                        u += row.getUploadSpeed();
     1699                        d += row.getDownloadSpeed();
     1700                }
     1701                setInnerHTML($('#statusbar #speed-up-label')[0], '&uarr; ' + Transmission.fmt.speedBps(u));
     1702                setInnerHTML($('#statusbar #speed-dn-label')[0], '&darr; ' + Transmission.fmt.speedBps(d));
    19431703        },
    19441704
     
    19731733                                args.dataType = 'xml';
    19741734                                args.iframe = true;
    1975                                 args.success = function( data ) {
     1735                                args.success = function() {
    19761736                                        tr.refreshTorrents();
    1977                                         tr.togglePeriodicRefresh( true );
     1737                                        tr.togglePeriodicRefresh(true);
    19781738                                };
    1979                                 tr.togglePeriodicRefresh( false );
    1980                                 $('#torrent_upload_form').ajaxSubmit( args );
     1739                                tr.togglePeriodicRefresh(false);
     1740                                $('#torrent_upload_form').ajaxSubmit(args);
    19811741                        }
    19821742                }
     
    19841744
    19851745        removeSelectedTorrents: function() {
    1986                 var torrents = this.getSelectedTorrents( );
    1987                 if( torrents.length )
    1988                         this.promptToRemoveTorrents( torrents );
     1746                var torrents = this.getSelectedTorrents();
     1747                if (torrents.length)
     1748                        this.promptToRemoveTorrents(torrents);
    19891749        },
    19901750
    19911751        removeSelectedTorrentsAndData: function() {
    1992                 var torrents = this.getSelectedTorrents( );
    1993                 if( torrents.length )
    1994                         this.promptToRemoveTorrentsAndData( torrents );
    1995         },
    1996 
    1997         promptToRemoveTorrents:function( torrents )
    1998         {
    1999                 if( torrents.length == 1 )
     1752                var torrents = this.getSelectedTorrents();
     1753                if (torrents.length)
     1754                        this.promptToRemoveTorrentsAndData(torrents);
     1755        },
     1756
     1757        promptToRemoveTorrents:function(torrents)
     1758        {
     1759                if (torrents.length == 1)
    20001760                {
    20011761                        var torrent = torrents[0];
    20021762                        var header = 'Remove ' + torrent.getName() + '?';
    20031763                        var message = 'Once removed, continuing the transfer will require the torrent file. Are you sure you want to remove it?';
    2004                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
     1764                        dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
    20051765                }
    20061766                else
     
    20081768                        var header = 'Remove ' + torrents.length + ' transfers?';
    20091769                        var message = 'Once removed, continuing the transfers will require the torrent files. Are you sure you want to remove them?';
    2010                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrents', torrents );
    2011                 }
    2012         },
    2013 
    2014         promptToRemoveTorrentsAndData:function( torrents )
    2015         {
    2016                 if( torrents.length == 1 )
     1770                        dialog.confirm(header, message, 'Remove', 'transmission.removeTorrents', torrents);
     1771                }
     1772        },
     1773
     1774        promptToRemoveTorrentsAndData:function(torrents)
     1775        {
     1776                if (torrents.length == 1)
    20171777                {
    20181778                        var torrent = torrents[0],
    20191779                                header = 'Remove ' + torrent.getName() + ' and delete data?',
    20201780                                message = 'All data downloaded for this torrent will be deleted. Are you sure you want to remove it?';
    2021                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
     1781                        dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
    20221782                }
    20231783                else
     
    20251785                        var header = 'Remove ' + torrents.length + ' transfers and delete data?',
    20261786                                message = 'All data downloaded for these torrents will be deleted. Are you sure you want to remove them?';
    2027                         dialog.confirm( header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents );
    2028                 }
    2029         },
    2030 
    2031         removeTorrents: function( torrents ) {
    2032                 var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); } );
    2033                 var tr = this;
    2034                 this.remote.removeTorrents( torrent_ids, function(){ tr.refreshTorrents() } );
    2035         },
    2036 
    2037         removeTorrentsAndData: function( torrents ) {
    2038                 this.remote.removeTorrentsAndData( torrents );
     1787                        dialog.confirm(header, message, 'Remove', 'transmission.removeTorrentsAndData', torrents);
     1788                }
     1789        },
     1790
     1791        removeTorrents: function(torrents) {
     1792                var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
     1793                var tr = this;
     1794                this.remote.removeTorrents(torrent_ids, function() { tr.refreshTorrents();});
     1795        },
     1796
     1797        removeTorrentsAndData: function(torrents) {
     1798                this.remote.removeTorrentsAndData(torrents);
    20391799        },
    20401800
    20411801        verifySelectedTorrents: function() {
    2042                 this.verifyTorrents( this.getSelectedTorrents( ) );
     1802                this.verifyTorrents(this.getSelectedTorrents());
    20431803        },
    20441804
    20451805        reannounceSelectedTorrents: function() {
    2046                 this.reannounceTorrents( this.getSelectedTorrents( ) );
    2047         },
    2048 
    2049         startSelectedTorrents: function( force ) {
    2050                 this.startTorrents( this.getSelectedTorrents( ), force );
    2051         },
    2052         startAllTorrents: function( ) {
    2053                 this.startTorrents( this.getAllTorrents( ), false );
    2054         },
    2055         startTorrent: function( torrent ) {
    2056                 this.startTorrents( [ torrent ], false );
    2057         },
    2058         startTorrents: function( torrents, force ) {
    2059                 var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); } );
    2060                 var tr = this;
    2061                 this.remote.startTorrents( torrent_ids, force, function(){ tr.refreshTorrents(torrent_ids) } );
    2062         },
    2063         verifyTorrent: function( torrent ) {
    2064                 this.verifyTorrents( [ torrent ] );
    2065         },
    2066         verifyTorrents: function( torrents ) {
    2067                 var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); } );
    2068                 var tr = this;
    2069                 this.remote.verifyTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
    2070         },
    2071 
    2072         reannounceTorrent: function( torrent ) {
    2073                 this.reannounceTorrents( [ torrent ] );
    2074         },
    2075         reannounceTorrents: function( torrents ) {
    2076                 var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); } );
    2077                 var tr = this;
    2078                 this.remote.reannounceTorrents( torrent_ids, function(){ tr.refreshTorrents(torrent_ids) } );
    2079         },
    2080 
    2081         stopSelectedTorrents: function( ) {
    2082                 this.stopTorrents( this.getSelectedTorrents( ) );
    2083         },
    2084         stopAllTorrents: function( ) {
    2085                 this.stopTorrents( this.getAllTorrents( ) );
    2086         },
    2087         stopTorrent: function( torrent ) {
    2088                 this.stopTorrents( [ torrent ] );
    2089         },
    2090         stopTorrents: function( torrents ) {
    2091                 var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); } );
    2092                 var tr = this;
    2093                 this.remote.stopTorrents( torrent_ids,  function(){ tr.refreshTorrents(torrent_ids )} );
     1806                this.reannounceTorrents(this.getSelectedTorrents());
     1807        },
     1808
     1809        startSelectedTorrents: function(force) {
     1810                this.startTorrents(this.getSelectedTorrents(), force);
     1811        },
     1812        startAllTorrents: function() {
     1813                this.startTorrents(this.getAllTorrents(), false);
     1814        },
     1815        startTorrent: function(torrent) {
     1816                this.startTorrents([ torrent ], false);
     1817        },
     1818        startTorrents: function(torrents, force) {
     1819                var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
     1820                var tr = this;
     1821                this.remote.startTorrents(torrent_ids, force, function() { tr.refreshTorrents(torrent_ids); });
     1822        },
     1823        verifyTorrent: function(torrent) {
     1824                this.verifyTorrents([ torrent ]);
     1825        },
     1826        verifyTorrents: function(torrents) {
     1827                var tr = this;
     1828                var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
     1829                this.remote.verifyTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
     1830        },
     1831
     1832        reannounceTorrent: function(torrent) {
     1833                this.reannounceTorrents([ torrent ]);
     1834        },
     1835        reannounceTorrents: function(torrents) {
     1836                var tr = this;
     1837                var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
     1838                this.remote.reannounceTorrents(torrent_ids, function() { tr.refreshTorrents(torrent_ids); });
     1839        },
     1840
     1841        stopSelectedTorrents: function() {
     1842                this.stopTorrents(this.getSelectedTorrents());
     1843        },
     1844        stopAllTorrents: function() {
     1845                this.stopTorrents(this.getAllTorrents());
     1846        },
     1847        stopTorrent: function(torrent) {
     1848                this.stopTorrents([ torrent ]);
     1849        },
     1850        stopTorrents: function(torrents) {
     1851                var torrent_ids = jQuery.map(torrents, function(t) { return t.getId(); });
     1852                var tr = this;
     1853                this.remote.stopTorrents(torrent_ids,   function() { tr.refreshTorrents(torrent_ids);});
    20941854        },
    20951855        changeFileCommand: function(command, rows) {
     
    20981858
    20991859        hideiPhoneAddressbar: function(timeInSeconds) {
    2100                 var tr = this;
    2101                 if( iPhone ) {
     1860                if (iPhone) {
    21021861                        var delayLength = timeInSeconds ? timeInSeconds*1000 : 150;
    21031862                        // not currently supported on iPhone
    2104                         if(/*document.body.scrollTop!=1 && */scroll_timeout==null) {
    2105                                 scroll_timeout = setTimeout(function(){ tr.doToolbarHide(); }, delayLength);
     1863                        if (/*document.body.scrollTop!=1 && */scroll_timeout==null) {
     1864                                var tr = this;
     1865                                scroll_timeout = setTimeout(function() {tr.doToolbarHide();}, delayLength);
    21061866                        }
    21071867                }
     
    21131873
    21141874        // Queue
    2115         moveTop: function( ) {
    2116                 var torrent_ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.getId(); } );
    2117                 var tr = this;
    2118                 this.remote.moveTorrentsToTop( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} );
    2119         },
    2120         moveUp: function( ) {
    2121                 var torrent_ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.getId(); } );
    2122                 var tr = this;
    2123                 this.remote.moveTorrentsUp( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} );
    2124         },
    2125         moveDown: function( ) {
    2126                 var torrent_ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.getId(); } );
    2127                 var tr = this;
    2128                 this.remote.moveTorrentsDown( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} );
    2129         },
    2130         moveBottom: function( ) {
    2131                 var torrent_ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.getId(); } );
    2132                 var tr = this;
    2133                 this.remote.moveTorrentsToBottom( torrent_ids, function(){ tr.refreshTorrents(torrent_ids )} );
     1875        moveTop: function() {
     1876                var tr = this;
     1877                var ids = this.getSelectedTorrentIds();
     1878                this.remote.moveTorrentsToTop(torrent_ids, function() { tr.refreshTorrents(ids);});
     1879        },
     1880        moveUp: function() {
     1881                var tr = this;
     1882                var ids = this.getSelectedTorrentIds();
     1883                this.remote.moveTorrentsUp(torrent_ids, function() { tr.refreshTorrents(ids);});
     1884        },
     1885        moveDown: function() {
     1886                var tr = this;
     1887                var ids = this.getSelectedTorrentIds();
     1888                this.remote.moveTorrentsDown(torrent_ids, function() { tr.refreshTorrents(ids);});
     1889        },
     1890        moveBottom: function() {
     1891                var tr = this;
     1892                var ids = this.getSelectedTorrentIds();
     1893                this.remote.moveTorrentsToBottom(torrent_ids, function() { tr.refreshTorrents(ids);});
    21341894        },
    21351895
     
    21391899        ***/
    21401900
    2141         onToggleRunningClicked: function( ev )
    2142         {
    2143                 var torrent = ev.data.r.getTorrent( );
    2144 
    2145                 if( torrent.isStopped( ) )
    2146                         this.startTorrent( torrent );
     1901        onToggleRunningClicked: function(ev)
     1902        {
     1903                var torrent = ev.data.r.getTorrent();
     1904
     1905                if (torrent.isStopped())
     1906                        this.startTorrent(torrent);
    21471907                else
    2148                         this.stopTorrent( torrent );
    2149         },
    2150 
    2151         refilter: function()
    2152         {
    2153                 // decide which torrents to keep showing
    2154                 var allTorrents = this.getAllTorrents( );
    2155                 var keep = [ ];
    2156                 for( var i=0, t; t=allTorrents[i]; ++i )
    2157                         if( t.test( this[Prefs._FilterMode], this._current_search ) )
    2158                                 keep.push( t );
    2159 
    2160                 // sort the keepers
    2161                 Torrent.sortTorrents( keep, this[Prefs._SortMethod],
    2162                                             this[Prefs._SortDirection] );
    2163 
    2164                 // make a backup of the selection
    2165                 var sel = this.getSelectedTorrents( );
    2166 
    2167                 // add rows it there aren't enough
    2168                 if( this._rows.length < keep.length ) {
    2169                         var tr = this;
    2170                         var fragment = document.createDocumentFragment( );
    2171