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