removed output-disable in dbms-output fetching procedure
[mediawiki.git] / js2 / mwEmbed / libSequencer / mvSequencer.js
blob7bc4314e1cc44534603e2ee95ed37534ed2539d6
1 /*
2  * mvSequencer.js Created on Oct 17, 2007
3  *
4  * All Metavid Wiki code is Released under the GPL2
5  * for more info visit http://metavid.org/wiki/Code
6  *
7  * @author Michael Dale
8  * @email mdale@wikimedia.org
9  *
10  * Further developed in open source development partnership with kaltura.
11  * more info at http://kaltura.com & http://kaltura.org
12  *
13  * mv_sequencer.js
14  *       is a basic embeddeble sequencer.
15  *  extends the playlist with drag/drop/sortable/add/remove functionality
16  *  editing of annotative content (mostly for wiki)
17  *  enables more dynamic layouts
18  *  exports back out to json or inline format
19  */
21 loadGM( {
22         "mwe-menu_clipedit" : "Edit media",
23         "mwe-menu_transition" : "Transitions and effects",
24         "mwe-menu_cliplib" : "Add media",
25         "mwe-menu_resource_overview" : "Resource overview",
26         "mwe-menu_options" : "Options",
27         "mwe-loading_timeline" : "Loading timeline ...",
28         "mwe-loading_user_rights" : "Loading user rights ...",
29         "mwe-no_edit_permissions" : "You do not have permissions to save changes to this sequence",
30         "mwe-edit_clip" : "Edit clip",
31         "mwe-edit_save" : "Save sequence changes",
32         "mwe-saving_wait" : "Save in progress (please wait)",
33         "mwe-save_done" : "Save complete",
34         "mwe-edit_cancel" : "Cancel sequence edit",
35         "mwe-edit_cancel_confirm" : "Are you sure you want to cancel your edit? Changes will be lost.",
36         "mwe-zoom_in" : "Zoom in",
37         "mwe-zoom_out" : "Zoom out",
38         "mwe-cut_clip" : "Cut clips",
39         "mwe-expand_track" : "Expand track",
40         "mwe-collapse_track" : "Collapse track",
41         "mwe-play_from_position" : "Play from playline position",
42         "mwe-pixle2sec" : "pixels to seconds",
43         "mwe-rmclip" : "Remove clip",
44         "mwe-clip_in" : "clip in",
45         "mwe-clip_out" : "clip out",
46         "mwe-welcome_to_sequencer" : "<h3>Welcome to the sequencer demo<\/h3> Very <b>limited<\/b> functionality right now. Not much documentation yet either.",
47         "mwe-no_selected_resource" : "<h3>No resource selected<\/h3> Select a clip to enable editing.",
48         "mwe-error_edit_multiple" : "<h3>Multiple resources selected<\/h3> Select a single clip to edit it.",
49         "mwe-editor_options" : "Editor options",
50         "mwe-editor_mode" : "Editor mode",
51         "mwe-simple_editor_desc" : "simple editor (iMovie style)",
52         "mwe-advanced_editor_desc" : "advanced editor (Final Cut style)",
53         "mwe-other_options" : "Other options",
54         "mwe-contextmenu_opt" : "Enable context menus",
55         "mwe-sequencer_credit_line" : "Developed by <a href=\"http:\/\/kaltura.com\">Kaltura, Inc.<\/a> in partnership with the <a href=\"http:\/\/wikimediafoundation.org\/wiki\/Home\">Wikimedia Foundation<\/a> (<a href=\"#\">more information<\/a>)."
56 } );
57  // used to set default values and validate the passed init object
58 var sequencerDefaultValues = {
60         instance_name:'mvSeq', // for now only one instance by name mvSeq is allowed
62         target_sequence_container:null,// text value (so that its a valid property)
63         target_form_text: null,
65         // what is our save mode:
66         // can save to 'api' url or 'form'
67         saveMode : 'api',
69         video_container_id:'mv_video_container',
71         video_width : 400,
72         video_height: 300,
74         sequence_tools_id:'mv_sequence_tools',
75         timeline_id:'mv_timeline',
76         plObj_id:'seq_pl',
77         plObj:'null',
79         // In pixel to second ratio ie 100pixles for every ~30seconds
80         timeline_scale:.06, 
81         
82         // Default timeline duration in seconds
83         timeline_duration:500, 
84         
85         playline_time:0,
86         track_thumb_height:60,
87         track_text_height:20,
89         // Default timeline mode: "story" (i-movie like) or "time" (finalCut like)
90         timeline_mode:'storyboard',
92         // How large are the i-movie type clips
93         track_clipThumb_height:80, 
95         // Default time to subtract or add when adjusting clips.
96         base_adj_duration:.5, 
98         // Default clipboard is empty:
99         clipboard:new Array(),
100         
101         // Stores the clipboard edit token (if user has rights to edit their User page)
102         clipboardEditToken:null,
103         
104         // Stores the sequence edit token (if user has rights to edit the current sequence)
105         sequenceEditToken:null,
106         
107         // The time the sequence was last touched (grabbed at time of startup)
108         sequenceTouchedTime:null,
109         
110         // the default config for the add media wizard
111         amw_conf: { },
113         
114         inline_playlist:'null',
115         inline_playlist_id:'null',
116         mv_pl_src:'null',
117         
118         // The edit stack (so that you can "undo" edits)
119         edit_stack:new Array(),
120         disp_menu_item:null,
121         
122         // Track Object 
123         tracks: { }
125 var mvSequencer = function( iObj ) {
126         return this.init( iObj );
128 // Set up the mvSequencer object
129 mvSequencer.prototype = {
130         // The menu_items Object contains: default html, js setup/loader functions
131         menu_items : {
132                 'clipedit': {
133                         'default':0,
134                         'html':'',
135                         'js': function( this_seq ) {
136                                 this_seq.doEditSelectedClip();
137                         },
138                         'click_js':function( this_seq ) {
139                                 this_seq.doEditSelectedClip();
140                         }
141                 },
142                 'transition': {
143                         'default':0,
144                         'html' : '<h3>' + gM( 'mwe-menu_transition' ) + '</h3>',
145                         'js':function( this_seq ) {
146                                 this_seq.doEditTransitionSelectedClip();
147                         },
148                         'click_js':function( this_seq ) {
149                                 // Highlight the transition of the selected clip:
150                                 this_seq.doEditTransitionSelectedClip();
151                         }
152                 },
153                 'cliplib': {
154                         'default':0,
155                         'html': gM( 'mwe-loading_txt' ),
156                         'js':function( this_seq ) {
157                                 // Load the search interface with sequence tool targets
158                                 mvJsLoader.doLoad( [
159                                         'remoteSearchDriver',
160                                         'seqRemoteSearchDriver'
161                                 ], function() {
162                                          this_seq.mySearch = new seqRemoteSearchDriver( this_seq );
163                                          this_seq.mySearch.doInitDisplay();
164                                 } );
165                         }
166                 },
167                 'options': {
168                         'default':0,
169                         'html' : '<h3>' + gM( 'mwe-menu_options' ) + '</h3>' +
170                                 gM( 'mwe-editor_mode' ) + '<br> ' +
171                                 '<blockquote><input type="radio" value="simple_editor" name="opt_editor">' +
172                                                 gM( 'mwe-simple_editor_desc' ) + ' </blockquote>' +
173                                 '<blockquote><input type="radio" value="advanced_editor" name="opt_editor">' +
174                                                 gM( 'mwe-advanced_editor_desc' ) + ' </blockquote>' +
175                                 gM( 'mwe-other_options' ) + '<br>' +
176                                 '<blockquote><input type="checkbox" value="contextmenu_opt" name="contextmenu_opt">' +
177                                                 gM( 'mwe-contextmenu_opt' ) + ' </blockquote>',
178                         'js':function( this_seq ) {
179                                 $j( '#options_ic input[value=\'simple_editor\']' ).attr( {
180                                         'checked':( this_seq.timeline_mode == 'storyboard' ) ? true:false
181                                 } ).click( function() {
182                                         this_seq.doSimpleTl();
183                                 } );
184                                 $j( '#options_ic input[value=\'advanced_editor\']' ).attr( {
185                                         'checked':( this_seq.timeline_mode == 'time' ) ? true:false
186                                 } ).click( function() {
187                                         this_seq.doAdvancedTl();
188                                 } );
189                                 // set up the options for context menus
190                         }
191                 }
192         },
194         // set up initial key states:
195         key_shift_down:false,
196         key_ctrl_down:false,
197         inputFocus:false,
199         init:function( iObj ) {
200                 // set up pointer to this_seq for current scope:
201                 var this_seq = this;
202                 // set the default values:
203                 for ( var i in sequencerDefaultValues ) {
204                         this[ i ] = sequencerDefaultValues[i];
205                 }
206                 for ( var i in iObj ) {
207                         // js_log('on '+ i + ' :' + iObj[i]);
208                         if ( typeof sequencerDefaultValues[i] != 'undefined' ) { // make sure its a valid property
209                                 this[i] = iObj[i];
210                         }
211                 }
213                 // check for sequence_container
214                 if ( $j( this.target_sequence_container ).length === 0 ) {
215                         js_log( "Error: missing target_sequence_container" );
216                         return false;
217                 }
219                 // $j(this.target_sequence_container).css('position', 'relative');
220                 this['base_width']  = $j( this.target_sequence_container ).width();
221                 this['base_height'] = $j( this.target_sequence_container ).height();
223                 // add the container divs (with basic layout ~universal~
224                 $j( this.target_sequence_container ).html( '' +
225                         '<div id="' + this.video_container_id + '" style="position:absolute;right:0px;top:0px;' +
226                                 'width:' + this.video_width + 'px;height:' + ( this.video_height + 54 ) + 'px;"/>' +
227                         '<div id="' + this.timeline_id + '" class="ui-widget ui-widget-content ui-corner-all" style="position:absolute;' +
228                                 'left:0px;right:0px;top:' + ( this.video_height + 60 ) + 'px;bottom:20px;overflow:auto;">' +
229                                         gM( 'mwe-loading_timeline' ) + '</div>' +
230                         '<div class="seq_status" style="position:absolute;left:0px;width:300px;"></div>' +
231                         '<div class="seq_save_cancel" style="position:absolute;' +
232                                 'left:5px;bottom:0px;height:15px;">' +
233                                         gM( 'mwe-loading_user_rights' ) +
234                         '</div>' +
235                         '<div class="about_editor" style="position:absolute;right:5px;bottom:0px;">' +
236                                 gM( 'mwe-sequencer_credit_line' ) +
237                         '</div>' +
238                         '<div id="' + this.sequence_tools_id + '" style="position:absolute;' +
239                                 'left:0px;right:' + ( this.video_width + 10 ) + 'px;top:0px;height:' + ( this.video_height + 47 ) + 'px;"/>'
240                 ).css( {
241                         'min-width':'850px'
242                 } );
244                 /*js_log('set: '+this.target_sequence_container + ' html to:'+ "\n"+
245                         $j(this.target_sequence_container).html()
246                 );*/
247                 
248                 // first check if we got a cloned PL object:
249                 // (when the editor is invoked with the plalylist already on the page)
250                 /*if( this.plObj != 'null' ){
251                         js_log('found plObj clone');
252                         //extend with mvSeqPlayList object:
253                         this.plObj = new mvSeqPlayList(this.plObj);
254                         js_log('mvSeqPlayList added: ' + this.plObj.org_control_height );
255                         $j('#'+this.video_container_id).get(0).attachNode( this.plObj );
256                         this.plObj.getHTML();
257                         this.checkReadyPlObj();
258                         return ;
259                 }*/
261                 // else check for source based sequence editor (a clean page load of the editor)
262                 if ( this.mv_pl_src != 'null' ) {
263                         js_log( ' pl src:: ' + this.mv_pl_src );
264                         var src_attr = ' src="' + this.mv_pl_src + '" ';
265                 } else {
266                         js_log( ' null playlist src .. (start empty) ' );
267                         var src_attr = '';
268                 }
269                 $j( '#' + this.video_container_id ).html( '<playlist ' + src_attr +
270                         ' style="width:' + this.video_width + 'px;height:' + this.video_height + 'px;" ' +
271                         ' sequencer="true" id="' + this.plObj_id + '" />' );
272                 rewrite_by_id( this.plObj_id );
273                 setTimeout( this.instance_name + '.checkReadyPlObj()', 25 );
274         },
275         updateSeqSaveButtons:function() {
276                 var _this = this;
277                 if ( this.sequenceEditToken ) {
278                         $j( this.target_sequence_container + ' .seq_save_cancel' ).html(
279                                 $j.btnHtml( gM( 'mwe-edit_save' ), 'seq_edit_save', 'close' ) + ' ' +
280                                 $j.btnHtml( gM( 'mwe-edit_cancel' ), 'seq_edit_cancel', 'close' )
281                         );
282                 } else {
283                         $j( this.target_sequence_container + ' .seq_save_cancel' ).html( cancel_button + gM( 'mwe-no_edit_permissions' ) );
284                 }
285                 // assing bindings
286                 $j( this.target_sequence_container + ' .seq_edit_cancel' ).unbind().click( function() {
287                         var x = window.confirm( gM( 'mwe-edit_cancel_confirm' ) );
288                         if ( x ) {
289                                 _this.closeModEditor();
290                         } else {
291                                 // close request canceled.
292                         }
293                 } );
294                 $j( this.target_sequence_container + ' .seq_edit_save' ).unbind().click( function() {
295                         // pop up progress dialog ~requesting edit line summary~
296                         // remove any other save dialog
297                         $j( '#seq_save_dialog' ).remove();
298                         $j( 'body' ).append( '<div id="seq_save_dialog" title="' + gM( 'mwe-edit_save' ) + '">' +
299                                                 '<span class="mw-summary">' +
300                                                         '<label for="seq_save_summary">Edit summary: </label>' +
301                                                 '</span>' +
302                                                 '<input id="seq_save_summary" tabindex="1" maxlength="200" value="" size="30" name="seq_save_summary"/>' +
303                                         '</div>' );
304                         var bConf = { };
305                         bConf[ gM( 'mwe-cancel' ) ] = function() {
306                                 $j( this ).dialog( 'close' );
307                         };
308                         bConf[ gM( 'mwe-edit_save' ) ] = function() {
309                                 var saveReq = {
310                                         'action'        : 'edit',
311                                         'title'         : _this.plObj.mTitle,
312                                         // the text is the sequence XML + the description
313                                         'text'          : _this.getSeqOutputHLRDXML() + "\n" +
314                                                                   _this.plObj.wikiDesc,
315                                         'token'         : _this.sequenceEditToken,
316                                         'summary'       : $j( '#seq_save_summary' ).val()
317                                 };
318                                 // change to progress bar and save:
319                                 $j( '#seq_save_dialog' ).html( '<div class="progress" /><br>' +
320                                         gM( 'mwe-saving_wait' )
321                                 )
322                                 $j( '#seq_save_dialog .progress' ).progressbar( {
323                                         value: 100
324                                 } );
325                                 // run the Seq Save Request:
326                                 do_api_req( {
327                                         'data': saveReq,
328                                         'url' : _this.getLocalApiUrl()
329                                 }, function( data ) {
330                                         $j( '#seq_save_dialog' ).html( gM( 'mwe-save_done' ) );
331                                         $j( '#seq_save_dialog' ).dialog( 'option',
332                                                 'buttons', {
333                                                         "Done":function() {
334                                                                 // refresh the page?
335                                                                 window.location.reload();
336                                                         },
337                                                         "Do More Edits": function() {
338                                                                 $j( this ).dialog( "close" );
339                                                         }
340                                         } );
341                                 } );
342                         };
343                         // dialog:
344                         $j( '#seq_save_dialog' ).dialog( {
345                                 bgiframe: true,
346                                 autoOpen: true,
347                                 modal: true,
348                                 buttons: bConf
349                         } );
350                 } )
351         },
352         // display a menu item (hide the rest)
353         disp:function( item, dispCall ) {
354                 js_log( 'menu_item disp: ' + item );
355                 this.disp_menu_item = item;
356                 // update the display and item state:
357                 if ( this.menu_items[item] ) {
358                         // update the tabs display:
359                         if ( !dispCall )
360                                 $j( "#seq_menu" ).tabs( 'select', this.menu_items[item].inx );
362                         this.menu_items[item].default = 1;
363                         // do any click_js actions:getInsertControl
364                         if ( this.menu_items[item].click_js )
365                                 this.menu_items[item].click_js( this );
366                 }
367         },
368         // setup the menu items:
369         setupMenuItems:function() {
370                 js_log( 'loadInitMenuItems' );
371                 var this_seq = this;
372                 // do all the menu_items setup:  @@we could defer this to once the menu item is requested
373                 for ( var i in this.menu_items ) {
374                         if (    this.menu_items[i].js )
375                                 this.menu_items[i].js( this );
376                 }
377         },
378         renderTimeLine:function() {
379                 // empty out the top level html:
380                 $j( '#' + this.timeline_id ).html( '' );
381                 // add html general for timeline
382                 if ( this.timeline_mode == 'time' ) {
383                         $j( '#' + this.timeline_id ).html( '' +
384                                 '<div id="' + this.timeline_id + '_left_cnt" class="mv_tl_left_cnt">' +
385                                         '<div id="' + this.timeline_id + '_head_control" style="position:absolute;top:0px;left:0px;right:0px;height:30px;">' +
386                                                 '<a title="' + gM( 'mwe-play_from_position' ) + '" href="javascript:' + this.instance_name + '.play_jt()">' +
387                                                         '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/control_play_blue.png">' +
388                                                 '</a>' +
389                                                 '<a title="' + gM( 'mwe-zoom_in' ) + '" href="javascript:' + this.instance_name + '.zoom_in()">' +
390                                                         '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/zoom_in.png">' +
391                                                 '</a>' +
392                                                 '<a title="' + gM( 'mwe-zoom_out' ) + '" href="javascript:' + this.instance_name + '.zoom_out()">' +
393                                                         '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/zoom_out.png">' +
394                                                 '</a>' +
395                                                 '<a title="' + gM( 'mwe-cut_clip' ) + '" href="javascript:' + this.instance_name + '.cut_mode()">' +
396                                                         '<img style="width:16px;height:16px;border:0" src="' + mv_embed_path + 'images/cut.png">' +
397                                                         '</a>' +
398                                         '</div>' +
399                                 '</div>' +
400                                 '<div id="' + this.timeline_id + '_tracks" class="mv_seq_tracks">' +
401                                         '<div id="' + this.timeline_id + '_head_jump" class="mv_head_jump" style="position:absolute;top:0px;left:0px;height:20px;"></div>' +
402                                         '<div id="' + this.timeline_id + '_playline" class="mv_playline"></div>' +
403                                 '</div>'
404                         );
405                         // add playlist hook to update timeline
406                         this.plObj.update_tl_hook = this.instance_name + '.update_tl_hook';
407                         var this_sq = this;
408                         var top_pos = 25;
409                         // add tracks:
410                         for ( var i in this.plObj.tracks ) {
411                                 var track = this.plObj.tracks[i];
412                                 // js_log("on track: "+ i + ' t:'+ $j('#'+this.timeline_id+'_left_cnt').html() );
413                                 // set up track based on disp type
414                                 switch( track.disp_mode ) {
415                                         case 'timeline_thumb':
416                                                 var track_height = 60;
417                                                 var exc_img = 'opened';
418                                                 var exc_action = 'close';
419                                                 var exc_msg = gM( 'mwe-collapse_track' );
420                                         break;
421                                         case 'text':
422                                                 var track_height = 20;
423                                                 var exc_img = 'closed';
424                                                 var exc_action = 'open';
425                                                 var exc_msg = gM( 'mwe-expand_track' );
426                                         break;
427                                 }
428                                 // add track name:
429                                 $j( '#' + this.timeline_id + '_left_cnt' ).append(
430                                         '<div id="track_cnt_' + i + '" style="top:' + top_pos + 'px;height:' + track_height + 'px;" class="track_name">' +
431                                                 '<a id="mv_exc_' + i + '" title="' + exc_msg + '" href="javascript:' + this_sq.instance_name + '.exc_track(' + i + ',\'' + exc_action + '\')">' +
432                                                         '<img id="' + this_sq.timeline_id + '_close_expand" style="width:16px;height:16px;border:0" ' +
433                                                                 ' src="' + mv_embed_path + 'images/' + exc_img + '.png">' +
434                                                 '</a>' +
435                                         track.title + '</div>'
436                                 );
437                                 // also render the clips in the trackset container: (thumb or text view)
438                                 $j( '#' + this.timeline_id + '_tracks' ).append(
439                                         '<div id="container_track_' + i + '" style="top:' + top_pos + 'px;height:' + ( track_height + 2 ) + 'px;left:0px;right:0px;" class="container_track" />'
440                                 );
441                                 top_pos += track_height + 20;
442                         }
443                 }
444                 if ( this.timeline_mode == 'storyboard' ) {
445                         var top_pos = this.plObj.org_control_height;
446                         // debugger;
447                         for ( var i in this.plObj.tracks ) {
448                                 var track_height = this.track_clipThumb_height;
449                                 var timeline_id = this.timeline_id
450                                 // add in play box and container tracks
451                                 $j( '#' + timeline_id ).append( '' +
452                                         '<div id="interface_container_track_' + i + '" ' +
453                                         '       style="position:absolute;top:5px;height:' + ( track_height + 30 ) + 'px;left:10px;right:0px;"' +
454                                         '>' +
455                                                 '<div id="container_track_' + i + '" style="position:relative;top:0px;' +
456                                                         'height:' + ( track_height + 30 ) + 'px;left:0px;right:0px;" class="container_track">' +
457                                                 '</div>' +
458                                                 '<div id="' + timeline_id + '_playline" class="mv_story_playline">' +
459                                                         '<div class="mv_playline_top"/>' +
460                                                 '</div>' +
461                                         '</div>'
462                                 );
463                                 top_pos += track_height + 20;
464                         }
465                 }
466         },
467         // once playlist is ready continue
468         checkReadyPlObj:function() {
469                 // set up pointers from sequencer to pl obj
470                 this.plObj = $j( '#' + this.plObj_id ).get( 0 );
471                 // & from seq obj to sequencer
472                 this.plObj.pSeq = this;
474                 if ( this.plObj )
475                         if ( ! this.plObj.loading )
476                                 this.plReadyInit();
478                 // else keep checking for the playlist to be ready
479                 if ( this.plObj.loading ) {
480                         if ( this.plReadyTimeout == 200 ) {
481                                 js_error( 'error playlist never ready' );
482                         } else {
483                                 this.plReadyTimeout++;
484                                 setTimeout( this.instance_name + '.checkReadyPlObj()', 25 );
485                         }
486                 }
487         },
488         getLocalApiUrl:function() {
489                 return this.plObj.interface_url;
490         },
491         plReadyInit:function() {
492                 var _this = this;
493                 js_log( 'plReadyInit' );
494                 js_log( this.plObj );
495                 // give the playlist a pointer to its parent seq:
496                 this.plObj['seqObj'] = this;
498                 // update playlist (if its empty right now)
499                 if ( this.plObj.getClipCount() == 0 ) {
500                         $j( '#' + this.plObj_id ).html( 'empty playlist' );
501                 }
503                 // propagate the edit tokens
504                 // if on an edit page just grab from the form:
505                 this.sequenceEditToken = $j( 'input[wpEditToken]' ).val();
507                 if ( typeof this.sequenceEditToken == 'undefined' && this.getLocalApiUrl() != null ) {
508                         get_mw_token( _this.plObj.mTitle, _this.getLocalApiUrl(),
509                                 function( token ) {
510                                         if ( token ) {
511                                                 _this.sequenceEditToken = token;
512                                                 _this.updateSeqSaveButtons();
513                                         }
514                                 }
515                         );
516                         get_mw_token( _this.plObj.mTalk, _this.getLocalApiUrl(),
517                                 function( token ) {
518                                         _this.clipboardEditToken = token;
519                                 }
520                         );
521                         // also grab permissions for sending clipboard commands to the server
523                         // (calling the sequencer inline) try and get edit token via api call:
524                         // (somewhat fragile way to get at the api... should move to config
525                         /*var token_url = this.plObj.interface_url.replace(/index\.php/, 'api.php');
526                         token_url += '?action=query&format=xml&prop=info&intoken=edit&titles=';
527                         $j.ajax({
528                                 type: "GET",
529                                 url: token_url + this_seq.plObj.mTitle,
530                                 success:function(data){
531                                         var pageElm = data.getElementsByTagName('page')[0];
532                                         if( $j(pageElm).attr('edittoken') ){
533                                                 this_seq.sequenceEditToken = $j(pageElm).attr('edittoken');
534                                         }
536                                 }
537                         });*/
538                         // also grab permissions for sending clipboard commands to the server
539                         /*$j.ajax({
540                                 type:"GET",
541                                 url: token_url + this_seq.plObj.mTalk,
542                                 success:function(data){
543                                         var pageElm = data.getElementsByTagName('page')[0];
544                                         if( $j(pageElm).attr('edittoken') ){
545                                                 this_seq.clipboardEditToken = $j(pageElm).attr('edittoken');
546                                         }
547                                 }
548                         });*/
549                 }
552                 // Render the menu tabs::
553                 var item_containers = '';
554                 var inx = 0;
555                 var selected_tab = 0;
556                 var tabc = '';
557                 var o = '<div id="seq_menu" style="width:100%;height:100%">';
558                 o += '<ul>';
559                 for ( var tab_id in this.menu_items ) {
560                         menu_item = this.menu_items[tab_id];
561                         menu_item.inx = inx;
562                         if ( menu_item.default ) {
563                                 selected_tab = inx;
564                                 _this.disp_menu_item = tab_id;
565                         }
567                         o += '<li>' +
568                                         '<a id="mv_menu_item_' + tab_id + '" href="#' + tab_id + '_ic">' + gM( 'mwe-menu_' + tab_id ) + '</a>' +
569                                 '</li>';
571                         tabc += '<div id="' + tab_id + '_ic" style="overflow:auto;height:268px;" >';
572                                 tabc += ( menu_item.html ) ? menu_item.html : '<h3>' + gM( 'mwe-menu_' + tab_id ) + '</h3>';
573                         tabc += '</div>';
574                         inx++;
575                 };
576                 o += '</ul>';
577                 o += tabc;
578                 $j( '#' + this.sequence_tools_id ).html( o );
581                 $j( "#seq_menu" ).tabs( {
582                         selected:selected_tab,
583                         select: function( event, ui ) {
584                                 _this.disp( $j( ui.tab ).attr( 'id' ).replace( 'mv_menu_item_', '' ), true );
585                         }
586                 // Add sorting
587                 } ).find( ".ui-tabs-nav" ).sortable( { axis : 'x' } );
590                 // Render the timeline
591                 this.renderTimeLine();
592                 this.do_refresh_timeline();
594                 // Load initial content into containers
595                 this.setupMenuItems();
597                 this.doFocusBindings();
599                 // Set up key bidnings
600                 $j( window ).keydown( function( e ) {
601                         js_log( 'pushed down on:' + e.which );
602                         if ( e.which == 16 )
603                                 _this.key_shift_down = true;
605                         if ( e.which == 17 )
606                                 _this.key_ctrl_down = true;
608                         if ( ( e.which == 67 && _this.key_ctrl_down ) && !_this.inputFocus )
609                                 _this.copySelectedClips();
611                         if ( ( e.which == 88 && _this.key_ctrl_down ) && !_this.inputFocus )
612                                 _this.cutSelectedClips();
614                         // Paste cips on v + ctrl while not focused on a text area:
615                         if ( ( e.which == 86 && _this.key_ctrl_down ) && !_this.inputFocus )
616                                 _this.pasteClipBoardClips();
618                 } );
619                 $j( window ).keyup( function( e ) {
620                         js_log( 'key up on ' + e.which );
621                         // User let go of "shift" turn off multi-select
622                         if ( e.which == 16 )
623                                 _this.key_shift_down = false;
625                         if ( e.which == 17 )
626                                 _this.key_ctrl_down = false;
628                         // Escape key ( deselect )
629                         if ( e.which == 27 )
630                                 _this.deselectClip();
633                         // Backspace or Delete key while not focused on a text area:
634                         if ( ( e.which == 8 || e.which == 46 ) && !_this.inputFocus )
635                                 _this.removeSelectedClips();
636                 } );
637         },
638         /**
639         * Check all text nodes for focus
640         */
641         doFocusBindings:function() {
642                 var _this = this;
643                 // if an input or text area has focus disable delete key binding
644                 $j( "input,textarea" ).focus( function () {
645                         js_log( "inputFocus:true" );
646                         _this.inputFocus = true;
647                 } );
648                 $j( "input,textarea" ).blur( function () {
649                         js_log( "inputFocus:blur" );
650                         _this.inputFocus = false;
651                 } )
652         },
653         /*
654         * Update the timeline hook
655         */
656         update_tl_hook:function( jh_time_ms ) {
657                 // Put into seconds scale:
658                 var jh_time_sec_float = jh_time_ms / 1000;
659                 // Render playline at given time
660                 $j( '#' + this.timeline_id + '_playline' )
661                         .css( 
662                                 'left', 
663                                 Math.round( jh_time_sec_float / this.timeline_scale ) + 'px' 
664                         );
665                 // js_log('at time:'+ jh_time_sec + ' px:'+ Math.round(jh_time_sec_float/this.timeline_scale));
666         },
667         /*
668         * Returns a xml or json representation of the current sequence 
669         */
670         getSeqOutputJSON:function() {
671                 js_log( 'json output:' );
672         },
673         /*
674         * Gets the Sequence as a formated high level resource description xml string
675         * @returns {xml} 
676         */
677         getSeqOutputHLRDXML:function() {
678                 var o = '<sequence_hlrd>' + "\n";
679                 o += "\t<head>\n";
680                 // Get transitions
681                 for ( var i in this.plObj.transitions ) {
682                         if ( this.plObj.transitions[i] ) {
683                                 var tObj = this.plObj.transitions[i].getAttributeObj();
684                                 o += "\t\t<transition ";
685                                 for ( var j in tObj ) {
686                                         o += ' ' + j + '="' + tObj[j] + '"\n\t\t';
687                                 }
688                                 o += '/>' + "\n"; // transitions don't have children
689                         }
690                 }
691                 o += "\t</head>\n";
693                 // Get clips
694                 o += "\t<body>\n";
695                 // Output each track:
696                 for ( var i in this.plObj.tracks ) {
697                         var curTrack = this.plObj.tracks[i];
698                         o += "\t<seq";
699                                 var tAttr = curTrack.getAttributeObj();
700                                 for ( var j in  tAttr ) {
701                                         o += ' ' + j + '="' + tAttr[j] + '"\n\t\t\t';
702                                 }
703                         o += ">\n";
704                         for ( var k in curTrack.clips ) {
705                                 var curClip = curTrack.clips[k];
706                                 o += "\t\t<ref ";
707                                         var cAttr = curClip.getAttributeObj();
708                                         var lt = '';
709                                         for ( var j in  cAttr ) {
710                                                 var val =  ( j == 'transIn' || j == 'transOut' ) ? cAttr[j].id : cAttr[j];
711                                                 o += lt + j + '="' + val + '"';
712                                                 lt = "\n\t\t";
713                                         }
714                                 o += ">\n" // close the clip
715                                 for ( var pName in curClip.params ) {
716                                         var pVal = curClip.params[pName];
717                                         o += "\t\t\t" + '<param name="' + pName + '">' + pVal + '</param>' + "\n";
718                                 }
719                                 o += "\t\t</ref>\n\n";
720                         }
721                         o += "\n</seq>\n";
722                 }
723                 o += "\t</body>\n";
724                 // Close the tag
725                 o += '</sequence_hlrd>';
727                 return o;
728         },
729         /**
730         * Takes a track index and a clip index, to get a clip Object.
731         * It then calls doEditClip with that clip Object. 
732         */
733         editClip:function( track_inx, clip_inx ) {
734                 var cObj = this.plObj.tracks[ track_inx ].clips[ clip_inx ];
735                 this.doEditClip( cObj );
736         },
737         /**
738         * Calls the doEditClip interface on the selected clip
739         * Handles cases where no clips are selected or multiple clips are selected.  
740         */
741         doEditSelectedClip:function() {
742                 js_log( "f:doEditSelectedClip:" );
743                 // And only one clip selected
744                 if ( $j( '.mv_selected_clip' ).length == 1 ) {
745                         this.doEditClip( this.getClipFromSeqID( $j( '.mv_selected_clip' ).parent().attr( 'id' ) ) );
746                 } else if ( $j( '.mv_selected_clip' ).length === 0 ) {
747                         // No clip selected warning:
748                         $j( '#clipedit_ic' ).html( gM( 'mwe-no_selected_resource' ) );
749                 } else {
750                         // Multiple clip selected warning:
751                         $j( '#clipedit_ic' ).html( gM( 'mwe-error_edit_multiple' ) );
752                 }
753         },
754         /**
755         * Pulls up the edit transition interface for the selected clip
756         */
757         doEditTransitionSelectedClip:function() {
758                 var _this = this;
759                 js_log( "f:doEditTransitionSelectedClip:" + $j( '.mv_selected_clip' ).length );
760                 if ( $j( '.mv_selected_clip' ).length == 1 ) {
761                         _this.doEditTransition( _this.getClipFromSeqID( $j( '.mv_selected_clip' ).parent().attr( 'id' ) ) );
762                 } else if ( $j( '.mv_selected_clip' ).length === 0 ) {
763                         // no clip selected warning:
764                         $j( '#transition_ic' ).html( gM( 'mwe-no_selected_resource' ) );
765                 } else {
766                         // multiple clip selected warning:
767                         $j( '#transition_ic' ).html( gM( 'mwe-error_edit_multiple' ) );
768                 }
769         },
770         /**
771         * Loads the transition edit javascript libs and 
772         * displays the transition edit interface. 
773         */
774         doEditTransition:function( cObj ) {
775                 js_log( "sequence:doEditTransition" );
776                 var _this = this;
777                 // Add a loading image
778                 mv_get_loading_img( '#transitions_ic' );
779                 mvJsLoader.doLoad( [
780                         '$j.fn.ColorPicker',
781                         'mvTimedEffectsEdit'
782                 ], function() {
783                         // For some reason we lose scope in the options passed to mvTimedEffectsEdit
784                         // so we re refrence the sequence here: 
785                         var localSeqRef = _this;
786                         _this.myEffectEdit = new mvTimedEffectsEdit( {
787                                 'rObj'           : cObj,
788                                 'control_ct' : 'transition_ic',
789                                 'pSeq'   : localSeqRef
790                         } );
791                 } )
792         },
793         /*
794         * Updates the clip details div if edit resource is set
795         */
796         doEditClip:function( cObj ) {
797                 js_log( 'seq:doEditClip' );
798                 var _this = this;
800                 // Set default edit action
801                 var edit_action = 'fileopts';
803                 mv_get_loading_img( '#clipedit_ic' );
804                 
805                 // Load the clipEdit library if not already loaded:
806                 mvJsLoader.doLoad( [
807                         'mvClipEdit'
808                 ], function() {
809                         // Zero out the current editor:                         
810                         _this.myClipEditor = { };
811                         // Setup the cliploader options
812                         _this.myClipEditor = new mvClipEdit( {
813                                 'rObj'                  : cObj,
814                                 'control_ct'    : 'clipedit_ic',
815                                 'clip_disp_ct'  : cObj.id,
816                                 'edit_action'   : edit_action,
817                                 'p_seqObj'              : _this,
818                                 'profile'               : 'sequence'
819                         } );
820                 } );
821         },
822         
823         /*
824         * Save new clip segment
825         * FIXME this is just a stub
826         */
827         saveClipEdit:function() {
828                 // saves the clip updates
829         },
830         
831         /**
832         * Closes the sequence and dereferences the global instance. 
833         */ 
834         closeModEditor:function() {
835                 // unset the sequencer
836                 _global['mvSeq'] = null;
837                 $j( this.target_sequence_container + ',.ui-widget-overlay' ).remove();
838         },
839         
840         /**
841         * Copies the selected clips to the server hosted "clipboard"
842         * 
843         * FIXME need to support local clipboard for stand alone editing.  
844         * FIXME this does not really work at all right now
845         */
846         copySelectedClips:function() {
847                 var this_seq = this;
848                 // set all the selected clips
849                 this.clipboard = new Array();
850                 $j( '.mv_selected_clip' ).each( function() {
852                         // Add each clip to the clip board:
853                         var cur_clip = this_seq.getClipFromSeqID( $j( this ).parent().attr( 'id' ) );
854                         this_seq.clipboard.push( cur_clip.getAttributeObj() );
855                         
856                 } );
857                 
858                 // Upload clipboard to the server (if possible)
859                 if ( mw.parseUri(  document.URL ).host != mw.parseUri( this_seq.plObj.interface_url ).host ) {
860                         js_log( 'error: presently we can\'t copy clips across domains' );
861                 } else {
862                         // FIXME we need to add an api entry point to store a "clipboard"
863                         // right now this is dependent on a custom hook:                        
864                         if ( this_seq.clipboardEditToken && this_seq.plObj.interface_url ) {
865                                 var req_url = this_seq.plObj.interface_url.replace( /api.php/, 'index.php' ) + '?action=ajax&rs=mv_seqtool_clipboard&rsargs[]=copy';
866                                 $j.ajax( {
867                                         type: "POST",
868                                         url : req_url,
869                                         data: $j.param( {
870                                                 "clipboard_data": $j.toJSON( this_seq.clipboard ),
871                                                 "clipboardEditToken": this_seq.clipboardEditToken
872                                         } ),
873                                         success:function( data ) {
874                                                 js_log( 'did clipboard push ' + $j.toJSON( this_seq.clipboard ) );
875                                         }
876                                 } );
877                         } else {
878                                 js_log( 'error: no clipboardEditToken to uplaod clipboard to server' );
879                         }
880                 }
881         },
882         /*
883         * Paste the clipboard clips into the sequence
884         */
885         pasteClipBoardClips:function() {
886                 js_log( 'f:pasteClipBoardClips' );
887                 // @@todo query the server for updated clipboard
888                 // paste before the "current clip"
889                 this.addClips( this.clipboard, this.plObj.cur_clip.order );
890         },
891         
892         /** 
893         * Cut selected clips from the timeline
894         */
895         cutSelectedClips:function() {
896                 this.copySelectedClips();
897                 this.removeSelectedClips();
898         },
899         
900         /**
901         * Remove selected clips from the timeline
902         */ 
903         removeSelectedClips:function() {
904                 var remove_clip_ary = new Array();
905                 // Remove selected clips from display
906                 $j( '.container_track .mv_selected_clip' ).each( function() {
907                         // grab the track index from the id (assumes track_#_clip_#
908                         remove_clip_ary.push ( $j( this ).parent().attr( 'id' ).replace( 'track_', '' ).replace( 'clip_', '' ).split( '_' ) );
909                 } );
910                 if ( remove_clip_ary.length != 0 )
911                         this.removeClips( remove_clip_ary );
913                 // doEdit selected clips (updated selected resource)
914                 // @@todo refresh menu of current
915                 this.doEditSelectedClip();
916         },
917         /*
918         * Add a clip to the timeline
919         */      
920         addClip:function( clip, before_clip_pos, track_inx ) {
921                 this.addClips( [clip],  before_clip_pos, track_inx )
922         },
923         /** 
924         * add a single or set of clips
925         * to a given position and track_inx
926         */
927         addClips:function( clipSet, before_clip_pos, track_inx ) {
928                 this_seq = this;
930                 if ( !track_inx )
931                         track_inx = this.plObj.default_track.inx;
933                 if ( !before_clip_pos )
934                         before_clip_pos = this.plObj.default_track.getClipCount();
936                 js_log( "seq: add clip: at: " + before_clip_pos + ' in track: ' + track_inx );
937                 var cur_pos = before_clip_pos;
939                 $j.each( clipSet, function( inx, clipInitDom ) {
940                         var mediaElement = document.createElement( 'ref' );
941                         for ( var i in clipInitDom ) {
942                                 js_log( "set: " + i + ' to ' + clipInitDom[i] );
943                                 if ( i != 'id' )
944                                         $j( mediaElement ).attr( i, clipInitDom[i] );
945                         }
946                         if ( this_seq.plObj.tryAddMedia(        mediaElement, cur_pos, track_inx ) )
947                                 cur_pos++;
948                 } );
949                 // debugger;
950                 this.do_refresh_timeline();
951         },
952         
953         /**
954         * Removes Clips listed in the remove_clip_ary paramater
955         */
956         removeClips:function( remove_clip_ary ) {
957                 var this_seq = this;
958                 var jselect = coma = '';
959                 js_log( 'clip count before removal : ' + this_seq.plObj.default_track.clips.length + ' should remove ' + remove_clip_ary.length );
960                 var afected_tracks = new Array();
961                 // add order to track_clip before we start removing:
962                 $j.each( remove_clip_ary, function( inx, track_clip ) {
963                         remove_clip_ary[inx]['order'] = this_seq.plObj.tracks[ track_clip[0] ].clips[ track_clip[1] ].order;
964                 } );
965                 $j.each( remove_clip_ary, function( inx, track_clip ) {
966                         var track_inx = track_clip[0];
967                         var clip_inx = track_clip[1];
968                         var clip_rm_order = track_clip['order'];
969                         js_log( 'remove t:' + track_inx + ' c:' + clip_inx + ' id:' + ' #track_' + track_inx + '_clip_' + clip_inx + ' order:' + clip_rm_order );
970                         // remove the clips from the base tracks
971                         for ( var i in this_seq.plObj.tracks[ track_inx ].clips ) {
972                                 cur_clip = this_seq.plObj.tracks[ track_inx ].clips[i]
973                                 if ( cur_clip.order == clip_rm_order ) {
974                                         this_seq.plObj.tracks[ track_clip[0] ].clips.splice( i, 1 );
975                                 }
976                         }
977                         // add track to affected track list:
978                         afected_tracks[ track_inx ] = true;
979                         jselect += coma + '#track_' + track_inx + '_clip_' + clip_inx;
980                         coma = ',';
981                 } );
982                 // update/ reorder:
983                 $j.each( afected_tracks, function( track_inx, affected ) {
984                         this_seq.plObj.tracks[track_inx].reOrderClips();
985                 } );
987                 js_log( 'clip count after removal : ' + this_seq.plObj.default_track.clips.length );
988                 // animate the removal (@@todo should be able to call the resulting fadeOut only once without a flag)
989                 var done_with_refresh = false;
990                 $j( jselect ).fadeOut( "slow", function() {
991                         if ( !done_with_refresh )
992                                 this_seq.do_refresh_timeline();
993                         done_with_refresh = true;
994                 } ).empty(); // empty to remove any persistent bindings
995         },
996         doEdit:function( editObj ) {
997                 // add the current editObj to the edit stack (should allow for "undo")
998                 this.edit_stack.push( editObj );
999                 // make the adjustments
1000                 this.makeAdjustment( editObj );
1001         },
1002         /*
1003         * takes adjust ment object with options:
1004         * track_inx, clip_inx, start, end delta
1005         */
1006         makeAdjustment:function( e ) {
1007                 switch( e.type ) {
1008                         case 'resize_start':
1009                                 this.plObj.tracks[e.track_inx].clips[e.clip_inx].doAdjust( 'start', e.delta );
1010                         break;
1011                         case 'resize_end':
1012                                  this.plObj.tracks[e.track_inx].clips[e.clip_inx].doAdjust( 'end', e.delta );
1013                         break;
1014                 }
1015                 js_log( 're render: ' + e.track_inx );
1016                 // refresh the playlist after adjustment
1017                 this.do_refresh_timeline();
1018         },
1019         // @@todo set up key bindings for undo
1020         undoEdit:function() {
1021                 var editObj = this.edit_stack.pop();
1022                 // invert the delta
1024         },
1025         exc_track:function( inx, req ) {
1026                 this_seq = this;
1027                 if ( req == 'close' ) {
1028                         $j( '#mv_exc_' + inx ).attr( 'href', 'javascript:' + this.instance_name + '.exc_track(' + inx + ',\'open\')' );
1029                         $j( '#mv_exc_' + inx + ' > img' ).attr( 'src', mv_embed_path + 'images/closed.png' );
1030                         $j( '#track_cnt_' + inx + ',#container_track_' + inx ).animate( { height:this.track_text_height }, "slow", '',
1031                                 function() {
1032                                         this_seq.plObj.tracks[inx].disp_mode = 'text';
1033                                         this_seq.render_tracks( inx );
1034                                 } );
1035                 } else if ( req == 'open' ) {
1036                         $j( '#mv_exc_' + inx ).attr( 'href', 'javascript:' + this.instance_name + '.exc_track(' + inx + ',\'close\')' );
1037                         $j( '#mv_exc_' + inx + ' > img' ).attr( 'src', mv_embed_path + 'images/opened.png' );
1038                         $j( '#track_cnt_' + inx + ',#container_track_' + inx ).animate( { height:this.track_thumb_height }, "slow", '',
1039                                 function() {
1040                                         this_seq.plObj.tracks[inx].disp_mode = 'timeline_thumb';
1041                                         this_seq.render_tracks( inx );
1042                                 } );
1044                 }
1045         },
1046         // adds tracks
1047         add_track:function( inx, track ) {
1049         },
1050         // toggle cut mode (change icon to cut)
1051         cut_mode:function() {
1052                 js_log( 'do cut mode' );
1053                 // add cut layer ontop of clips
1054         },
1055         doAdvancedTl:function() {
1056                 this.timeline_mode = 'time';
1057                 this.renderTimeLine();
1058                 this.do_refresh_timeline();
1059                 return false;
1060         },
1061         doSimpleTl:function() {
1062                 this.timeline_mode = 'storyboard';
1063                 this.renderTimeLine();
1064                 this.do_refresh_timeline();
1065                 return false;
1066         },
1067         // renders updates the timeline based on the current scale
1068         render_tracks:function( track_inx ) {
1069                 js_log( "f::render track: " + track_inx );
1070                 var this_seq = this;
1071                 // inject the tracks into the timeline (if not already there)
1072                 for ( var track_id in this.plObj.tracks ) {
1073                         if ( track_inx == track_id || typeof track_inx == 'undefined' ) {
1074                                 // empty out the track container:
1075                                 // $j('#container_track_'+track_id).empty();
1076                                 var track_html = droppable_html = '';
1077                                 // set up per track vars:
1078                                 var track = this.plObj.tracks[track_id];
1079                                 var cur_clip_time = 0;
1081                                 // set up some constants for timeline_mode == storyboard:
1082                                 if ( this.timeline_mode == 'storyboard' ) {
1083                                         var frame_width = Math.round( this.track_clipThumb_height * 1.3333333 );
1084                                         var container_width = frame_width + 60;
1085                                 }
1087                                 // for each clip:
1088                                 for ( var j in track.clips ) {
1089                                         clip = track.clips[j];
1090                                         // var img = clip.getClipImg('icon');
1091                                         if ( this.timeline_mode == 'storyboard' ) {
1092                                                 clip.left_px = j * container_width;
1093                                                 clip.width_px = container_width;
1094                                                 var base_id = 'track_' + track_id + '_clip_' + j;
1095                                                 track_html += '<span id="' + base_id + '" ' +
1096                                                                                 'class="mv_storyboard_container mv_clip_drag" ' +
1097                                                                                 'style="' +
1098                                                                                 'left:' + clip.left_px + 'px;' +
1099                                                                                 'height:' + ( this.track_clipThumb_height + 30 ) + 'px;' +
1100                                                                                 'width:' + ( container_width ) + 'px;" >';
1101                                                 track_html += clip.embed.renderTimelineThumbnail( {
1102                                                                                 'width' : frame_width,
1103                                                                                 'thumb_class' : 'mv_clip_thumb',
1104                                                                                 'height':this.track_clipThumb_height,
1105                                                                                 'time':0
1106                                                                         } );
1107                                                 // render out edit button
1108                                                 /*track_html+='<div class="clip_edit_button clip_edit_base clip_control"/>';*/
1110                                                 // check if the clip has transitions
1111                                                 var imgHtml = '';
1112                                                 var imsrc = '';
1113                                                 var cat = clip;
1114                                                 if ( clip.transIn || clip.transOut ) {
1115                                                         if ( clip.transIn && clip.transIn.getIconSrc )
1116                                                                 imsrc = clip.transIn.getIconSrc();
1117                                                         // @@todo put transOut somewhere else
1118                                                         if ( clip.transOut && clip.transOut.getIconSrc )
1119                                                                 imsrc = clip.transOut.getIconSrc();
1120                                                         if ( imsrc != '' )
1121                                                                 imgHtml = '<img style="width:32px;height:32px" src="' + imsrc + '" />';
1122                                                 }
1123                                                 // render out transition edit box
1124                                                 track_html +=   '<div id="tb_' + base_id + '"  class="clip_trans_box">' +
1125                                                                                         imgHtml +
1126                                                                                 '</div>'
1128                                                 // render out adjustment text
1129                                                 /*track_html+='<div id="' + base_id + '_adj' + '" class="mv_adj_text" style="top:'+ (this.track_clipThumb_height+10 )+'px;">'+
1130                                                                                 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'-\')" /> - </span>'+
1131                                                                                   ( (clip.getDuration() > 60 )? seconds2npt(clip.getDuration()): clip.getDuration() )  +
1132                                                                                 '<span class="mv_adjust_click" onClick="'+this.instance_name+'.adjClipDur(' + track_id + ',' + j + ',\'+\')" /> + </span>'+
1133                                                                         '</div>';
1134                                                 */
1135                                                 track_html += '</span>';
1137                                         }
1138                                         // do timeline_mode rendering:
1139                                         if ( this.timeline_mode == 'time' ) {
1140                                                 clip.left_px = Math.round( cur_clip_time / this.timeline_scale );
1141                                                 clip.width_px = Math.round( Math.round( clip.getDuration() ) / this.timeline_scale );
1142                                                 clip.height_px = 60;
1143                                                 js_log( 'at time:' + cur_clip_time + ' left: ' + clip.left_px + ' clip dur: ' +  Math.round( clip.getDuration() ) + ' clip width:' + clip.width_px );
1145                                                 // for every clip_width pixle output image
1146                                                 if ( track.disp_mode == 'timeline_thumb' ) {
1147                                                         track_html += '<span id="track_' + track_id + '_clip_' + j + '" ' +
1148                                                                                         'class="mv_tl_clip mv_clip_drag" ' +
1149                                                                                         'style="' +
1150                                                                                                 'left:' + clip.left_px + 'px;' +
1151                                                                                                 'width:' + clip.width_px + 'px;' +
1152                                                                                                 'height:' + clip.height_px + 'px" >';
1153                                                         track_html += this.render_clip_frames( clip );
1154                                                 } else if ( track.disp_mode == 'text' ) {
1155                                                         // '+left_px+
1156                                                         track_html += '<span id="track_' + track_id + '_clip_' + j + '" style="left:' + clip.left_px + 'px;' +
1157                                                                 'width:' + clip.width_px + 'px;background:' + clip.getColor() +
1158                                                                         '" class="mv_time_clip_text mv_clip_drag">' + clip.title;
1159                                                 }
1160                                                 // add in per clip controls
1161                                                 track_html += '<div title="' + gM( 'mwe-clip_in' ) + ' ' + clip.embed.start_ntp + '" class="ui-resizable-w ui-resizable-handle" style="width: 16px; height: 16px; left: 0px; top: 2px;background:url(\'' + mv_embed_path + 'images/application_side_contract.png\');" ></div>' + "\n";
1162                                                 track_html += '<div title="' + gM( 'mwe-clip_out' ) + ' ' + clip.embed.end_ntp + '" class="ui-resizable-e ui-resizable-handle" style="width: 16px; height: 16px; right: 0px; top: 2px;background:url(\'' + mv_embed_path + 'images/application_side_expand.png\');" ></div>' + "\n";
1163                                                 track_html += '<div title="' + gM( 'mwe-rmclip' ) + '" onClick="' + this.instance_name + '.removeClips(new Array([' + track_id + ',' + j + ']))" style="position:absolute;cursor:pointer;width: 16px; height: 16px; left: 0px; bottom:2px;background:url(\'' + mv_embed_path + 'images/delete.png\');"></div>' + "\n";
1164                                                 track_html += '<span style="display:none;" class="mv_clip_stats"></span>';
1166                                                 track_html += '</span>';
1167                                                 // droppable_html+='<div id="dropBefore_'+i+'_c_'+j+'" class="mv_droppable" style="height:'+this.track_thumb_height+'px;left:'+clip.left_px+'px;width:'+Math.round(clip.width_px/2)+'px"></div>';
1168                                                 // droppable_html+='<div id="dropAfter_'+i+'_c_'+j+'" class="mv_droppable" style="height:'+this.track_thumb_height+'px;left:'+(clip.left_px+Math.round(clip.width_px/2))+'px;width:'+(clip.width_px/2)+'px"></div>';
1169                                                 cur_clip_time += Math.round( clip.getDuration() ); // increment cur_clip_time
1170                                         }
1172                                 }
1174                                 // js_log("new htmL for track i: "+track_id + ' html:'+track_html);
1175                                 $j( '#container_track_' + track_id ).html( track_html );
1177                                 // apply transition click action
1178                                 $j( '.clip_trans_box' ).click( function() {
1179                                         if ( $j( this ).hasClass( 'mv_selected_transition' ) ) {
1180                                                 $j( this ).removeClass( 'mv_selected_transition' );
1181                                                 this_seq.deselectClip( $j( this ).siblings( '.mv_clip_thumb' ).get( 0 ) );
1182                                         } else {
1183                                                 // deselect others
1184                                                 this_seq.deselectClip();
1185                                                 $j( '.clip_trans_box' ).removeClass( 'mv_selected_transition' );
1186                                                 $j( this ).addClass( "mv_selected_transition" );
1187                                                 $j( this ).siblings( '.mv_clip_thumb' ).addClass( "mv_selected_clip" );
1188                                                 var sClipObj = this_seq.getClipFromSeqID( $j( this ).parent().attr( 'id' ) );
1189                                                 // jump to the current clip
1190                                                 this_seq.plObj.updateCurrentClip( sClipObj  );
1191                                                 // display the transition edit tab:
1192                                                 this_seq.disp( 'transition' );
1193                                         }
1194                                 } );
1196                                 // apply edit button mouse over effect:
1197                                 $j( '.clip_edit_button' ).hover( function() {
1198                                         $j( this ).removeClass( "clip_edit_base" ).addClass( "clip_edit_over" );
1199                                 }, function() {
1200                                         $j( this ).removeClass( "clip_edit_over" ).addClass( "clip_edit_base" );
1201                                 } ).click( function() {
1202                                         // deselect everything else:
1203                                         $j( '.mv_selected_clip' ).each( function( inx, selected_clip ) {
1204                                                 this_seq.deselectClip( this );
1205                                         } );
1207                                         var sClipObj = this_seq.getClipFromSeqID( $j( this ).parent().attr( 'id' ) );
1208                                         this_seq.plObj.updateCurrentClip( sClipObj  );
1209                                         // get the clip (siblings with mv_clip_thumb class)
1210                                         var cur_clip_elm =  $j( this ).siblings( '.mv_clip_thumb' );
1211                                         // select the clip (add mv_selected_clip if not already selected)
1212                                         if ( ! $j( cur_clip_elm ).hasClass( "mv_selected_clip" ) ) {
1213                                                 $j( cur_clip_elm ).addClass( 'mv_selected_clip' );
1214                                                 $j( '#' + $j( cur_clip_elm ).parent().attr( "id" ) + '_adj' ).fadeIn( "fast" );
1215                                         }
1216                                         // display the edit tab:
1217                                         this_seq.disp( 'clipedit' );
1218                                         // display edit dialog:
1219                                         this_seq.doEditClip( sClipObj );
1220                                 } );
1222                                 // apply onClick edit controls:
1223                                 $j( '.mv_clip_thumb' ).click( function() {
1224                                         var cur_clip_click = this;
1225                                         // if not in multi select mode remove all existing selections
1226                                         // (except for the current click which is handled down below)
1227                                         js_log( ' ks: ' + this_seq.key_shift_down + '  ctrl_down:' + this_seq.key_ctrl_down );
1228                                         if ( ! this_seq.key_shift_down && ! this_seq.key_ctrl_down ) {
1229                                                 $j( '.mv_selected_clip' ).each( function( inx, selected_clip ) {
1230                                                         if ( $j( this ).parent().attr( 'id' ) != $j( cur_clip_click ).parent().attr( 'id' )
1231                                                                 || ( $j( '.mv_selected_clip' ).length > 1 ) ) {
1232                                                                         this_seq.deselectClip( this );
1233                                                         }
1234                                                 } );
1235                                         }
1237                                         // jump to clip time
1238                                         var sClipObj = this_seq.getClipFromSeqID( $j( this ).parent().attr( 'id' ) );
1239                                         this_seq.plObj.updateCurrentClip( sClipObj  );
1240                                         if ( $j( this ).hasClass( "mv_selected_clip" ) ) {
1241                                                 $j( this ).removeClass( "mv_selected_clip" );
1242                                                 $j( '#' + $j( this ).parent().attr( "id" ) + '_adj' ).fadeOut( "fast" );
1243                                         } else {
1244                                                 $j( this ).addClass( 'mv_selected_clip' );
1245                                                 $j( '#' + $j( this ).parent().attr( "id" ) + '_adj' ).fadeIn( "fast" );
1246                                         }
1247                                         // if shift select is down select the in-between clips
1248                                         if ( this_seq.key_shift_down ) {
1249                                                 // get the min max of current selection (within the current track)
1250                                                 var max_order = 0;
1251                                                 var min_order = 999999999;
1252                                                 $j( '.mv_selected_clip' ).each( function() {
1253                                                         var cur_clip = this_seq.getClipFromSeqID( $j( this ).parent().attr( 'id' ) );
1254                                                         // get min max
1255                                                         if ( cur_clip.order < min_order )
1256                                                                 min_order = cur_clip.order;
1257                                                         if ( cur_clip.order > max_order )
1258                                                                 max_order = cur_clip.order;
1259                                                 } );
1260                                                 // select all non-selected between max or min
1261                                                 js_log( 'sOrder: ' + sClipObj.order + ' min:' + min_order + ' max:' + max_order );
1262                                                 if ( sClipObj.order <= min_order ) {
1263                                                         for ( var i = sClipObj.order; i <= max_order; i++ ) {
1264                                                                 $j( '#track_' + track_id + '_clip_' + i + ' > .mv_clip_thumb' ).addClass( 'mv_selected_clip' );
1265                                                         }
1266                                                 }
1267                                                 if ( sClipObj.order >= max_order ) {
1268                                                         for ( var i = min_order; i <= max_order; i++ ) {
1269                                                                 $j( '#track_' + track_id + '_clip_' + i + ' > .mv_clip_thumb' ).addClass( 'mv_selected_clip' );
1270                                                         }
1271                                                 }
1272                                         }
1273                                         this_seq.doEditSelectedClip();
1274                                 } );
1275                                 // add in control for time based display
1276                                 // debugger;
1277                                 if ( this.timeline_mode == 'time' ) {
1278                                         $j( '.ui-resizable-handle' ).mousedown( function() {
1279                                                 js_log( 'hid: ' +  $j( this ).attr( 'class' ) );
1280                                                 this_seq.resize_mode = ( $j( this ).attr( 'class' ).indexOf( 'ui-resizable-e' ) != -1 ) ?
1281                                                                                 'resize_end':'resize_start';
1282                                         } );
1283                                 }
1284                                 var insert_key = 'na';
1285                                 // drag hooks:
1286                                 // @@todo support multiple clips
1287                                 for ( var j in track.clips ) {
1288                                         $j( '#track_' + track_id + '_clip_' + j ).draggable( {
1289                                                 axis:'x',
1290                                                 containment:'#container_track_' + track_id,
1291                                                 opacity:50,
1292                                                 handle: ":not(.clip_control)",
1293                                                 scroll:true,
1294                                                 drag:function( e, ui ) {
1295                                                         // debugger;
1296                                                         insert_key = this_seq.clipDragUpdate( ui, this );
1297                                                 },
1298                                                 start:function( e, ui ) {
1299                                                         js_log( 'start drag:' + this.id );
1300                                                         // make sure we are ontop
1301                                                         $j( this ).css( { top:'0px', zindex:10 } );
1302                                                 },
1303                                                 stop:function( e, ui ) {
1304                                                         $j( this ).css( { top:'0px', zindex:0 } );
1306                                                         var id_parts = this.id.split( '_' );
1307                                                         var track_inx = id_parts[1];
1308                                                         var clip_inx = id_parts[3];
1309                                                         var clips = this_seq.plObj.tracks[track_inx].clips;
1310                                                         var cur_drag_clip = clips[clip_inx];
1312                                                         if ( insert_key != 'na' && insert_key != 'end' ) {
1313                                                                 cur_drag_clip.order = insert_key - .5;
1314                                                         } else if ( insert_key == 'end' ) {
1315                                                                 cur_drag_clip.order = clips.length;
1316                                                         }
1317                                                         // reorder array based on new order
1318                                                         clips.sort( sort_func );
1319                                                         function sort_func( a, b ) {
1320                                                                 return a.order - b.order;
1321                                                         }
1322                                                         // assign keys back to order:
1323                                                         this_seq.plObj.tracks[track_inx].reOrderClips();
1324                                                         // redraw:
1325                                                         this_seq.do_refresh_timeline();
1326                                                 }
1327                                         } );
1328                                         // add in resize hook if in time mode:
1329                                         if ( this.timeline_mode == 'time' ) {
1330                                                 $j( '#track_' + track_id + '_clip_' + j ).resizable( {
1331                                                         minWidth:10,
1332                                                         maxWidth:6000,
1333                                                         start: function( e, ui ) {
1334                                                                 // set border to red
1335                                                                 $j( this ).css( { 'border':'solid thin red' } );
1336                                                                 // fade In Time stats (end or start based on handle)
1337                                                                 // dragging east (adjusting end time)
1338                                                                 js_log( 'append to: ' + this.id );
1339                                                                 $j( '#' + this.id + ' > .mv_clip_stats' ).fadeIn( "fast" );
1340                                                         },
1341                                                         stop: function( e, ui ) {
1342                                                                 js_log( 'stop resize' );
1343                                                                 // restore border
1344                                                                 $j( this ).css( 'border', 'solid thin white' );
1345                                                                 // remove stats
1346                                                                 var clip_drag = this;
1347                                                                 $j( '#' + this.id + ' > .mv_clip_stats' ).fadeOut( "fast", function() {
1348                                                                         var id_parts = clip_drag.id.split( '_' );
1349                                                                         var track_inx = id_parts[1];
1350                                                                         var clip_inx = id_parts[3];
1351                                                                         // update clip
1352                                                                         this_seq.doEdit( {
1353                                                                                 type:this_seq.resize_mode,
1354                                                                                 delta:this_seq.edit_delta,
1355                                                                                 track_inx:track_inx,
1356                                                                                 clip_inx:clip_inx } )
1357                                                                         } );
1358                                                         },
1359                                                         resize: function( e, ui ) {
1360                                                                 // update time stats & render images:
1361                                                                 this_seq.update_clip_resize( this );
1362                                                         }
1363                                                 } );
1364                                         }
1365                                 }
1366                                 $j( '#container_track_' + track_id ).width( Math.round( this.timeline_duration / this.timeline_scale ) );
1367                         }
1368                         // debugger;
1369                 }
1370         },
1371         clipDragUpdate:function( ui, clipElm ) {
1372                 var this_seq = this;
1374                 var insert_key = 'na';
1375                 // animate re-arrange by left position:
1376                 // js_log('left: '+ui.position.left);
1377                 // locate clip (based on clip duration not animate)
1378                 var id_parts = clipElm.id.split( '_' );
1379                 var track_inx = id_parts[1];
1380                 var clip_inx = id_parts[3];
1381                 var clips = this_seq.plObj.tracks[track_inx].clips;
1382                 var cur_drag_clip = clips[clip_inx];
1383                 var return_org = true;
1384                 $j( clipElm ).css( 'zindex', 10 );
1385                 // find out where we are inserting and set left border to solid red thick
1386                 for ( var k in clips ) {
1387                         if (    ui.position.left > clips[k].left_px &&
1388                                 ui.position.left < ( clips[k].left_px + clips[k].width_px ) ) {
1389                                 if ( clip_inx != k ) {
1390                                         // also make sure we are not where we started
1391                                         if ( k - 1 != clip_inx ) {
1392                                                 $j( '#track_' + track_inx + '_clip_' + k ).css( 'border-left', 'solid thick red' );
1393                                                 insert_key = k;
1394                                         } else {
1395                                                 insert_key = 'na';
1396                                         }
1397                                 } else {
1398                                         insert_key = 'na';
1399                                 }
1400                         } else {
1401                                 $j( '#track_' + track_inx + '_clip_' + k ).css( 'border-left', 'solid thin white' );
1402                         }
1403                 }
1404                 // if greater than the last k insert after
1405                 if ( ui.position.left > ( clips[k].left_px + clips[k].width_px ) &&
1406                         k != clip_inx ) {
1407                                 $j( '#track_' + track_inx + '_clip_' + k ).css( 'border-right', 'solid thick red' );
1408                                 insert_key = 'end';
1409                 } else {
1410                         $j( '#track_' + track_inx + '_clip_' + k ).css( 'border-right', 'solid thin white' );
1411                 }
1412                 return insert_key;
1413         },
1414         deselectClip:function( clipElm ) {
1415                 if ( !clipElm ) {
1416                         $j( '.mv_selected_clip' ).removeClass( "mv_selected_clip" );
1417                 } else {
1418                         $j( clipElm ).removeClass( "mv_selected_clip" );
1419                         // make sure the transition sibling is removed:
1420                         $j( clipElm ).siblings( '.clip_trans_box' ).removeClass( 'mv_selected_transition' );
1421                         $j( '#' + $j( clipElm ).parent().attr( "id" ) + '_adj' ).fadeOut( "fast" );
1422                 }
1423         },
1424         getClipFromSeqID:function( clip_seq_id ) {
1425                 js_log( 'get id from: ' + clip_seq_id );
1426                 var ct = clip_seq_id.replace( 'track_', '' ).replace( 'clip_', '' ).split( '_' );
1427                 return this.plObj.tracks[ ct[0] ].clips[ ct[1] ];
1428         },
1429         // renders clip frames
1430         render_clip_frames:function( clip, frame_offset_count ) {
1431                 js_log( 'f:render_clip_frames: ' + clip.id + ' foc:' + frame_offset_count );
1432                 var clip_frames_html = '';
1433                 var frame_width = Math.round( this.track_thumb_height * 1.3333333 );
1435                 var pint = ( frame_offset_count == null ) ? 0:frame_offset_count * frame_width;
1437                 // js_log("pinit: "+ pint+ ' < '+clip.width_px+' ++'+frame_width);
1438                 for ( var p = pint; p < clip.width_px; p += frame_width ) {
1439                         var clip_time = ( p == 0 ) ? 0:Math.round( p * this.timeline_scale );
1440                         js_log( 'rendering clip frames: p:' + p + ' pts:' + ( p * this.timeline_scale ) + ' time:' + clip_time + ' height:' + this.track_thumb_height );
1441                         clip_frames_html += clip.embed.renderTimelineThumbnail( {
1442                                 'width':  frame_width,
1443                                 'thumb_class':'mv_tl_thumb',
1444                                 'height': this.track_thumb_height,
1445                                 'size' : "icon", // set size to "icon" preset
1446                                 'time':   clip_time
1447                         } );
1448                 }
1449                 js_log( 'render_clip_frames:' + clip_frames_html );
1450                 return clip_frames_html;
1451         },
1452         update_clip_resize:function( clip_element ) {
1453                 // js_log('update_clip_resize');
1454                 var this_seq = this;
1455                 var id_parts = clip_element.id.split( '_' );
1456                 track_inx = id_parts[1];
1457                 clip_inx = id_parts[3];
1458                 // set clip:
1459                 var clip = this.plObj.tracks[ track_inx ].clips[ clip_inx ];
1460                 var clip_desc = '';
1461                 // would be nice if getting the width did not flicker the border
1462                 // @@todo do a work around e in resize function has some screen based offset values
1463                 clip.width_px = $j( clip_element ).width();
1464                 var width_dif = clip.width_px - Math.round( Math.round( clip.getDuration() ) / this.timeline_scale );
1465                 // var left_px = $j(clip_element).css('left');
1467                 var new_clip_dur = Math.round( clip.width_px * this.timeline_scale );
1468                 var clip_dif = ( new_clip_dur - clip.getDuration() );
1469                 var clip_dif_str = ( clip_dif > 0 ) ? '+' + clip_dif:clip_dif;
1470                 // set the edit global delta
1471                 this.edit_delta = clip_dif;
1473                 // get new length:
1474                 clip_desc += 'length: ' + seconds2npt( new_clip_dur ) + '(' + clip_dif_str + ')';
1475                 if ( this_seq.resize_mode == 'resize_end' ) {
1476                         // expanding right
1477                         var new_end = seconds2npt( npt2seconds( clip.embed.end_ntp ) + clip_dif );
1478                         clip_desc += '<br>end time: ' + new_end;
1479                         // also shift all the other clips (after the current)
1480                         // js_log("track_inx: " + track_inx + ' clip inx:'+clip_inx);
1481                         // $j('#container_track_'+track_inx+' > .mv_clip_drag :gt('+clip_inx+')').each(function(){
1482                         $j( '#container_track_' + track_inx + ' > :gt(' + clip_inx + ')' ).each( function() {
1483                                 var move_id_parts = this.id.split( '_' );
1484                                 var move_clip = this_seq.plObj.tracks[move_id_parts[1]].clips[move_id_parts[3]];
1485                                 // js_log('should move:'+ this.id);
1486                                 $j( this ).css( 'left', move_clip.left_px + width_dif );
1487                         } );
1488                 } else {
1489                         // expanding left (resize_start)
1490                         var new_start = seconds2npt( npt2seconds( clip.embed.start_ntp ) + clip_dif );
1491                         clip_desc += '<br>start time: ' + new_start;
1492                 }
1494                 // update clip stats:
1495                 $j( '#' + clip_element.id + ' > .mv_clip_stats' ).html( clip_desc );
1496                 var frame_width = Math.round( this.track_thumb_height * 1.3333333 );
1497                 // check if we need to append some images:
1498                 var frame_count = $j( '#' + clip_element.id + ' > img' ).length;
1499                 if ( clip.width_px > ( frame_count *  frame_width ) ) {
1500                         // if dragging left append
1501                         js_log( 'width_px:' + clip.width_px + ' framecount:' + frame_count + ' Xcw=' + ( frame_count *  frame_width ) );
1502                         $j( '#' + clip_element.id ).append( this.render_clip_frames( clip, frame_count ) );
1503                 }
1504         },
1505         // renders cnt_time
1506         render_playheadhead_seeker:function() {
1507                 js_log( 'render_playheadhead_seeker' );
1508                 // render out time stamps and time "jump" links
1509                 // first get total width
1511                 // remove the old one if its still there
1512                 $j( '#' + this.timeline_id + '_pl_control' ).remove();
1513                 // render out a playlist clip wide and all the way to the right (only playhead and play button) (outside of timeline)
1514                 $j( this.target_sequence_container ).append( '<div id="' + this.timeline_id + '_pl_control"' +
1515                         ' style="position:absolute;top:' + ( this.plObj.height ) + 'px;' +
1516                         'right:1px;width:' + this.plObj.width + 'px;height:' + this.plObj.org_control_height + '" ' +
1517                         'class="' + this.plObj.ctrlBuilder.pClass + '"><div class="ui-widget ui-corner-bottom ui-state-default control-bar">' +
1518                                          this.plObj.getControlsHTML() +
1519                                  '</div>' +
1520                         '</div>' );
1521                 // update time and render out clip dividers .. should be used to show load progress
1522                 this.plObj.updateBaseStatus();
1524                 // once the controls are in the DOM add hooks:
1525                 this.plObj.ctrlBuilder.addControlHooks( $j( '#' + this.timeline_id + '_pl_control' ) );
1527                 // render out the "jump" div
1528                 if ( this.timeline_mode == 'time' ) {
1529                         /*$j('#'+this.timeline_id+'_head_jump').width(pixle_length);
1530                         //output times every 50pixles
1531                         var out='';
1532                         //output time-desc every 50pixles and jump links every 10 pixles
1533                         var n=0;
1534                         for(i=0;i<pixle_length;i+=10){
1535                                 out+='<div onclick="'+this.instance_name+'.jt('+i*this.timeline_scale+');"' +
1536                                                 ' style="z-index:2;position:absolute;left:'+i+'px;width:10px;height:20px;top:0px;"></div>';
1537                                 if(n==0)
1538                                         out+='<span style="position:absolute;left:'+i+'px;">|'+seconds2npt(Math.round(i*this.timeline_scale))+'</span>';
1539                                 n++;
1540                                 if(n==10)n=0;
1541                         }*/
1543                 }
1544         },
1545         jt:function( jh_time ) {
1546                 js_log( 'jt:' + jh_time );
1547                 var this_seq = this;
1548                 this.playline_time = jh_time;
1549                 js_log( 'time: ' + seconds2npt( jh_time ) + ' ' + Math.round( jh_time / this.timeline_scale ) );
1550                 // render playline at given time
1551                 $j( '#' + this.timeline_id + '_playline' ).css( 'left', Math.round( jh_time / this.timeline_scale ) + 'px' );
1552                 cur_pl_time = 0;
1553                 // update the thumb with the requested time:
1554                 this.plObj.updateThumbTime( jh_time );
1555         },
1556         // adjusts the current scale
1557         zoom_in:function() {
1558                 this.timeline_scale = this.timeline_scale * .75;
1559                 this.do_refresh_timeline();
1560                 js_log( 'zoomed in:' + this.timeline_scale );
1561         },
1562         zoom_out:function() {
1563                 this.timeline_scale = this.timeline_scale * ( 1 + ( 1 / 3 ) );
1564                 this.do_refresh_timeline();
1565                 js_log( 'zoom out: ' + this.timeline_scale );
1566         },
1567         do_refresh_timeline:function( preserve_selection ) {
1568                 js_log( 'Sequencer:do_refresh_timeline()' );
1569                 // @@todo should "lock" interface while refreshing timeline
1570                 var pSelClips = [];
1571                 if ( preserve_selection ) {
1572                         $j( '.mv_selected_clip' ).each( function() {
1573                                 pSelClips.push( $j( this ).parent().attr( 'id' ) );
1574                         } );
1575                 }
1576                 // regen duration
1577                 this.plObj.getDuration( true );
1578                 // refresh player:
1579                 this.plObj.getHTML();
1581                 // this.render_playheadhead_seeker();
1582                 this.render_tracks();
1583                 this.jt( this.playline_time );
1585                 if ( preserve_selection ) {
1586                         for ( var i = 0; i < pSelClips.length; i++ ) {
1587                                 $j( '#' + pSelClips[i] + ' .mv_clip_thumb' ).addClass( 'mv_selected_clip' );
1588                         }
1589                 }
1590         }
1593 /* extension to mvPlayList to support sequencer features properties */
1594 var mvSeqPlayList = function( element ) {
1595         return this.init( element );
1597 mvSeqPlayList.prototype = {
1598         init:function( element ) {
1599                 var myPlObj = new mvPlayList( element );
1601                 // inherit mvClip
1602                 for ( var method in myPlObj ) {
1603                         if ( typeof this[method] != 'undefined' ) {
1604                                 this[ 'parent_' + method ] = myPlObj[method];
1605                         } else {
1606                                 this[method] = myPlObj[method];
1607                         }
1608                 }
1610                 this.org_control_height = this.pl_layout.control_height;
1611                 // do specific mods:(controls and title are managed by the sequencer)
1612                 this.pl_layout.title_bar_height = 0;
1613                 this.pl_layout.control_height = 0;
1614         },
1615         setSliderValue:function( perc ) {
1616                 js_log( 'setSliderValue::' + perc );
1617                 // get the track_clipThumb_height from parent mvSequencer
1618                 var frame_width = Math.round( this.pSeq.track_clipThumb_height * 1.3333333 );
1619                 var container_width = frame_width + 60;
1621                 var perc_clip = this.cur_clip.embed.currentTime / this.cur_clip.getDuration();
1623                 var left_px = parseInt( ( this.cur_clip.order * container_width ) + ( frame_width * perc_clip ) ) + 'px';
1624                 js_log( "set " + perc + ' of cur_clip: ' + this.cur_clip.order + ' lp:' + left_px );
1627                 // update the timeline playhead and
1628                 $j( '#' + this.seqObj.timeline_id + '_playline' ).css( 'left', left_px );
1630                 // pass update request to parent:
1631                 this.parent_setSliderValue( perc );
1632         },
1633         getControlsHTML:function() {
1634                 // get controls from current clip add some playlist specific controls:
1635                 this.cur_clip.embed.supports['prev_next'] = true;
1636                 this.cur_clip.embed.supports['options']   = false;
1637                 return ctrlBuilder.getControls( this.cur_clip.embed );
1638         },
1639         // override renderDisplay
1640         renderDisplay:function() {
1641                 js_log( 'mvSequence:renderDisplay' );
1642                 // setup layout for title and dc_ clip container
1643                 $j( this ).html( '<div id="dc_' + this.id + '" style="width:' + this.width + 'px;' +
1644                                 'height:' + ( this.height ) + 'px;position:relative;" />' );
1646                 this.setupClipDisplay();
1647         }