OpenShot Video Editor  2.0.0
clip.js
Go to the documentation of this file.
1 /**
2  * @file
3  * @brief Clip directives (draggable & resizable functionality)
4  * @author Jonathan Thomas <jonathan@openshot.org>
5  * @author Cody Parker <cody@yourcodepro.com>
6  *
7  * @section LICENSE
8  *
9  * Copyright (c) 2008-2014 OpenShot Studios, LLC
10  * <http://www.openshotstudios.com/>. This file is part of
11  * OpenShot Video Editor, an open-source project dedicated to
12  * delivering high quality video editing and animation solutions to the
13  * world. For more information visit <http://www.openshot.org/>.
14  *
15  * OpenShot Video Editor is free software: you can redistribute it
16  * and/or modify it under the terms of the GNU General Public License
17  * as published by the Free Software Foundation, either version 3 of the
18  * License, or (at your option) any later version.
19  *
20  * OpenShot Video Editor is distributed in the hope that it will be
21  * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
27  */
28 
29 
30 // Init variables
31 var dragging = false;
32 var resize_disabled = false;
34 var start_clips = {};
35 var move_clips = {};
36 var out_of_bounds = false;
38 
39 // Treats element as a clip
40 // 1: can be dragged
41 // 2: can be resized
42 // 3: class change when hovered over
43 var dragLog = null;
44 
45 App.directive('tlClip', function($timeout){
46  return {
47  scope: "@",
48  link: function(scope, element, attrs){
49 
50  //handle resizability of clip
51  element.resizable({
52  handles: "e, w",
53  minWidth: 1,
54  maxWidth: scope.clip.length * scope.pixelsPerSecond,
55  start: function(e, ui) {
56  dragging = true;
57 
58  //determine which side is being changed
59  var parentOffset = element.offset();
60  var mouseLoc = e.pageX - parentOffset.left;
61  if (mouseLoc < 5) {
62  dragLoc = 'left';
63  } else {
64  dragLoc = 'right';
65  }
66 
67  // Does this bounding box overlap a locked track?
68  if (hasLockedTrack(scope, e.pageY, e.pageY))
69  return !event; // yes, do nothing
70 
71  // Does this bounding box overlap a locked track?
72  var vert_scroll_offset = $("#scrolling_tracks").scrollTop();
73  var track_top = (parseInt(element.position().top) + parseInt(vert_scroll_offset));
74  var track_bottom = (parseInt(element.position().top) + parseInt(element.height()) + parseInt(vert_scroll_offset));
75  if (hasLockedTrack(scope, track_top, track_bottom))
76  resize_disabled = true;
77 
78  // Hide keyframe points
79  element.find('.point_icon').fadeOut('fast');
80  element.find('.audio-container').fadeOut('fast');
81 
82  },
83  stop: function(e, ui) {
84  dragging = false;
85 
86  if (resize_disabled) {
87  // disabled, do nothing
88  resize_disabled = false;
89  return;
90  }
91 
92  // Hide keyframe points
93  if (dragLoc == 'right') {
94  // Make the keyframe points visible again
95  element.find('.point_icon').show();
96  element.find('.audio-container').show();
97  }
98 
99  //get amount changed in width
100  var delta_x = ui.originalSize.width - ui.size.width;
101  var delta_time = delta_x/scope.pixelsPerSecond;
102 
103  //change the clip end/start based on which side was dragged
104  new_position = scope.clip.position;
105  new_left = scope.clip.start;
106  new_right = scope.clip.end;
107 
108  if (dragLoc == 'left'){
109  // changing the start of the clip
110  new_left += delta_time;
111  if (new_left < 0) {
112  // prevent less than zero
113  new_left = 0.0;
114  new_position -= scope.clip.start
115  } else {
116  new_position += delta_time
117  }
118  } else {
119  // changing the end of the clips
120  new_right -= delta_time;
121  if (new_right > scope.clip.duration)
122  // prevent greater than duration
123  new_right = scope.clip.duration;
124  }
125 
126  //apply the new start, end and length to the clip's scope
127  scope.$apply(function(){
128 
129  if (scope.clip.end != new_right){
130  scope.clip.end = new_right;
131  }
132  if (scope.clip.start != new_left){
133  scope.clip.start = new_left;
134  scope.clip.position = new_position;
135  }
136 
137  // Resize timeline if it's too small to contain all clips
138  scope.ResizeTimeline();
139 
140  // update clip in Qt (very important =)
141  if (scope.Qt)
142  timeline.update_clip_data(JSON.stringify(scope.clip));
143 
144  });
145 
146  //resize the audio canvas to match the new clip width
147  if (scope.clip.show_audio){
148  element.find(".audio-container").show();
149  //redraw audio as the resize cleared the canvas
150  drawAudio(scope, scope.clip.id);
151  }
152 
153  dragLoc = null;
154 
155  },
156  resize: function(e, ui) {
157 
158  if (resize_disabled) {
159  // disabled, keep the item the same size
160  $(this).css(ui.originalPosition);
161  $(this).width(ui.originalSize.width);
162  return;
163  }
164 
165  // get amount changed in width
166  var delta_x = parseFloat(ui.originalSize.width) - ui.size.width;
167  var delta_time = delta_x / scope.pixelsPerSecond;
168 
169  // change the clip end/start based on which side was dragged
170  new_left = scope.clip.start;
171  new_right = scope.clip.end;
172 
173  if (dragLoc == 'left'){
174  // changing the start of the clip
175  new_left += delta_time;
176  if (new_left < 0) {
177  ui.element.width(ui.size.width + (new_left * scope.pixelsPerSecond));
178  ui.element.css("left", ui.position.left - (new_left * scope.pixelsPerSecond));
179  } else {
180  ui.element.width(ui.size.width);
181  }
182  } else {
183  // changing the end of the clips
184  new_right -= delta_time;
185  if (new_right > scope.clip.duration) {
186  new_right = scope.clip.duration - new_right; // difference from duration
187  ui.element.width(ui.size.width + (new_right * scope.pixelsPerSecond));
188 
189  // change back to actual duration (for the preview below)
190  new_right = scope.clip.duration;
191  } else {
192  ui.element.width(ui.size.width);
193  }
194  }
195 
196 
197  // Preview frame during resize
198  if (dragLoc == 'left'){
199  // Preview the left side of the clip
200  scope.PreviewClipFrame(scope.clip.id, new_left);
201  } else {
202  // Preview the right side of the clip
203  scope.PreviewClipFrame(scope.clip.id, new_right);
204  }
205 
206  },
207 
208  });
209 
210  //handle hover over on the clip
211  element.hover(
212  function () {
213  if (!dragging)
214  {
215  element.addClass( "highlight_clip", 200, "easeInOutCubic" );
216  }
217  },
218  function () {
219  if (!dragging)
220  {
221  element.removeClass( "highlight_clip", 200, "easeInOutCubic" );
222  }
223  }
224  );
225 
226  //handle draggability of clip
227  element.draggable({
228  snap: ".track", // snaps to a track
229  snapMode: "inner",
230  snapTolerance: 20,
231  scroll: true,
232  cancel: '.effect-container',
233  start: function(event, ui) {
234  previous_drag_position = null;
235  dragging = true;
236  if (!element.hasClass('ui-selected'))
237  {
238  // Clear previous selections?
239  var clear_selections = false;
240  if ($(".ui-selected").length > 0)
241  clear_selections = true;
242 
243  // SelectClip, SelectTransition
244  var id = $(this).attr("id");
245  if (element.hasClass('clip')) {
246  // Select this clip, unselect all others
247  scope.SelectTransition("", clear_selections);
248  scope.SelectClip(id, clear_selections);
249 
250  } else if (element.hasClass('transition')) {
251  // Select this transition, unselect all others
252  scope.SelectClip("", clear_selections);
253  scope.SelectTransition(id, clear_selections);
254  }
255  }
256 
257  // Apply scope up to this point
258  scope.$apply(function(){});
259 
260  var vert_scroll_offset = $("#scrolling_tracks").scrollTop();
261  var horz_scroll_offset = $("#scrolling_tracks").scrollLeft();
263 
264  bounding_box = {};
265 
266  // Init all other selected clips (prepare to drag them)
267  $(".ui-selected").each(function(){
268  start_clips[$(this).attr('id')] = {"top": $(this).position().top + vert_scroll_offset,
269  "left": $(this).position().left + horz_scroll_offset};
270  move_clips[$(this).attr('id')] = {"top": $(this).position().top + vert_scroll_offset,
271  "left": $(this).position().left + horz_scroll_offset};
272 
273  //send clip to bounding box builder
274  setBoundingBox($(this));
275  });
276 
277  // Does this bounding box overlap a locked track?
278  if (hasLockedTrack(scope, bounding_box.top, bounding_box.bottom))
279  return !event; // yes, do nothing
280 
281  },
282  stop: function(event, ui) {
283 
284  // Ignore clip-menu click
285  $( event.toElement ).one('.clip_menu', function(e){ e.stopImmediatePropagation(); } );
286 
287  // Hide snapline (if any)
288  scope.HideSnapline();
289 
290  // Clear previous drag position
291  previous_drag_position = null;
292  dragging = false;
293 
294  },
295  drag: function(e, ui) {
296  var previous_x = ui.originalPosition.left;
297  var previous_y = ui.originalPosition.top;
298  if (previous_drag_position != null)
299  {
300  // if available, override with previous drag position
301  previous_x = previous_drag_position.left;
302  previous_y = previous_drag_position.top;
303  }
304 
305  // set previous position (for next time around)
306  previous_drag_position = ui.position;
307 
308  // Calculate amount to move clips
309  var x_offset = ui.position.left - previous_x;
310  var y_offset = ui.position.top - previous_y;
311 
312  // Move the bounding box and apply snapping rules
313  results = moveBoundingBox(scope, previous_x, previous_y, x_offset, y_offset, ui.position.left, ui.position.top);
314  x_offset = results.x_offset;
315  y_offset = results.y_offset;
316 
317  // Update ui object
318  ui.position.left = results.position.left;
319  ui.position.top = results.position.top;
320 
321  // Move all other selected clips with this one
322  $(".ui-selected").each(function(){
323  var newY = move_clips[$(this).attr('id')]["top"] + y_offset;
324  var newX = move_clips[$(this).attr('id')]["left"] + x_offset;
325 
326  //update the clip location in the array
327  move_clips[$(this).attr('id')]['top'] = newY;
328  move_clips[$(this).attr('id')]['left'] = newX;
329 
330  //change the element location
331  $(this).css('left', newX);
332  $(this).css('top', newY);
333 
334  });
335 
336  },
337  revert: function(valid) {
338  if(!valid) {
339  //the drop spot was invalid, so we're going to move all clips to their original position
340  $(".ui-selected").each(function(){
341  var oldY = start_clips[$(this).attr('id')]['top'];
342  var oldX = start_clips[$(this).attr('id')]['left'];
343 
344  $(this).css('left', oldX);
345  $(this).css('top', oldY);
346  });
347  }
348  }
349  });
350 
351 
352  }
353  };
354 });
355 
356 // Handle clip effects
357 App.directive('tlClipEffects', function(){
358  return{
359  link: function(scope, element, attrs){
360 
361  }
362  };
363 });
364 
365 // Handle multiple selections
366 App.directive('tlMultiSelectable', function(){
367  return {
368  link: function(scope, element, attrs){
369  element.selectable({
370  filter: '.droppable',
371  distance: 0,
372  cancel: '.effect-container',
373  selected: function( event, ui ) {
374 
375  // Identify the selected ID and TYPE
376  var id = ui.selected.id;
377  var type = "";
378  var item = null;
379 
380  if (id.match("^clip_")) {
381  id = id.replace("clip_", "");
382  type = "clip";
383  item = findElement(scope.project.clips, "id", id);
384  } else if (id.match("^transition_")) {
385  id = id.replace("transition_", "");
386  type = "transition";
387  item = findElement(scope.project.effects, "id", id);
388  }
389 
390  if (scope.Qt)
391  {
392  timeline.qt_log("Add to selection: " + id);
393  timeline.addSelection(id, type, false);
394 
395  // Clear effect selections (if any)
396  timeline.addSelection("", "effect", true);
397  }
398 
399  scope.$apply(function(){
400  item.selected = true;
401  });
402 
403  },
404  unselected: function( event, ui ) {
405 
406  // Identify the selected ID and TYPE
407  var id = ui.unselected.id;
408  var type = "";
409  var item = null;
410 
411  if (id.match("^clip_")) {
412  id = id.replace("clip_", "");
413  type = "clip";
414  item = findElement(scope.project.clips, "id", id);
415  } else if (id.match("^transition_")) {
416  id = id.replace("transition_", "");
417  type = "transition";
418  item = findElement(scope.project.effects, "id", id);
419  }
420 
421  if (scope.Qt)
422  {
423  timeline.qt_log("Remove from selection: " + id);
424  timeline.removeSelection(id, type);
425  }
426  scope.$apply(function(){
427  item.selected = false;
428  });
429 
430  }
431  });
432  }
433  };
434 });
435 
436 
437 
438 
439 
jQuery fx start
Definition: jquery.js:9518
var dragLog
Definition: clip.js:43
function getTrackContainerHeight()
Definition: functions.js:41
if(window.getComputedStyle)
Definition: jquery.js:7083
function findElement(arr, propName, propValue)
Definition: functions.js:31
var move_clips
Definition: clip.js:35
var bounding_box
Definition: functions.js:179
function moveBoundingBox(scope, previous_x, previous_y, x_offset, y_offset, left, top)
Definition: functions.js:214
jQuery fx stop
Definition: jquery.js:9524
var previous_drag_position
Definition: clip.js:33
var start_clips
Definition: clip.js:34
var App
Definition: app.js:31
function setBoundingBox(item)
Definition: functions.js:182
var a[b] e
Tween propHooks scrollTop
Definition: jquery.js:9274
var dragging
Definition: clip.js:31
function drawAudio(scope, clip_id)
Definition: functions.js:52
jQuery each(["height","width"], function(i, name){jQuery.cssHooks[name]={get:function(elem, computed, extra){if(computed){return elem.offsetWidth===0 &&rdisplayswap.test(jQuery.css(elem,"display"))?jQuery.swap(elem, cssShow, function(){return getWidthOrHeight(elem, name, extra);}):getWidthOrHeight(elem, name, extra);}}, set:function(elem, value, extra){var styles=extra &&getStyles(elem);return setPositiveNumber(elem, value, extra?augmentWidthOrHeight(elem, name, extra, jQuery.support.boxSizing &&jQuery.css(elem,"boxSizing", false, styles)==="border-box", styles):0);}};})
function hasLockedTrack(scope, top, bottom)
Definition: functions.js:161
var out_of_bounds
Definition: clip.js:36
var resize_disabled
Definition: clip.js:32
var track_container_height
Definition: clip.js:37