Changeset 12690


Ignore:
Timestamp:
Aug 16, 2011, 6:49:26 PM (10 years ago)
Author:
jordan
Message:

(trunk web) #3624 "Compact mode is very slow for large number of torrents" -- fixed.

Location:
trunk/web
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/web/index.html

    r12633 r12690  
    2727                <script type="text/javascript" src="./javascript/transmission.js"></script>
    2828                <script type="text/javascript" src="./javascript/torrent.js"></script>
     29                <script type="text/javascript" src="./javascript/torrent-renderer.js"></script>
     30                <script type="text/javascript" src="./javascript/file-row.js"></script>
    2931                <script type="text/javascript" src="./javascript/dialog.js"></script>
    3032                <script type="text/javascript" src="./javascript/formatter.js"></script>
     
    195197
    196198                        <div style="display:none;" class="inspector_container" id="inspector_tab_files_container">
    197                                 <div id="inspector_file_list">
    198                                         <ul id="select_all_button_container">
    199                                                 <li id="files_deselect_all" class="select_all_button">Deselect All</li>
    200                                                 <li id="files_select_all" class="select_all_button">Select All</li>
    201                                         </ul>
     199                                <div id="select_all_button_container">
     200                                        <div id="files_deselect_all" class="select_all_button">Deselect All</div>
     201                                        <div id="files_select_all" class="select_all_button">Select All</div>
    202202                                </div>
     203                                <ul id="inspector_file_list">
     204                                </ul>
    203205                        </div><!-- id="inspector_tab_files_container" -->
    204206
  • trunk/web/javascript/common.js

    r12611 r12690  
    1414// degrades gracefully let's not worry too much.
    1515var Safari3 = testSafari3();
    16 var iPhone = RegExp("(iPhone|iPod)").test(navigator.userAgent);
     16var iPhone = RegExp("(iPhone|iPod|Android)").test(navigator.userAgent);
    1717if (iPhone) var scroll_timeout;
    1818
  • trunk/web/javascript/torrent.js

    r12650 r12690  
    77 */
    88
    9 function Torrent( transferListParent, fileListParent, controller, data) {
    10         this.initialize( transferListParent, fileListParent, controller, data);
     9function Torrent( controller, data) {
     10        this.initialize( controller, data);
    1111}
    1212
     
    1919Torrent._StatusSeedWait        = 5; /* queeud to seed */
    2020Torrent._StatusSeed            = 6; /* seeding */
    21 
    22 /*
    23 Torrent._StatusWaitingToCheck  = 1;
    24 Torrent._StatusChecking        = 2;
    25 Torrent._StatusDownloading     = 4;
    26 Torrent._StatusSeeding         = 8;
    27 */
    2821
    2922Torrent._InfiniteTimeRemaining = 215784000; // 999 Hours - may as well be infinite
     
    8679         * Constructor
    8780         */
    88         initialize: function( transferListParent, fileListParent, controller, data) {
     81        initialize: function( controller, data) {
    8982                this._id            = data.id;
    9083                this._hashString    = data.hashString;
     
    9285                this._trackerStats  = this.buildTrackerStats(data.trackerStats);
    9386                this._file_model    = [ ];
    94                 this._file_view     = [ ];
    9587                this.initMetaData( data );
    96 
    97                 // Create a new <li> element
    98                 var top_e = document.createElement( 'li' );
    99                 top_e.className = 'torrent';
    100                 top_e.id = 'torrent_' + data.id;
    101                 top_e._torrent = this;
    102                 var element = $(top_e);
    103                 $(element).bind('dblclick', function(e) { transmission.toggleInspector(); });
    104                 element._torrent = this;
    105                 element._id = this._id;
    106                 this._element = element;
    107                 this._controller = controller;
    108                 controller._rows.push( element );
    109 
    110                 // Create the 'name' <div>
    111                 var e = document.createElement( 'div' );
    112                 e.className = 'torrent_name';
    113                 top_e.appendChild( e );
    114                 element._name_container = e;
    115 
    116                 // Create the 'peer details' <div>
    117                 e = document.createElement( 'div' );
    118                 e.className = 'torrent_peer_details';
    119                 top_e.appendChild( e );
    120                 element._peer_details_container = e;
    121 
    122                 //Create a progress bar container
    123                 top_a = document.createElement( 'div' );
    124                 top_a.className = 'torrent_progress_bar_container';
    125                 element._progress_bar_container = top_a;
    126 
    127                 // Create the 'in progress' bar
    128                 e = document.createElement( 'div' );
    129                 e.className = 'torrent_progress_bar incomplete';
    130                 e.style.width = '0%';
    131                 top_a.appendChild( e );
    132                 element._progress_complete_container = e;
    133 
    134                 // Create the 'incomplete' bar (initially hidden)
    135                 e = document.createElement( 'div' );
    136                 e.className = 'torrent_progress_bar incomplete';
    137                 e.style.display = 'none';
    138                 top_a.appendChild( e );
    139                 element._progress_incomplete_container = e;
    140 
    141                 //Add the progress bar container to the torrent
    142                 top_e.appendChild(top_a);
    143 
    144                 // Add the pause/resume button - don't specify the
    145                 // image or alt text until the 'refresh()' function
    146                 // (depends on torrent state)
    147                 var image = document.createElement( 'div' );
    148                 image.className = 'torrent_pause';
    149                 e = document.createElement( 'a' );
    150                 e.appendChild( image );
    151                 top_e.appendChild( e );
    152                 element._pause_resume_button_image = image;
    153                 if (!iPhone) $(e).bind('click', function(e) { element._torrent.clickPauseResumeButton(e); });
    154 
    155                 // Create the 'progress details' <div>
    156                 e = document.createElement( 'div' );
    157                 e.className = 'torrent_progress_details';
    158                 top_e.appendChild( e );
    159                 element._progress_details_container = e;
    160 
    161                 // Set the torrent click observer
    162                 element.bind('click', function(e){ element._torrent.clickTorrent(e) });
    163 
    164                 // Safari hack - first torrent needs to be moved down for some reason. Seems to be ok when
    165                 // using <li>'s in straight html, but adding through the DOM gets a bit odd.
    166                 if ($.browser.safari)
    167                         this._element.css('margin-top', '7px');
    168 
    169                 this.initializeTorrentFilesInspectorGroup( fileListParent );
    17088
    17189                // Update all the labels etc
    17290                this.refresh(data);
    173 
    174                 // insert the element
    175                 transferListParent.appendChild(top_e);
    176         },
    177 
    178         initializeTorrentFilesInspectorGroup: function( fileListParent ) {
    179                 var e = document.createElement( 'ul' );
    180                 e.className = 'inspector_torrent_file_list inspector_group';
    181                 e.style.display = 'none';
    182                 fileListParent.appendChild( e );
    183                 this._fileList = e;
    184         },
    185 
    186         fileList: function() {
    187                 return $(this._fileList);
    18891        },
    18992
     
    222125         *--------------------------------------------*/
    223126
    224         /* Return the DOM element for this torrent (a <LI> element) */
    225         element: function() {
    226                 return this._element;
    227         },
    228 
    229         setElement: function( element ) {
    230                 this._element = element;
    231                 element._torrent = this;
    232                 element[0]._torrent = this;
    233                 this.refreshHTML( );
    234         },
    235 
    236         activity: function() { return this._download_speed + this._upload_speed; },
     127        activity: function() { return this.downloadSpeed() + this.uploadSpeed(); },
    237128        comment: function() { return this._comment; },
    238129        completed: function() { return this._completed; },
     
    247138                                         || this.webseedsSendingToUs() > 0
    248139                                         || this.state() == Torrent._StatusCheck; },
     140        isStopped: function() { return this.state() === Torrent._StatusStopped; },
    249141        isActive: function() { return this.state() != Torrent._StatusStopped; },
    250142        isDownloading: function() { return this.state() == Torrent._StatusDownload; },
    251143        isFinished: function() { return this._isFinishedSeeding; },
     144        isDone: function() { return this._leftUntilDone < 1; },
    252145        isSeeding: function() { return this.state() == Torrent._StatusSeed; },
    253146        name: function() { return this._name; },
    254147        queuePosition: function() { return this._queue_position; },
    255         isQueued: function() { return ( this.state() == Torrent._StatusSeedWait )
    256                                    || ( this.state() == Torrent._StatusDownloadWait ); },
    257148        webseedsSendingToUs: function() { return this._webseeds_sending_to_us; },
    258149        peersSendingToUs: function() { return this._peers_sending_to_us; },
     
    284175        uploadSpeed: function() { return this._upload_speed; },
    285176        uploadTotal: function() { return this._upload_total; },
    286         showFileList: function() {
    287                 if(this.fileList().is(':visible'))
    288                         return;
    289                 this.ensureFileListExists();
    290                 this.refreshFileView();
    291                 this.fileList().show();
    292         },
    293         hideFileList: function() { this.fileList().hide(); },
    294         seedRatioLimit: function(){
     177        seedRatioLimit: function(controller){
    295178                switch( this._seed_ratio_mode ) {
    296                         case Torrent._RatioUseGlobal: return this._controller.seedRatioLimit();
     179                        case Torrent._RatioUseGlobal: return controller.seedRatioLimit();
    297180                        case Torrent._RatioUseLocal:  return this._seed_ratio_limit;
    298181                        default:                      return -1;
    299182                }
    300183        },
    301 
    302         /*--------------------------------------------
    303          *
    304          *  E V E N T   F U N C T I O N S
    305          *
    306          *--------------------------------------------*/
    307 
    308         /*
    309          * Process a click event on this torrent
    310          */
    311         clickTorrent: function( event )
    312         {
    313                 // Prevents click carrying to parent element
    314                 // which deselects all on click
    315                 event.stopPropagation();
    316                 // but still hide the context menu if it is showing
    317                 $('#jqContextMenu').hide();
    318 
    319                 var torrent = this;
    320 
    321                 // 'Apple' button emulation on PC :
    322                 // Need settable meta-key and ctrl-key variables for mac emulation
    323                 var meta_key = event.metaKey;
    324                 var ctrl_key = event.ctrlKey;
    325                 if (event.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) {
    326                         meta_key = true;
    327                         ctrl_key = false;
    328                 }
    329 
    330                 // Shift-Click - Highlight a range between this torrent and the last-clicked torrent
    331                 if (iPhone) {
    332                         if ( torrent.isSelected() )
    333                                 torrent._controller.showInspector();
    334                         torrent._controller.setSelectedTorrent( torrent, true );
    335 
    336                 } else if (event.shiftKey) {
    337                         torrent._controller.selectRange( torrent, true );
    338                         // Need to deselect any selected text
    339                         window.focus();
    340 
    341                 // Apple-Click, not selected
    342                 } else if (!torrent.isSelected() && meta_key) {
    343                         torrent._controller.selectTorrent( torrent, true );
    344 
    345                 // Regular Click, not selected
    346                 } else if (!torrent.isSelected()) {
    347                         torrent._controller.setSelectedTorrent( torrent, true );
    348 
    349                 // Apple-Click, selected
    350                 } else if (torrent.isSelected() && meta_key) {
    351                         torrent._controller.deselectTorrent( torrent, true );
    352 
    353                 // Regular Click, selected
    354                 } else if (torrent.isSelected()) {
    355                         torrent._controller.setSelectedTorrent( torrent, true );
    356                 }
    357 
    358                 torrent._controller.setLastTorrentClicked(torrent);
    359         },
    360 
    361         /*
    362          * Process a click event on the pause/resume button
    363          */
    364         clickPauseResumeButton: function( event )
    365         {
    366                 // prevent click event resulting in selection of torrent
    367                 event.stopPropagation();
    368 
    369                 // either stop or start the torrent
    370                 var torrent = this;
    371                 if( torrent.isActive( ) )
    372                         torrent._controller.stopTorrent( torrent );
    373                 else
    374                         torrent._controller.startTorrent( torrent );
    375         },
     184        getErrorMessage: function() {
     185                if( this._error  == Torrent._ErrTrackerWarning )
     186                        return 'Tracker returned a warning: ' + this._error_string;
     187                if( this._error  == Torrent._ErrTrackerError )
     188                        return 'Tracker returned an error: ' + this._error_string;
     189                if( this._error  == Torrent._ErrLocalError )
     190                        return 'Error: ' + this._error_string;
     191                return null;
     192        },
     193
    376194
    377195        /*--------------------------------------------
     
    381199         *--------------------------------------------*/
    382200
    383         refreshMetaData: function(data) {
     201        fireDataChanged: function()
     202        {
     203                $(this).trigger('dataChanged',[]);
     204        },
     205
     206        refreshMetaData: function(data)
     207        {
    384208                this.initMetaData( data );
    385                 this.ensureFileListExists();
    386                 this.refreshFileView();
    387                 this.refreshHTML( );
    388         },
    389 
    390         refresh: function(data) {
    391                 this.refreshData( data );
    392                 this.refreshHTML( );
    393         },
    394 
    395         /*
    396          * Refresh display
    397          */
    398         refreshData: function(data)
     209                this.fireDataChanged();
     210        },
     211
     212        refresh: function(data)
    399213        {
    400214                if( this.needsMetaData() && ( data.metadataPercentComplete >= 1 ) )
     
    431245                if (data.fileStats)
    432246                        this.refreshFileModel( data );
     247
     248                this.fireDataChanged();
    433249        },
    434250
     
    445261        },
    446262
    447         getErrorMessage: function()
    448         {
    449                 if( this._error  == Torrent._ErrTrackerWarning )
    450                         return 'Tracker returned a warning: ' + this._error_string;
    451                 if( this._error  == Torrent._ErrTrackerError )
    452                         return 'Tracker returned an error: ' + this._error_string;
    453                 if( this._error  == Torrent._ErrLocalError )
    454                         return 'Error: ' + this._error_string;
    455                 return null;
    456         },
    457 
    458         formatUL: function() {
    459                 return 'UL: ' + Transmission.fmt.speedBps(this._upload_speed);
    460         },
    461         formatDL: function() {
    462                 return 'DL: ' + Transmission.fmt.speedBps(this._download_speed);
    463         },
    464 
    465         getPeerDetails: function()
    466         {
    467                 var c;
    468 
    469                 var compact_mode = this._controller[Prefs._CompactDisplayState];
    470                 if(( c = this.getErrorMessage( )))
    471                         return c;
    472 
    473                 var st = this.state( );
    474                 switch( st )
    475                 {
    476                         case Torrent._StatusStopped:
    477                         case Torrent._StatusCheckWait:
    478                         case Torrent._StatusDownloadWait:
    479                         case Torrent._StatusSeedWait:
    480                                 c = this.stateStr( );
    481                                 break;
    482 
    483                         case Torrent._StatusDownload:
    484                                 var a = [ ];
    485                                 if(!compact_mode)
    486                                         a.push( 'Downloading from', this.peersSendingToUs(), 'of', this._peers_connected, 'peers', '-' );
    487                                 a.push( this.formatDL(), this.formatUL() );
    488                                 c = a.join(' ');
    489                                 break;
    490 
    491                         case Torrent._StatusSeed:
    492                                 if(compact_mode){
    493                                         c = this.formatUL();
    494                                 } else {
    495                                         // 'Seeding to 13 of 22 peers - UL: 36.2 KiB/s'
    496                                         c = [ 'Seeding to', this.peersGettingFromUs(), 'of', this._peers_connected, 'peers', '-', this.formatUL() ].join(' ');
    497                                 }
    498                                 break;
    499 
    500                         case Torrent._StatusCheck:
    501                                 // 'Verifying local data (40% tested)'
    502                                 c = [ 'Verifying local data (', Transmission.fmt.percentString( 100.0 * this._recheckProgress ), '% tested)' ].join('');
    503                                 break;
    504                 }
    505                 return c;
    506         },
    507 
    508         refreshHTML: function() {
    509                 var c;
    510                 var e;
    511                 var progress_details;
    512                 var root = this._element;
    513                 var MaxBarWidth = 100; // reduce this to make the progress bar shorter (%)
    514                 var compact_mode = this._controller[Prefs._CompactDisplayState];
    515                 var compact = '';
    516                 if(compact_mode){
    517                         compact = ' compact';
    518                         root._peer_details_container.style.display = 'none';
    519                 } else {
    520                         root._peer_details_container.style.display = 'block';
    521                 }
    522 
    523                 root._progress_details_container.className = 'torrent_progress_details'+compact
    524                 root._progress_bar_container.className = 'torrent_progress_bar_container'+compact;
    525                 root._name_container.className = 'torrent_name'+compact;
    526 
    527                 setInnerHTML( root._name_container, this._name );
    528 
    529                 // Add the progress bar
    530                 var notDone = this._leftUntilDone > 0;
    531 
    532                 // Fix for situation
    533                 // when a verifying/downloading torrent gets state seeding
    534                 if( this._state === Torrent._StatusSeeding )
    535                         notDone = false ;
    536 
    537                 if( this.needsMetaData() ){
    538                         var metaPercentComplete = this._metadataPercentComplete * 100;
    539                         progress_details = [ "Magnetized transfer - retrieving metadata (",
    540                                              Transmission.fmt.percentString( metaPercentComplete ),
    541                                              "%)" ].join('');
    542 
    543                         var empty = "";
    544                         if(metaPercentComplete == 0)
    545                                 empty = "empty";
    546 
    547                         root._progress_complete_container.style.width = metaPercentComplete + "%";
    548                         root._progress_complete_container.className = 'torrent_progress_bar in_progress meta ' + empty+compact;
    549                         root._progress_incomplete_container.style.width = 100 - metaPercentComplete + "%"
    550                         root._progress_incomplete_container.className = 'torrent_progress_bar incomplete compact meta'+compact;
    551                         root._progress_incomplete_container.style.display = 'block';
    552                 }
    553                 else if( notDone )
    554                 {
    555                         // Create the 'progress details' label
    556                         // Eg: '101 MiB of 631 MiB (16.02%) - 2 hr remaining'
    557 
    558                         c = [ Transmission.fmt.size( this._sizeWhenDone - this._leftUntilDone ),
    559                               ' of ', Transmission.fmt.size( this._sizeWhenDone ),
    560                               ' (', this.getPercentDoneStr(), '%)' ];
    561                         if( this.isActive( ) ) {
    562                                 c.push( ' - ' );
    563                                 if (this._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining )
    564                                         c.push( 'remaining time unknown' );
    565                                 else
    566                                         c.push( Transmission.fmt.timeInterval(this._eta) + ' remaining' );
    567                         }
    568                         progress_details = c.join('');
    569 
    570                         // Figure out the percent completed
    571                         var css_completed_width = ( this.getPercentDone() * MaxBarWidth ).toTruncFixed( 2 );
    572 
    573                         // Update the 'in progress' bar
    574                         e = root._progress_complete_container;
    575                         c = [ 'torrent_progress_bar'+compact,
    576                               this.isActive() ? 'in_progress' : 'incomplete_stopped' ];
    577                         if(css_completed_width === 0) { c.push( 'empty' ); }
    578                         e.className = c.join(' ');
    579                         e.style.width = css_completed_width + '%';
    580 
    581                         // Update the 'incomplete' bar
    582                         e = root._progress_incomplete_container;
    583                         e.className = 'torrent_progress_bar incomplete'
    584                         e.style.width =  (MaxBarWidth - css_completed_width) + '%';
    585                         e.style.display = 'block';
    586                 }
    587                 else
    588                 {
    589                         // Create the 'progress details' label
    590 
    591                         if( this._size == this._sizeWhenDone )
    592                         {
    593                                 // seed: '698.05 MiB'
    594                                 c = [ Transmission.fmt.size( this._size ) ];
    595                         }
    596                         else
    597                         {
    598                                 // partial seed: '127.21 MiB of 698.05 MiB (18.2%)'
    599                                 c = [ Transmission.fmt.size( this._sizeWhenDone ), ' of ', Transmission.fmt.size( this._size ),
    600                                       ' (', Transmission.fmt.percentString( 100.0 * this._sizeWhenDone / this._size ), '%)' ];
    601                         }
    602 
    603                         // append UL stats: ', uploaded 8.59 GiB (Ratio: 12.3)'
    604                         c.push( ', uploaded ', Transmission.fmt.size( this._upload_total ),
    605                                 ' (Ratio ', Transmission.fmt.ratioString( this._upload_ratio ), ')' );
    606 
    607                         // maybe append remaining time
    608                         if( this.isActive( ) && this.seedRatioLimit( ) > 0 )
    609                         {
    610                                 c.push(' - ');
    611 
    612                                 if (this._eta < 0 || this._eta >= Torrent._InfiniteTimeRemaining )
    613                                         c.push( 'remaining time unknown' );
    614                                 else
    615                                         c.push( Transmission.fmt.timeInterval(this._eta), ' remaining' );
    616                         }
    617 
    618                         progress_details = c.join('');
    619 
    620                         var status = this.isActive() ? 'complete' : 'complete_stopped';
    621 
    622                         if(this.isActive() && this.seedRatioLimit() > 0){
    623                                 status = 'complete seeding'
    624                                 var seedRatioRatio = this._upload_ratio / this.seedRatioLimit();
    625                                 var seedRatioPercent = Math.round( seedRatioRatio * 100 * MaxBarWidth ) / 100;
    626 
    627                                 // Set progress to percent seeded
    628                                 root._progress_complete_container.style.width = seedRatioPercent + '%';
    629 
    630                                 // Update the 'incomplete' bar
    631                                 root._progress_incomplete_container.className = 'torrent_progress_bar incomplete seeding'
    632                                 root._progress_incomplete_container.style.display = 'block';
    633                                 root._progress_incomplete_container.style.width = MaxBarWidth - seedRatioPercent + '%';
    634                         }
    635                         else
    636                         {
    637                                 // Hide the 'incomplete' bar
    638                                 root._progress_incomplete_container.className = 'torrent_progress_bar incomplete'
    639                                 root._progress_incomplete_container.style.display = 'none';
    640 
    641                                 // Set progress to maximum
    642                                 root._progress_complete_container.style.width = MaxBarWidth + '%';
    643                         }
    644 
    645                         // Update the 'in progress' bar
    646                         e = root._progress_complete_container;
    647                         e.className = 'torrent_progress_bar ' + status;
    648                 }
    649 
    650                 var hasError = this.getErrorMessage( ) != undefined;
    651                 // Update the progress details
    652                 if(compact_mode){
    653                         progress_details = this.getPeerDetails();
    654                         $(root._progress_details_container).toggleClass('error',hasError);
    655                 } else {
    656                         $(root._peer_details_container).toggleClass('error',hasError);
    657                 }
    658                 setInnerHTML( root._progress_details_container, progress_details );
    659 
    660                 if( compact ){
    661                         var width = root._progress_details_container.offsetLeft - root._name_container.offsetLeft;
    662                         root._name_container.style.width = width + 'px';
    663                 }
    664                 else {
    665                         root._name_container.style.width = '100%';
    666                 }
    667 
    668                 // Update the peer details and pause/resume button
    669                 e = root._pause_resume_button_image;
    670                 if ( this.state() === Torrent._StatusStopped ) {
    671                         e.alt = 'Resume';
    672                         e.className = "torrent_resume"+compact;
    673                 } else {
    674                         e.alt = 'Pause';
    675                         e.className = "torrent_pause"+compact;
    676                 }
    677 
    678                 setInnerHTML( root._peer_details_container, this.getPeerDetails( ) );
    679 
    680                 this.refreshFileView( );
    681         },
    682 
    683         refreshFileView: function() {
    684                 if( this._file_view.length )
    685                         for( var i=0; i<this._file_model.length; ++i )
    686                                 this._file_view[i].update( this._file_model[i] );
    687         },
    688 
    689         ensureFileListExists: function() {
    690                 if( this._file_view.length == 0 ) {
    691                         if(this._file_model.length == 1)
    692                                 this._fileList.className += ' single_file';
    693                         var v, e;
    694                         for( var i=0; i<this._file_model.length; ++i ) {
    695                                 v = new TorrentFile( this._file_model[i] );
    696                                 this._file_view[i] = v;
    697                                 e = v.domElement( );
    698                                 e.className = (i % 2 ? 'even' : 'odd') + ' inspector_torrent_file_list_entry';
    699                                 this._fileList.appendChild( e );
    700                         }
    701                 }
    702         },
    703 
    704         deleteFiles: function(){
    705                 if (this._fileList)
    706                         $(this._fileList).remove();
    707         },
    708 
    709         /*
    710          * Return true if this torrent is selected
    711          */
    712         isSelected: function() {
    713                 return this.element()[0].className.indexOf('selected') != -1;
    714         },
    715 
    716263        /**
    717264         * @param filter one of Prefs._Filter*
     
    863410        return torrents;
    864411};
    865 
    866 /**
    867  * @brief fast binary search to find a torrent
    868  * @param torrents an array of torrents sorted by Id
    869  * @param id the id to search for
    870  * @return the index, or -1
    871  */
    872 Torrent.indexOf = function( torrents, id )
    873 {
    874         var low = 0;
    875         var high = torrents.length;
    876         while( low < high ) {
    877                 var mid = Math.floor( ( low + high ) / 2 );
    878                 if( torrents[mid].id() < id )
    879                         low = mid + 1;
    880                 else
    881                         high = mid;
    882         }
    883         if( ( low < torrents.length ) && ( torrents[low].id() == id ) ) {
    884                 return low;
    885         } else {
    886                 return -1; // not found
    887         }
    888 };
    889 
    890 function TorrentFile(file_data) {
    891         this.initialize(file_data);
    892 }
    893 
    894 TorrentFile.prototype = {
    895         initialize: function(file_data) {
    896                 this._dirty = true;
    897                 this._torrent = file_data.torrent;
    898                 this._index = file_data.index;
    899                 var name = file_data.name.substring (file_data.name.lastIndexOf('/')+1);
    900                 this.readAttributes(file_data);
    901 
    902                 var li = document.createElement('li');
    903                 li.id = 't' + this._torrent.id() + 'f' + this._index;
    904                 li.classNameConst = 'inspector_torrent_file_list_entry ' + ((this._index%2)?'odd':'even');
    905                 li.className = li.classNameConst;
    906 
    907                 var wanted_div = document.createElement('div');
    908                 wanted_div.className = "file_wanted_control";
    909 
    910                 var pri_div = document.createElement('div');
    911                 pri_div.classNameConst = "file_priority_control";
    912                 pri_div.className = pri_div.classNameConst;
    913 
    914                 var file_div = document.createElement('div');
    915                 file_div.className = "inspector_torrent_file_list_entry_name";
    916                 file_div.innerHTML = name.replace(/([\/_\.])/g, "$1&#8203;");
    917 
    918                 var prog_div = document.createElement('div');
    919                 prog_div.className = "inspector_torrent_file_list_entry_progress";
    920 
    921                 li.appendChild(wanted_div);
    922                 li.appendChild(pri_div);
    923                 li.appendChild(file_div);
    924                 li.appendChild(prog_div);
    925 
    926                 this._element = li;
    927                 this._priority_control = pri_div;
    928                 this._progress = $(prog_div);
    929         },
    930 
    931         update: function(file_data) {
    932                 this.readAttributes(file_data);
    933                 this.refreshHTML();
    934         },
    935 
    936         isDone: function () {
    937                 return this._done >= this._size;
    938         },
    939 
    940         isEditable: function () {
    941                 return (this._torrent._file_model.length>1) && !this.isDone();
    942         },
    943 
    944         readAttributes: function(file_data) {
    945                 if( file_data.index !== undefined && file_data.index !== this._index ) {
    946                         this._index = file_data.index;
    947                         this._dirty = true;
    948                 }
    949                 if( file_data.bytesCompleted !== undefined && file_data.bytesCompleted !== this._done ) {
    950                         this._done   = file_data.bytesCompleted;
    951                         this._dirty = true;
    952                 }
    953                 if( file_data.length !== undefined && file_data.length !== this._size ) {
    954                         this._size   = file_data.length;
    955                         this._dirty = true;
    956                 }
    957                 if( file_data.priority !== undefined && file_data.priority !== this._prio ) {
    958                         this._prio   = file_data.priority;
    959                         this._dirty = true;
    960                 }
    961                 if( file_data.wanted !== undefined && file_data.wanted !== this._wanted ) {
    962                         this._wanted = file_data.wanted;
    963                         this._dirty = true;
    964                 }
    965         },
    966 
    967         element: function() {
    968                 return $(this._element);
    969         },
    970 
    971         domElement: function() {
    972                 return this._element;
    973         },
    974 
    975         setPriority: function(prio) {
    976                 if (this.isEditable()) {
    977                         var cmd;
    978                         switch( prio ) {
    979                                 case  1: cmd = 'priority-high';   break;
    980                                 case -1: cmd = 'priority-low';    break;
    981                                 default: cmd = 'priority-normal'; break;
    982                         }
    983                         this._prio = prio;
    984                         this._dirty = true;
    985                         this._torrent._controller.changeFileCommand( cmd, this._torrent, this );
    986                 }
    987         },
    988 
    989         setWanted: function(wanted, process) {
    990                 this._dirty = true;
    991                 this._wanted = wanted;
    992                 if(!iPhone)
    993                         this.element().toggleClass( 'skip', !wanted );
    994                 if (process) {
    995                         var command = wanted ? 'files-wanted' : 'files-unwanted';
    996                         this._torrent._controller.changeFileCommand(command, this._torrent, this);
    997                 }
    998         },
    999 
    1000         toggleWanted: function() {
    1001                 if (this.isEditable())
    1002                         this.setWanted( !this._wanted, true );
    1003         },
    1004 
    1005         refreshHTML: function() {
    1006                 if( this._dirty ) {
    1007                         this._dirty = false;
    1008                         this.refreshProgressHTML();
    1009                         this.refreshWantedHTML();
    1010                         this.refreshPriorityHTML();
    1011                 }
    1012         },
    1013 
    1014         refreshProgressHTML: function() {
    1015                 var c = [ Transmission.fmt.size(this._done),
    1016                           ' of ',
    1017                           Transmission.fmt.size(this._size),
    1018                           ' (',
    1019                           this._size ? Transmission.fmt.percentString(100 * this._done / this._size) : '100',
    1020                           '%)' ].join('');
    1021                 setInnerHTML(this._progress[0], c);
    1022         },
    1023 
    1024         refreshWantedHTML: function() {
    1025                 var e = this.domElement();
    1026                 var c = [ e.classNameConst ];
    1027                 if(!this._wanted) { c.push( 'skip' ); }
    1028                 if(this.isDone()) { c.push( 'complete' ); }
    1029                 e.className = c.join(' ');
    1030         },
    1031 
    1032         refreshPriorityHTML: function() {
    1033                 var e = this._priority_control;
    1034                 var c = [ e.classNameConst ];
    1035                 switch( this._prio ) {
    1036                         case 1  : c.push( 'high'   ); break;
    1037                         case -1 : c.push( 'low'    ); break;
    1038                         default : c.push( 'normal' ); break;
    1039                 }
    1040                 e.className = c.join(' ');
    1041         },
    1042 
    1043         fileWantedControlClicked: function(event) {
    1044                 this.toggleWanted();
    1045         },
    1046 
    1047         filePriorityControlClicked: function(event, element) {
    1048                 var x = event.pageX;
    1049                 while (element !== null) {
    1050                         x = x - element.offsetLeft;
    1051                         element = element.offsetParent;
    1052                 }
    1053 
    1054                 var prio;
    1055                 if(iPhone)
    1056                 {
    1057                         if( x < 8 ) prio = -1;
    1058                         else if( x < 27 ) prio = 0;
    1059                         else prio = 1;
    1060                 }
    1061                 else
    1062                 {
    1063                         if( x < 12 ) prio = -1;
    1064                         else if( x < 23 ) prio = 0;
    1065                         else prio = 1;
    1066                 }
    1067                 this.setPriority( prio );
    1068         }
    1069 };
  • trunk/web/javascript/transmission.js

    r12634 r12690  
    5252                $('#stats_close_button').bind('click', function(e){ tr.closeStatsClicked(e); return false; });
    5353                $('.inspector_tab').bind('click', function(e){ tr.inspectorTabClicked(e, this); });
    54                 $('.file_wanted_control').live('click', function(e){ tr.fileWantedClicked(e, this); });
    55                 $('.file_priority_control').live('click', function(e){ tr.filePriorityClicked(e, this); });
    5654                $('#files_select_all').live('click', function(e){ tr.filesSelectAllClicked(e, this); });
    5755                $('#files_deselect_all').live('click', function(e){ tr.filesDeselectAllClicked(e, this); });
     
    225223        },
    226224
     225        setCompactMode: function( is_compact )
     226        {
     227                this.torrentRenderer = is_compact ? new TorrentRendererCompact( )
     228                                                  : new TorrentRendererFull( );
     229                $('ul.torrent_list li').remove();
     230                this._rows = [];
     231                this.refilter();
     232        },
     233
    227234        /*
    228235         * Load the clutch prefs and init the GUI according to those prefs
     
    252259                if( !iPhone && this[Prefs._CompactDisplayState] )
    253260                        $('#compact_view').selectMenuItem();
     261
     262                this.setCompactMode( this[Prefs._CompactDisplayState] );
    254263        },
    255264
     
    363372                        boundingBottomPad: 5,
    364373                        onContextMenu:     function(e) {
    365                                 var closestRow = $(e.target).closest('.torrent')[0]._torrent;
    366                                 if(!closestRow.isSelected())
    367                                         tr.setSelectedTorrent( closestRow, true );
     374                                var closest_row = $(e.target).closest('.torrent')[0];
     375                                for( var i=0, row; row = tr._rows[i]; ++i ) {
     376                                        if( row.getElement() === closest_row ) {
     377                                                tr.setSelectedRow( row );
     378                                                break;
     379                                        }
     380                                }
    368381                                return true;
    369382                        }
     
    422435                var torrents = [ ];
    423436                for( var i=0, row; row=this._rows[i]; ++i )
    424                         if( row._torrent && ( row[0].style.display != 'none' ) )
    425                                 torrents.push( row._torrent );
     437                        if( row.isVisible( ) )
     438                                torrents.push( row.getTorrent( ) );
    426439                return torrents;
    427440        },
    428441
    429         getSelectedTorrents: function()
    430         {
    431                 var v = this.getVisibleTorrents( );
     442        getSelectedRows: function()
     443        {
    432444                var s = [ ];
    433                 for( var i=0, row; row=v[i]; ++i )
     445
     446                for( var i=0, row; row=this._rows[i]; ++i )
    434447                        if( row.isSelected( ) )
    435448                                s.push( row );
     449
    436450                return s;
    437451        },
    438452
    439         getDeselectedTorrents: function() {
    440                 var visible_torrent_ids = jQuery.map(this.getVisibleTorrents(), function(t) { return t.id(); } );
    441                 var s = [ ];
    442                 jQuery.each( this.getAllTorrents( ), function() {
    443                         var visible = (-1 != jQuery.inArray(this.id(), visible_torrent_ids));
    444                         if (!this.isSelected() || !visible)
    445                                 s.push( this );
    446                 } );
     453        getSelectedTorrents: function()
     454        {
     455                var s = this.getSelectedRows( );
     456
     457                for( var i=0, row; row=s[i]; ++i )
     458                        s[i] = s[i].getTorrent();
     459
    447460                return s;
     461        },
     462
     463        getDeselectedTorrents: function()
     464        {
     465                var ret = { };
     466                for( var key in this._torrents )
     467                        ret[ key ] = this._torrents[key];
     468                var sel = this.getSelectedTorrents( );
     469                for( var i=0, tor; tor=sel[i]; ++i )
     470                        delete ret[ tor.id() ];
     471                return ret;
    448472        },
    449473
     
    452476                var rows = [ ];
    453477                for( var i=0, row; row=this._rows[i]; ++i )
    454                         if( row[0].style.display != 'none' )
     478                        if( row.isVisible( ) )
    455479                                rows.push( row );
    456480                return rows;
    457481        },
    458482
    459         getTorrentIndex: function( rows, torrent )
    460         {
    461                 for( var i=0, row; row=rows[i]; ++i )
    462                         if( row._torrent == torrent )
     483        getRowIndex: function( rows, row )
     484        {
     485                for( var i=0, r; r=rows[i]; ++i )
     486                        if( r === row )
    463487                                return i;
    464488                return null;
     
    480504                var innerHeight = container.innerHeight( );
    481505
    482                 var offsetTop = e[0].offsetTop;
    483                 var offsetHeight = e.outerHeight( );
     506                var offsetTop = e.offsetTop;
     507                var offsetHeight = $(e).outerHeight( );
    484508
    485509                if( offsetTop < scrollTop )
     
    502526         *--------------------------------------------*/
    503527
    504         setSelectedTorrent: function( torrent, doUpdate ) {
    505                 this.deselectAll( );
    506                 this.selectTorrent( torrent, doUpdate );
    507         },
    508 
    509         selectElement: function( e, doUpdate ) {
    510                 e.addClass('selected');
    511                 if( doUpdate )
    512                         this.selectionChanged( );
    513         },
    514         selectRow: function( rowIndex, doUpdate ) {
    515                 this.selectElement( this._rows[rowIndex], doUpdate );
    516         },
    517         selectTorrent: function( torrent, doUpdate ) {
    518                 if( torrent._element )
    519                         this.selectElement( torrent._element, doUpdate );
    520         },
    521 
    522         deselectElement: function( e, doUpdate ) {
    523                 e.removeClass('selected');
    524                 if( doUpdate )
    525                         this.selectionChanged( );
    526         },
    527         deselectTorrent: function( torrent, doUpdate ) {
    528                 if( torrent._element )
    529                         this.deselectElement( torrent._element, doUpdate );
    530         },
    531 
    532         selectAll: function( doUpdate ) {
     528        setSelectedRow: function( row ) {
     529                var rows = this.getSelectedRows( );
     530                for( var i=0, r; r=rows[i]; ++i )
     531                        this.deselectRow( r );
     532                this.selectRow( row );
     533        },
     534
     535        selectRow: function( row ) {
     536                row.setSelected( true );
     537                this.callSelectionChangedSoon();
     538        },
     539
     540        deselectRow: function( row ) {
     541                row.setSelected( false );
     542                this.callSelectionChangedSoon();
     543        },
     544
     545        selectAll: function( ) {
    533546                var tr = this;
    534547                for( var i=0, row; row=tr._rows[i]; ++i )
    535                         tr.selectElement( row );
    536                 if( doUpdate )
    537                         tr.selectionChanged();
    538         },
    539         deselectAll: function( doUpdate ) {
    540                 var tr = this;
    541                 for( var i=0, row; row=tr._rows[i]; ++i )
    542                         tr.deselectElement( row );
    543                 tr._last_torrent_clicked = null;
    544                 if( doUpdate )
    545                         tr.selectionChanged( );
     548                        tr.selectRow( row );
     549                this.callSelectionChangedSoon();
     550        },
     551        deselectAll: function( ) {
     552                for( var i=0, row; row=this._rows[i]; ++i )
     553                        this.deselectRow( row );
     554                this.callSelectionChangedSoon();
     555                this._last_row_clicked = null;
    546556        },
    547557
     
    549559         * Select a range from this torrent to the last clicked torrent
    550560         */
    551         selectRange: function( torrent, doUpdate )
    552         {
    553                 if( !this._last_torrent_clicked )
     561        selectRange: function( row )
     562        {
     563                if( this._last_row_clicked === null )
    554564                {
    555                         this.selectTorrent( torrent );
     565                        this.selectRow( row );
    556566                }
    557567                else // select the range between the prevous & current
    558568                {
    559569                        var rows = this.getVisibleRows( );
    560                         var i = this.getTorrentIndex( rows, this._last_torrent_clicked );
    561                         var end = this.getTorrentIndex( rows, torrent );
     570                        var i = this.getRowIndex( rows, this._last_row_clicked );
     571                        var end = this.getRowIndex( rows, row );
    562572                        var step = i < end ? 1 : -1;
    563573                        for( ; i!=end; i+=step )
    564                                 this.selectRow( i );
    565                         this.selectRow( i );
    566                 }
    567 
    568                 if( doUpdate )
    569                         this.selectionChanged( );
     574                                this.selectRow( this._rows[i] );
     575                        this.selectRow( this._rows[i] );
     576                }
     577
     578                this.callSelectionChangedSoon( );
    570579        },
    571580
     
    575584                this.updateInspector();
    576585                this.updateSelectedData();
     586                this.selectionChangedTimer = null;
     587        },
     588
     589        callSelectionChangedSoon: function()
     590        {
     591                if( this.selectionChangedTimer === null )
     592                        this.selectionChangedTimer = setTimeout(function(o) { o.selectionChanged(); }, 200, this);
    577593        },
    578594
     
    589605        {
    590606                var tr = this;
    591                 var sel = tr.getSelectedTorrents( );
     607                var sel = tr.getSelectedRows( );
    592608                var rows = tr.getVisibleRows( );
    593609                var i = -1;
     
    595611                if( event.keyCode == 40 ) // down arrow
    596612                {
    597                         var t = sel.length ? sel[sel.length-1] : null;
    598                         i = t==null ? null : tr.getTorrentIndex(rows,t)+1;
     613                        var r = sel.length ? sel[sel.length-1] : null;
     614                        i = r==null ? null : tr.getRowIndex(rows,r)+1;
    599615                        if( i == rows.length || i == null )
    600616                                i = 0;
     
    602618                else if( event.keyCode == 38 ) // up arrow
    603619                {
    604                         var t = sel.length ? sel[0] : null
    605                         i = t==null ? null : tr.getTorrentIndex(rows,t)-1;
     620                        var r = sel.length ? sel[0] : null
     621                        i = r==null ? null : tr.getRowIndex(rows,r)-1;
    606622                        if( i == -1 || i == null )
    607623                                i = rows.length - 1;
     
    610626                if( 0<=i && i<rows.length ) {
    611627                        tr.deselectAll( );
    612                         tr.selectRow( i, true );
     628                        tr.selectRow( tr._rows[i], true );
    613629                        tr.scrollToElement( tr._rows[i] );
    614630                }
     
    829845                this.hideiPhoneAddressbar();
    830846
    831                 this.updateVisibleFileLists();
    832847                this.updatePeersLists();
    833848                this.updateTrackersLists();
    834         },
    835 
    836         fileWantedClicked: function(event, element){
    837                 this.extractFileFromElement(element).fileWantedControlClicked(event);
    838         },
    839 
    840         filePriorityClicked: function(event, element){
    841                 this.extractFileFromElement(element).filePriorityControlClicked(event, element);
     849                this.updateFileList();
    842850        },
    843851
    844852        filesSelectAllClicked: function(event) {
    845                 var tr = this;
    846                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
    847                 var files_list = this.toggleFilesWantedDisplay(ids, true);
    848                 for (i = 0; i < ids.length; ++i) {
    849                         if (files_list[i].length)
    850                                 this.remote.filesSelectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
    851                 }
    852         },
    853 
     853                var t = this._files_torrent;
     854                if (t != null)
     855                        this.toggleFilesWantedDisplay(t, true);
     856        },
    854857        filesDeselectAllClicked: function(event) {
    855                 var tr = this;
    856                 var ids = jQuery.map(this.getSelectedTorrents( ), function(t) { return t.id(); } );
    857                 var files_list = this.toggleFilesWantedDisplay(ids, false);
    858                 for (i = 0; i < ids.length; ++i) {
    859                         if (files_list[i].length)
    860                                 this.remote.filesDeselectAll( [ ids[i] ], files_list[i], function() { tr.refreshTorrents( ids ); } );
    861                 }
    862         },
    863 
    864         extractFileFromElement: function(element) {
    865                 var match = $(element).closest('.inspector_torrent_file_list_entry').attr('id').match(/^t(\d+)f(\d+)$/);
    866                 var torrent_id = match[1];
    867                 var file_id = match[2];
    868                 var torrent = this._torrents[torrent_id];
    869                 return torrent._file_view[file_id];
    870         },
    871 
    872         toggleFilesWantedDisplay: function(ids, wanted) {
    873                 var i, j, k, torrent, files_list = [ ];
    874                 for (i = 0; i < ids.length; ++i) {
    875                         torrent = this._torrents[ids[i]];
    876                         files_list[i] = [ ];
    877                         for (j = k = 0; j < torrent._file_view.length; ++j) {
    878                                 if (torrent._file_view[j].isEditable() && torrent._file_view[j]._wanted != wanted) {
    879                                         torrent._file_view[j].setWanted(wanted, false);
    880                                         files_list[i][k++] = j;
    881                                 }
    882                         }
    883                         torrent.refreshFileView;
    884                 }
    885                 return files_list;
     858                var t = this._files_torrent;
     859                if (t != null)
     860                        this.toggleFilesWantedDisplay(t, false);
     861        },
     862        toggleFilesWantedDisplay: function(torrent, wanted) {
     863                var rows = [ ];
     864                for( var i=0, row; row=this._files[i]; ++i )
     865                        if( row.isEditable() && (torrent._file_model[i].wanted !== wanted) )
     866                                rows.push( row );
     867                if( rows.length > 1 ) {
     868                        var command = wanted ? 'files-wanted' : 'files-unwanted';
     869                        this.changeFileCommand( command, rows );
     870                }
    886871        },
    887872
     
    12381223                switch ($element.parent()[0].id) {
    12391224
    1240                         // Display the preferences dialog
     1225                                // Display the preferences dialog
    12411226                        case 'footer_super_menu':
    12421227                                if ($element[0].id == 'preferences') {
     
    12561241                                        else
    12571242                                                $element.deselectMenuItem();
    1258                                         this.refreshDisplay( );
     1243                                        this.setCompactMode( this[Prefs._CompactDisplayState] );
    12591244                                }
    12601245                                else if ($element[0].id == 'homepage') {
     
    13351320                $('#settings_menu').trigger('closemenu');
    13361321                return false; // to prevent the event from bubbling up
    1337         },
    1338 
    1339         setLastTorrentClicked: function( torrent )
    1340         {
    1341                 this._last_torrent_clicked = torrent;
    13421322        },
    13431323
     
    14081388                        setInnerHTML( tab.download_dir, na );
    14091389                        setInnerHTML( tab.error, na );
    1410                         this.updateVisibleFileLists();
     1390                        this.updateFileList();
    14111391                        this.updatePeersLists();
    14121392                        this.updateTrackersLists();
     
    14921472                this.updateTrackersLists();
    14931473                $(".inspector_row > div:contains('N/A')").css('color', '#666');
    1494                 this.updateVisibleFileLists();
    1495         },
    1496 
    1497         fileListIsVisible: function() {
    1498                 return this._inspector_tab_files.className.indexOf('selected') != -1;
    1499         },
    1500 
    1501         updateVisibleFileLists: function() {
    1502                 if( this.fileListIsVisible( ) === true ) {
    1503                         var selected = this.getSelectedTorrents();
    1504                         jQuery.each( selected, function() { this.showFileList(); } );
    1505                         jQuery.each( this.getDeselectedTorrents(), function() { this.hideFileList(); } );
    1506                         // Check if we need to display the select all buttions
    1507                         if ( !selected.length ) {
    1508                                 if ( $("#select_all_button_container").is(':visible') )
    1509                                         $("#select_all_button_container").hide();
    1510                         } else {
    1511                                 if ( !$("#select_all_button_container").is(':visible') )
    1512                                         $("#select_all_button_container").show();
    1513                         }
    1514                 }
     1474                this.updateFileList();
     1475        },
     1476
     1477        onFileWantedToggled: function( row, want ) {
     1478                var command = want ? 'files-wanted' : 'files-unwanted';
     1479                this.changeFileCommand( command, [ row ] );
     1480        },
     1481        onFilePriorityToggled: function( row, priority ) {
     1482                var command;
     1483                switch( priority ) {
     1484                        case -1: command = 'priority-low'; break;
     1485                        case  1: command = 'priority-high'; break;
     1486                        default: command = 'priority-normal'; break;
     1487                }
     1488                this.changeFileCommand( command, [ row ] );
     1489        },
     1490        clearFileList: function() {
     1491                $(this._inspector_file_list).empty();
     1492                delete this._files_torrent;
     1493                delete this._files;
     1494        },
     1495        updateFileList: function() {
     1496
     1497                // if the file list is hidden, clear the list
     1498                if( this._inspector_tab_files.className.indexOf('selected') == -1 ) {
     1499                        this.clearFileList( );
     1500                        return;
     1501                }
     1502
     1503                // if not torrent is selected, clear the list
     1504                var selected_torrents = this.getSelectedTorrents( );
     1505                if( selected_torrents.length != 1 ) {
     1506                        this.clearFileList( );
     1507                        return;
     1508                }
     1509
     1510                // if the active torrent hasn't changed, noop
     1511                var torrent = selected_torrents[0];
     1512                if( this._files_torrent === torrent )
     1513                        return;
     1514
     1515                // build the file list
     1516                this.clearFileList( );
     1517                this._files_torrent = torrent;
     1518                var n = torrent._file_model.length;
     1519                this._files = new Array( n );
     1520                var fragment = document.createDocumentFragment( );
     1521                var tr = this;
     1522                for( var i=0; i<n; ++i ) {
     1523                        var row = new FileRow( this, torrent, i );
     1524                        fragment.appendChild( row.getElement( ) );
     1525                        this._files[i] = row;
     1526                        $(row).bind('wantedToggled',function(e,row,want){tr.onFileWantedToggled(row,want);});
     1527                        $(row).bind('priorityToggled',function(e,row,priority){tr.onFilePriorityToggled(row,priority);});
     1528                }
     1529                this._inspector_file_list.appendChild( fragment );
     1530        },
     1531
     1532        refreshFileView: function() {
     1533                for( var i=0, row; row=this._files[i]; ++i )
     1534                        row.refresh();
    15151535        },
    15161536
     
    17451765                var tr = this;
    17461766                var refresh_files_for = [ ];
     1767                var selected_torrents = this.getSelectedTorrents();
    17471768                jQuery.each( torrents, function( ) {
    17481769                        var t = tr._torrents[ this.id ];
    17491770                        if( t ) {
    17501771                                t.refreshMetaData( this );
    1751                                 if( t.isSelected( ) )
     1772                                if( selected_torrents.indexOf(t) != -1 )
    17521773                                        refresh_files_for.push( t.id( ) );
    17531774                        }
     
    17691790                var new_torrent_ids = [];
    17701791                var refresh_files_for = [];
    1771                 jQuery.each( updated, function() {
    1772                         var t = tr._torrents[this.id];
    1773                         if (t){
    1774                                 t.refresh(this);
    1775                                 if(t.isSelected())
     1792                var selected_torrents = this.getSelectedTorrents();
     1793
     1794                for( var i=0, o; o=updated[i]; ++i ) {
     1795                        var t = tr._torrents[o.id];
     1796                        if (t == null)
     1797                                new_torrent_ids.push(o.id);
     1798                        else {
     1799                                t.refresh(o);
     1800                                if( selected_torrents.indexOf(t) != -1 )
    17761801                                        refresh_files_for.push(t.id());
    17771802                        }
    1778                         else
    1779                                 new_torrent_ids.push(this.id);
    1780                 } );
     1803                }
    17811804
    17821805                if(refresh_files_for.length > 0)
     
    17971820
    17981821        updateTorrentsFileData: function( torrents ){
    1799                 var tr = this;
    1800                 var listIsVisible = tr.fileListIsVisible( );
    1801                 jQuery.each( torrents, function() {
    1802                         var t = tr._torrents[this.id];
    1803                         if (t) {
    1804                                 t.refreshFileModel(this);
    1805                                 if( listIsVisible && t.isSelected())
    1806                                         t.refreshFileView();
     1822                for( var i=0, o; o=torrents[i]; ++i ) {
     1823                        var t = this._torrents[o.id];
     1824                        if( t !== null ) {
     1825                                t.refreshFileModel( o );
     1826                                if( t === this._files_torrent )
     1827                                        this.refreshFileView();
    18071828                        }
    1808                 } );
     1829                }
    18091830        },
    18101831
     
    18141835        },
    18151836
     1837        onRowClicked: function( ev, row )
     1838        {
     1839                // Prevents click carrying to parent element
     1840                // which deselects all on click
     1841                event.stopPropagation();
     1842                // but still hide the context menu if it is showing
     1843                $('#jqContextMenu').hide();
     1844
     1845                // 'Apple' button emulation on PC :
     1846                // Need settable meta-key and ctrl-key variables for mac emulation
     1847                var meta_key = event.metaKey;
     1848                var ctrl_key = event.ctrlKey;
     1849                if (event.ctrlKey && navigator.appVersion.toLowerCase().indexOf("mac") == -1) {
     1850                        meta_key = true;
     1851                        ctrl_key = false;
     1852                }
     1853
     1854                // Shift-Click - selects a range from the last-clicked row to this one
     1855                if (iPhone) {
     1856                        if ( row.isSelected() )
     1857                                this.showInspector();
     1858                        this.setSelectedRow( row, true );
     1859
     1860                } else if (event.shiftKey) {
     1861                        this.selectRange( row, true );
     1862                        // Need to deselect any selected text
     1863                        window.focus();
     1864
     1865                // Apple-Click, not selected
     1866                } else if (!row.isSelected() && meta_key) {
     1867                        this.selectRow( row, true );
     1868
     1869                // Regular Click, not selected
     1870                } else if (!row.isSelected()) {
     1871                        this.setSelectedRow( row, true );
     1872
     1873                // Apple-Click, selected
     1874                } else if (row.isSelected() && meta_key) {
     1875                        this.deselectRow( row );
     1876
     1877                // Regular Click, selected
     1878                } else if (row.isSelected()) {
     1879                        this.setSelectedRow( row, true );
     1880                }
     1881
     1882                this._last_row_clicked = row;
     1883        },
     1884
    18161885        addTorrents: function( new_torrents )
    18171886        {
    1818                 var transferFragment = document.createDocumentFragment( );
    1819                 var fileFragment = document.createDocumentFragment( );
     1887                var tr = this;
    18201888
    18211889                for( var i=0, row; row=new_torrents[i]; ++i ) {
    1822                         var new_torrent = new Torrent( transferFragment, fileFragment, this, row );
    1823                         this._torrents[new_torrent.id()] = new_torrent;
    1824                 }
    1825 
    1826                 this._inspector_file_list.appendChild( fileFragment );
    1827                 this._torrent_list.appendChild( transferFragment );
     1890                        var t = new Torrent( this, row );
     1891                        this._torrents[t.id()] = t;
     1892                }
    18281893
    18291894                this.refilter( );
     
    18401905                        if(torrent) {
    18411906                                removedAny = true;
    1842                                 var e = torrent.element();
     1907                                var e = torrent.view;
    18431908                                if( e ) {
    18441909                                        var row_index;
     
    18561921                                }
    18571922
    1858                                 torrent.hideFileList();
    1859                                 torrent.deleteFiles();
    18601923                                delete tr._torrents[torrent.id()];
    18611924                        }
     
    18671930        refreshDisplay: function( )
    18681931        {
    1869                 var torrents = this.getVisibleTorrents();
    1870                 for( var i=0; torrents[i]; ++i )
    1871                         torrents[i].refreshHTML();
     1932                var rows = this.getVisibleRows( );
     1933                for( var i=0, row; row=rows[i]; ++i )
     1934                        row.render( this );
    18721935        },
    18731936
     
    18781941        {
    18791942                var rows = this.getVisibleRows( );
    1880                 for( var i=0, row; row=rows[i]; ++i ) {
    1881                         var wasEven = row[0].className.indexOf('even') != -1;
    1882                         var isEven = ((i+1) % 2 == 0);
    1883                         if( wasEven != isEven )
    1884                                 row.toggleClass('even', isEven);
    1885                 }
     1943                for( var i=0, row; row=rows[i]; ++i )
     1944                        row.setEven((i+1) % 2 == 0);
    18861945        },
    18871946
     
    20692128                this.remote.stopTorrents( torrent_ids,  function(){ tr.refreshTorrents(torrent_ids )} );
    20702129        },
    2071         changeFileCommand: function(command, torrent, file) {
    2072                 this.remote.changeFileCommand(command, torrent, file)
     2130        changeFileCommand: function(command, rows) {
     2131                this.remote.changeFileCommand(command, rows);
    20732132        },
    20742133
     
    21152174        ***/
    21162175
     2176        onToggleRunningClicked: function( ev )
     2177        {
     2178                var torrent = ev.data.r.getTorrent( );
     2179
     2180                if( torrent.isStopped( ) )
     2181                        this.startTorrent( torrent );
     2182                else
     2183                        this.stopTorrent( torrent );
     2184        },
     2185
    21172186        refilter: function()
    21182187        {
     
    21302199                // make a backup of the selection
    21312200                var sel = this.getSelectedTorrents( );
    2132                 this.deselectAll( );
    2133 
    2134                 // hide the ones we're not keeping
     2201
     2202                // add rows it there aren't enough
     2203                if( this._rows.length < keep.length ) {
     2204                        var tr = this;
     2205                        var fragment = document.createDocumentFragment( );
     2206                        while( this._rows.length < keep.length ) {
     2207                                var row = new TorrentRow( this, this.torrentRenderer );
     2208                                if( !iPhone ) {
     2209                                        var b = row.getToggleRunningButton( );
     2210                                        if( b !== null ) {
     2211                                                $(b).bind('click', {r:row}, function(e) { tr.onToggleRunningClicked(e); });
     2212                                        }
     2213                                }
     2214                                $(row.getElement()).bind('click',{r: row}, function(ev){ tr.onRowClicked(ev,ev.data.r);});
     2215                                fragment.appendChild( row.getElement() );
     2216                                this._rows.push( row );
     2217                        }
     2218                        this._torrent_list.appendChild(fragment);
     2219                }
     2220
     2221                // hide rows if there are too many
    21352222                for( var i=keep.length, e; e=this._rows[i]; ++i ) {
    21362223                        delete e._torrent;
    2137                         e[0].style.display = 'none';
     2224                        e.setVisible(false);
    21382225                }
    21392226
    21402227                // show the ones we're keeping
    2141                 sel.sort( Torrent.compareById );
    21422228                for( var i=0, len=keep.length; i<len; ++i ) {
    2143                         var e = this._rows[i];
    2144                         e[0].style.display = 'block';
    2145                         var t = keep[i];
    2146                         t.setElement( e );
    2147                         if( Torrent.indexOf( sel, t.id() ) != -1 )
    2148                                 this.selectElement( e );
     2229                        var row = this._rows[i];
     2230                        var tor = keep[i];
     2231                        row.setVisible( true );
     2232                        row.setTorrent( this, tor );
     2233                        row.setSelected( sel.indexOf( tor ) !== -1 );
    21492234                }
    21502235
     
    21752260                        var havePausedSelection = false;
    21762261
    2177                         for( var i=0, len=torrents.length; !haveSelection && i<len; ++i ) {
    2178                                 var isActive = torrents[i].isActive( );
    2179                                 var isSelected = torrents[i].isSelected( );
    2180                                 if( isActive ) haveActive = true;
    2181                                 if( !isActive ) havePaused = true;
    2182                                 if( isSelected ) haveSelection = true;
    2183                                 if( isSelected && isActive ) haveActiveSelection = true;
    2184                                 if( isSelected && !isActive ) havePausedSelection = true;
     2262                        for( var i=0, row; row=this._rows[i]; ++i ) {
     2263                                if( row.isVisible( ) ) {
     2264                                        var isActive = row.getTorrent().isActive( );
     2265                                        var isSelected = row.isSelected();
     2266                                        if( isActive ) haveActive = true;
     2267                                        if( !isActive ) havePaused = true;
     2268                                        if( isSelected ) haveSelection = true;
     2269                                        if( isSelected && isActive ) haveActiveSelection = true;
     2270                                        if( isSelected && !isActive ) havePausedSelection = true;
     2271                                }
    21852272                        }
    21862273
  • trunk/web/javascript/transmission.remote.js

    r12633 r12690  
    190190        },
    191191
    192         changeFileCommand: function( command, torrent, file ) {
    193                 var remote = this;
    194                 var torrent_ids = [ torrent.id() ];
     192        changeFileCommand: function( command, rows ) {
     193                var remote = this;
     194                var torrent_ids = [ rows[0].getTorrent().id() ];
     195                var files = [ ];
     196                for( var i=0, row; row=rows[i]; ++i )
     197                        files.push( row.getIndex( ) );
    195198                var o = {
    196199                        method: 'torrent-set',
    197200                        arguments: { ids: torrent_ids }
    198201                };
    199                 o.arguments[command] = [ file._index ];
     202                o.arguments[command] = files;
    200203                this.sendRequest( o, function( ) {
    201204                        remote._controller.refreshTorrents( torrent_ids );
    202                 } );
     205                });
    203206        },
    204207
     
    288291                } );
    289292        },
     293/*
    290294        filesSelectAll: function( torrent_ids, files, callback ) {
    291295                this.sendTorrentSetRequests( 'torrent-set', torrent_ids, { 'files-wanted': files }, callback );
     
    294298                this.sendTorrentSetRequests( 'torrent-set', torrent_ids, { 'files-unwanted': files }, callback );
    295299        },
     300*/
    296301
    297302        // Added queue calls
  • trunk/web/stylesheets/common.css

    r12573 r12690  
    1414body {
    1515        font: 62.5% "lucida grande", Tahoma, Verdana, Arial, Helvetica, sans-serif; /* Resets 1em to 10px */
    16         color: #222 !important;
     16        color: #222;/* !important; */
    1717        background: #FFF;
    1818        text-align: center;
     
    354354        margin: 0px;
    355355        overflow: auto;
    356         z-index: 1;
    357356}
    358357
     
    362361        padding: 0;
    363362        text-align: left;
    364         z-index: 1;
    365363        cursor: default;
    366364}
     
    383381        color: #666;
    384382}
     383ul.torrent_list li.torrent.compact {
     384        padding: 4px;
     385}
    385386
    386387ul.torrent_list li.torrent a img {
     
    408409}
    409410
    410 ul.torrent_list li.torrent div.torrent_name.compact {
    411         float: left;
    412         z-index: 1;
    413         position: absolute;
    414         overflow: hidden;
    415         text-overflow: ellipsis;
    416         white-space: nowrap;
     411ul.torrent_list li.torrent div.torrent_name.paused {
     412        font-size: 1.3em;
     413        font-weight: normal;
     414        color: #777;
    417415}
    418416
     
    421419}
    422420
    423 ul.torrent_list li.torrent div.torrent_progress_details,
    424 ul.torrent_list li.torrent div.torrent_peer_details {
     421ul.torrent_list div.torrent_progress_details,
     422ul.torrent_list div.torrent_peer_details {
    425423        clear: left;
    426424        font-size: 1em;
     
    443441}
    444442
    445 ul.torrent_list li.torrent div.torrent_progress_details.compact,
    446 ul.torrent_list li.torrent div.torrent_peer_details.compact {
    447         padding-top: 3px;
    448         float: left;
    449 }
    450 ul.torrent_list li.torrent div.torrent_progress_details.compact {
    451         float: right;
    452 }
    453 
    454 ul.torrent_list li.torrent div.torrent_progress_bar_container.compact {
     443
     444/**
     445 * Progressbar
     446 *
     447 * Each progressbar has three elemens: a parent container and two children,
     448 * complete and incomplete.
     449 *
     450 * The only thing needed to set the progressbar percentage is to set
     451 * the complete child's width as a percentage. This is because incomplete
     452 * is pinned to the full width and height of the parent, and complete
     453 * is pinned to the left side of the parent and has a higher z-index.
     454 *
     455 * The progressbar has different colors depending on its state, so there
     456 * are four 'decorator' classNames: magnet, seeding, leeching, and paused.
     457 */
     458
     459ul.torrent_list div.torrent_progress_bar_container {
     460        height: 10px;
     461        position: relative;
     462        margin-top: 2px;
     463}
     464ul.torrent_list div.torrent_status_and_progress_bar {
     465/*      float: right;*/
     466}
     467ul.torrent_list div.torrent_progress_bar_container.compact {
     468        width: 50px;
    455469        position: absolute;
    456         left: 5px;
    457         right: 35px;
    458         opacity:0.4;
    459 }
    460 
    461 ul.torrent_list li.torrent div.torrent_progress_bar {
    462         height: 10px;
    463         margin: 3px 0;
    464         float: left;
    465         line-height: 1px;
    466         font-size: 1px;
    467         width: 100%;
     470        right: 10px;
     471        margin-top: 2px;
     472        /*float: right;*/
     473}
     474ul.torrent_list div.torrent_peer_details.compact {
     475        font-size: 12px; /* matching the progressbar height (10px) + progressbar border (1px top, 1px bottom) */
     476        margin-top: 2px;
     477        margin-left: 10px;
     478        margin-right: 65px; /* leave room on the right for the progressbar */
     479        float: right; /* pins it next to progressbar & forces torrent_name to ellipsize when it bumps up against this div */
     480}
     481ul.torrent_list div.torrent_progress_bar_container.full {
     482        margin-bottom: 5px;
     483}
     484ul.torrent_list div.torrent_progress_bar {
     485        height: 100%;
     486        position: absolute;
     487        top: 0px;
     488        left: 0px;
    468489        background-image: url('../images/progress/progress.png');
    469490        background-repeat: repeat-x;
    470         background-color: transparent;
    471 }
    472 
    473 ul.torrent_list li.torrent div.torrent_progress_bar.complete {
    474         background-position: left -10px;
    475         border: 1px solid #29AD35;
    476 }
    477 ul.torrent_list li.torrent div.torrent_progress_bar.complete.seeding {
    478         background-position: left -40px;
    479         border: 1px solid #269e30;
    480         border-right: none;
    481 }
    482 
    483 ul.torrent_list li.torrent div.torrent_progress_bar.in_progress {
    484         background-position: left 0px;
    485         border: 1px solid #3F79EE;
    486         border-right: none;
    487 }
    488 
    489 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete.compact {
    490         background: none;
    491         border: none;
    492 }
    493 
    494 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete {
    495         margin-right: -10px;
    496         background-position: left -20px;
    497         border: 1px solid #C8CACD;
    498         border-left: none;
    499 }
    500 
    501 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete.seeding {
    502         background-position: left -10px;
    503         border: 1px solid #29AD35;
    504 }
    505 
    506 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete_stopped {
    507         background-position: left -30px;
    508         border: 1px solid #939393;
    509         border-right: none;
    510 }
    511 
    512 ul.torrent_list li.torrent div.torrent_progress_bar.complete_stopped {
    513         background-position: left -30px;
    514         border: 1px solid #939393;
    515 }
    516 
    517 ul.torrent_list li.torrent div.torrent_progress_bar.empty {
    518         border-color: #c8cacd;
    519 }
    520 
    521 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete.meta {
    522         background-position: left -50px;
    523         border: 1px solid #cc6068;
    524 }
    525 
    526 ul.torrent_list li.torrent div.torrent_progress_bar.in_progress.meta {
    527         background-position: left -20px;
    528         border-color: #c8cacd;
    529 }
    530 
    531 ul.torrent_list li.torrent div.torrent_progress_bar.in_progress.meta.empty {
    532         border: 0 none;
    533 }
    534 
    535 li.torrent a div {
     491        border: 1px solid #888;
     492}
     493ul.torrent_list div.torrent_progress_bar.complete {
     494        z-index: 2;
     495}
     496ul.torrent_list div.torrent_progress_bar.incomplete {
     497        z-index: 1;
     498        width: 100%;
     499}
     500ul.torrent_list div.torrent_progress_bar.complete.paused {
     501        background-position: left -30px;
     502        border-color: #989898;
     503}
     504ul.torrent_list div.torrent_progress_bar.incomplete.paused {
     505        background-position: left -20px;
     506        border-color: #CFCFCF;
     507}
     508ul.torrent_list div.torrent_progress_bar.complete.magnet {
     509        background-position: left -20px;
     510        border-color: #CFCFCF;
     511}
     512ul.torrent_list div.torrent_progress_bar.incomplete.magnet {
     513        background-position: left -50px;
     514        border-color: #D47778;
     515}
     516ul.torrent_list div.torrent_progress_bar.complete.leeching {
     517        background-position: left 0px;
     518        border-color: #3D9DEA;
     519}
     520ul.torrent_list div.torrent_progress_bar.incomplete.leeching {
     521        background-position: left -20px;
     522        border-color: #CFCFCF;
     523}
     524ul.torrent_list div.torrent_progress_bar.complete.seeding {
     525        background-position: left -10px;
     526}
     527ul.torrent_list div.torrent_progress_bar.incomplete.seeding {
     528        background-position: left -40px;
     529        border-color: #35AC47;
     530}
     531
     532/****
     533*****  START / STOP BUTTON
     534****/
     535
     536li.torrent a {
    536537        float: right;
    537538        position: relative;
    538539        right: -22px;
    539         top: -16px;
     540        top: 1px;
     541}
     542li.torrent a div {
    540543        background: url('../images/buttons/torrent_buttons.png');
    541544        height: 14px;
    542545        width: 14px;
    543546}
    544 
    545 li.torrent a div.compact {
    546         top: 0px;
    547 }
    548 
    549547li.torrent a div.torrent_pause {
    550548        background-position: left top;
    551549}
    552 
    553550li.torrent a:hover div.torrent_pause {
    554551        background-position: left center;
    555552}
    556 
    557553li.torrent a:active div.torrent_pause {
    558554        background-position: left bottom;
    559555}
    560 
    561556li.torrent a div.torrent_resume {
    562557        background-position: center top;
    563558}
    564 
    565559li.torrent a:hover div.torrent_resume {
    566560        background-position: center center;
    567561}
    568 
    569562li.torrent a:active div.torrent_resume {
    570563        background-position: center bottom;
     
    841834        overflow: hidden;
    842835}
    843 #inspector_file_list #select_all_button_container {
    844         margin: 0;
    845 }
    846 #inspector_file_list ul li.select_all_button {
     836#inspector_tab_files_container #select_all_button_container {
     837        width: 100%;
     838}
     839#inspector_tab_files_container .select_all_button {
    847840        background: transparent url(../images/buttons/tab_backgrounds.png) repeat-x scroll left -6px;
    848841        border: 1px solid #888888;
     
    854847}
    855848
    856 ul.inspector_torrent_file_list {
     849#inspector_file_list {
    857850        width: 100%;
    858851        margin: 0  0 0 0;
  • trunk/web/stylesheets/iphone.css

    r11193 r12690  
    332332}
    333333
    334 ul.torrent_list li.torrent div.torrent_progress_bar {
    335         height: 10px;
    336         margin: 3px 0 3px 0;
    337         float: left;
    338         line-height: 1px;
    339         font-size: 1px;
    340         width: 100%;
    341         background-image: url('../images/progress/progress.png');
    342         background-repeat: repeat-x;
    343         background-color: transparent;
    344 }
    345 
    346 ul.torrent_list li.torrent div.torrent_progress_bar.complete {
    347         background-position: left -10px;
    348         border: 1px solid #29AD35;
    349 }
    350 
    351 ul.torrent_list li.torrent div.torrent_progress_bar.in_progress {
    352         background-position: left 0px;
    353         border: 1px solid #3F79EE;
    354         border-right: none;
    355 }
    356 
    357 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete {
    358         margin-right: -10px;
    359         background-position: left -20px;
    360         border: 1px solid #C8CACD;
    361         border-left: none;
    362 }
    363 
    364 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete_stopped {
    365         background-position: left -30px;
    366         border: 1px solid #939393;
    367         border-right: none;
    368 }
    369 
    370 ul.torrent_list li.torrent div.torrent_progress_bar.complete_stopped {
    371         background-position: left -30px;
    372         border: 1px solid #939393;
    373 }
    374 
    375 ul.torrent_list li.torrent div.torrent_progress_bar.incomplete {
    376         margin-right: -10px;
     334/***
     335**** Progressbar
     336***/
     337
     338ul.torrent_list div.torrent_progress_bar_container {
     339        height: 10px;
     340        position: relative;
     341        margin-top: 2px;
     342}
     343ul.torrent_list div.torrent_progress_bar_container.full {
     344        margin-bottom: 5px;
     345}
     346ul.torrent_list div.torrent_progress_bar {
     347        height: 100%;
     348        position: absolute;
     349        top: 0px;
     350        left: 0px;
     351        background-image: url('../images/progress/progress.png');
     352        background-repeat: repeat-x;
     353        border: 1px solid #888;
     354}
     355ul.torrent_list div.torrent_progress_bar.complete {
     356        z-index: 2;
     357}
     358ul.torrent_list div.torrent_progress_bar.incomplete {
     359        z-index: 1;
     360        width: 100%;
     361}
     362ul.torrent_list div.torrent_progress_bar.complete.paused {
     363        background-position: left -30px;
     364        border-color: #989898;
     365}
     366ul.torrent_list div.torrent_progress_bar.incomplete.paused {
     367        background-position: left -20px;
     368        border-color: #CFCFCF;
     369}
     370ul.torrent_list div.torrent_progress_bar.complete.magnet {
     371        background-position: left -20px;
     372        border-color: #CFCFCF;
     373}
     374ul.torrent_list div.torrent_progress_bar.incomplete.magnet {
     375        background-position: left -50px;
     376        border-color: #D47778;
     377}
     378ul.torrent_list div.torrent_progress_bar.complete.leeching {
     379        background-position: left 0px;
     380        border-color: #3D9DEA;
     381}
     382ul.torrent_list div.torrent_progress_bar.incomplete.leeching {
     383        background-position: left -20px;
     384        border-color: #CFCFCF;
     385}       
     386ul.torrent_list div.torrent_progress_bar.complete.seeding {
     387        background-position: left -10px;
     388        border-color: #35CA53;
     389}
     390ul.torrent_list div.torrent_progress_bar.incomplete.seeding {
     391        background-position: left -40px;
     392        border-color: #35AC47;
    377393}
    378394
Note: See TracChangeset for help on using the changeset viewer.