1 /* Copyright 2009-2010 Yorba Foundation
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later). See the COPYING file in this distribution.
9 public class GapView : Gtk.DrawingArea {
13 public GapView(int64 start, int64 length, int width, int height) {
15 gap = new Model.Gap(start, start + length);
17 Gdk.Color.parse("#777", out fill_color);
19 set_flags(Gtk.WidgetFlags.NO_WINDOW);
21 set_size_request(width, height);
24 public signal void removed(GapView gap_view);
25 public signal void unselected(GapView gap_view);
27 public void remove() {
31 public void unselect() {
35 public override bool expose_event(Gdk.EventExpose e) {
36 draw_rounded_rectangle(window, fill_color, true, allocation.x, allocation.y,
37 allocation.width - 1, allocation.height - 1);
42 public class ClipView : Gtk.DrawingArea {
50 public Model.Clip clip;
51 public int64 initial_time;
52 weak Model.TimeSystem time_provider;
53 public bool is_selected;
54 public int height; // TODO: We request size of height, but we aren't allocated this height.
55 // We should be using the allocated height, not the requested height.
56 public static Gtk.Menu context_menu;
57 TransportDelegate transport_delegate;
58 Gdk.Color color_black;
59 Gdk.Color color_normal;
60 Gdk.Color color_selected;
64 MotionMode motion_mode = MotionMode.NONE;
65 bool button_down = false;
66 bool pending_selection;
67 const int MIN_DRAG = 5;
68 const int TRIM_WIDTH = 10;
69 public const int SNAP_DELTA = 10;
71 static Gdk.Cursor left_trim_cursor = new Gdk.Cursor(Gdk.CursorType.LEFT_SIDE);
72 static Gdk.Cursor right_trim_cursor = new Gdk.Cursor(Gdk.CursorType.RIGHT_SIDE);
73 static Gdk.Cursor hand_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-none");
74 // will be used for drag
75 static Gdk.Cursor plus_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-copy");
77 public signal void clip_deleted(Model.Clip clip);
78 public signal void clip_moved(ClipView clip);
79 public signal void selection_request(ClipView clip_view, bool extend_selection);
80 public signal void move_request(ClipView clip_view, int64 delta);
81 public signal void move_commit(ClipView clip_view, int64 delta);
82 public signal void move_begin(ClipView clip_view, bool copy);
83 public signal void trim_begin(ClipView clip_view, Gdk.WindowEdge edge);
84 public signal void trim_commit(ClipView clip_view, Gdk.WindowEdge edge);
86 public ClipView(TransportDelegate transport_delegate, Model.Clip clip,
87 Model.TimeSystem time_provider, int height) {
88 this.transport_delegate = transport_delegate;
90 this.time_provider = time_provider;
94 clip.moved.connect(on_clip_moved);
95 clip.updated.connect(on_clip_updated);
97 Gdk.Color.parse("000", out color_black);
100 set_flags(Gtk.WidgetFlags.NO_WINDOW);
105 void get_clip_colors() {
106 if (clip.clipfile.is_online()) {
107 Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#d82" : "#84a",
109 Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#da5" : "#b9d",
112 Gdk.Color.parse("red", out color_selected);
113 Gdk.Color.parse("#AA0000", out color_normal);
117 void on_clip_updated() {
118 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_updated");
123 // Note that a view's size may vary slightly (by a single pixel) depending on its
124 // starting position. This is because the clip's length may not be an integer number of
125 // pixels, and may get rounded either up or down depending on the clip position.
126 public void adjust_size(int height) {
127 int width = time_provider.time_to_xpos(clip.start + clip.duration) -
128 time_provider.time_to_xpos(clip.start);
129 set_size_request(width + 1, height);
132 public void on_clip_moved(Model.Clip clip) {
133 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved");
138 public void delete_clip() {
143 weak Gdk.Color fill = is_selected ? color_selected : color_normal;
145 bool left_trimmed = clip.media_start != 0 && !clip.is_recording;
147 bool right_trimmed = clip.clipfile.is_online() ?
148 (clip.media_start + clip.duration != clip.clipfile.length) : false;
150 if (!left_trimmed && !right_trimmed) {
151 draw_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
152 allocation.width - 2, allocation.height - 2);
153 draw_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
154 allocation.width - 1, allocation.height - 1);
156 } else if (!left_trimmed && right_trimmed) {
157 draw_left_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
158 allocation.width - 2, allocation.height - 2);
159 draw_left_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
160 allocation.width - 1, allocation.height - 1);
162 } else if (left_trimmed && !right_trimmed) {
163 draw_right_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
164 allocation.width - 2, allocation.height - 2);
165 draw_right_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
166 allocation.width - 1, allocation.height - 1);
169 draw_square_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
170 allocation.width - 2, allocation.height - 2);
171 draw_square_rectangle(window, color_black, false, allocation.x, allocation.y,
172 allocation.width - 1, allocation.height - 1);
175 Gdk.GC gc = new Gdk.GC(window);
176 Gdk.Rectangle r = { 0, 0, 0, 0 };
178 // Due to a Vala compiler bug, we have to do this initialization here...
181 r.width = allocation.width;
182 r.height = allocation.height;
184 gc.set_clip_rectangle(r);
187 if (clip.is_recording) {
188 layout = create_pango_layout("Recording");
189 } else if (!clip.clipfile.is_online()) {
190 layout = create_pango_layout("%s (Offline)".printf(clip.name));
193 layout = create_pango_layout("%s".printf(clip.name));
196 layout.get_pixel_size(out width, out height);
197 Gdk.draw_layout(window, gc, allocation.x + 10, allocation.y + height, layout);
200 public override bool expose_event(Gdk.EventExpose event) {
205 public override bool button_press_event(Gdk.EventButton event) {
206 if (!transport_delegate.is_stopped()) {
210 event.x -= allocation.x;
211 bool primary_press = event.button == 1;
214 drag_point = (int)event.x;
219 bool extend_selection = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0;
220 // The clip is not responsible for changing the selection state.
221 // It may depend upon knowledge of multiple clips. Let anyone who is interested
223 if (is_left_trim(event.x, event.y)) {
224 selection_request(this, false);
226 trim_begin(this, Gdk.WindowEdge.WEST);
227 motion_mode = MotionMode.LEFT_TRIM;
229 } else if (is_right_trim(event.x, event.y)){
230 selection_request(this, false);
232 trim_begin(this, Gdk.WindowEdge.EAST);
233 motion_mode = MotionMode.RIGHT_TRIM;
237 pending_selection = false;
238 selection_request(this, extend_selection);
240 pending_selection = true;
244 if (event.button == 3) {
245 context_menu.select_first(true);
246 context_menu.popup(null, null, null, event.button, event.time);
248 context_menu.popdown();
254 public override bool button_release_event(Gdk.EventButton event) {
255 if (!transport_delegate.is_stopped()) {
259 event.x -= allocation.x;
261 if (event.button == 1) {
262 switch (motion_mode) {
263 case MotionMode.NONE: {
264 if (pending_selection) {
265 selection_request(this, true);
269 case MotionMode.DRAGGING: {
270 int64 delta = time_provider.xsize_to_time((int) event.x - drag_point);
271 if (motion_mode == MotionMode.DRAGGING) {
272 move_commit(this, delta);
276 case MotionMode.LEFT_TRIM:
277 trim_commit(this, Gdk.WindowEdge.WEST);
279 case MotionMode.RIGHT_TRIM:
280 trim_commit(this, Gdk.WindowEdge.EAST);
284 motion_mode = MotionMode.NONE;
288 public override bool motion_notify_event(Gdk.EventMotion event) {
289 if (!transport_delegate.is_stopped()) {
293 event.x -= allocation.x;
294 int delta_pixels = (int)(event.x - drag_point) - snap_amount;
296 snap_amount += delta_pixels;
297 if (snap_amount.abs() < SNAP_DELTA) {
300 delta_pixels += snap_amount;
305 int64 delta_time = time_provider.xsize_to_time(delta_pixels);
307 switch (motion_mode) {
308 case MotionMode.NONE:
309 if (!button_down && is_left_trim(event.x, event.y)) {
310 window.set_cursor(left_trim_cursor);
311 } else if (!button_down && is_right_trim(event.x, event.y)) {
312 window.set_cursor(right_trim_cursor);
313 } else if (is_selected && button_down) {
314 if (delta_pixels.abs() > MIN_DRAG) {
315 bool do_copy = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0;
317 window.set_cursor(plus_cursor);
319 window.set_cursor(hand_cursor);
321 motion_mode = MotionMode.DRAGGING;
322 move_begin(this, do_copy);
325 window.set_cursor(null);
328 case MotionMode.RIGHT_TRIM:
329 case MotionMode.LEFT_TRIM:
331 if (motion_mode == MotionMode.LEFT_TRIM) {
332 clip.trim(delta_time, Gdk.WindowEdge.WEST);
334 int64 duration = clip.duration;
335 clip.trim(delta_time, Gdk.WindowEdge.EAST);
336 if (duration != clip.duration) {
337 drag_point += (int)delta_pixels;
342 case MotionMode.DRAGGING:
343 move_request(this, delta_time);
349 bool is_trim_height(double y) {
350 return y - allocation.y > allocation.height / 2;
353 bool is_left_trim(double x, double y) {
354 return is_trim_height(y) && x > 0 && x < TRIM_WIDTH;
357 bool is_right_trim(double x, double y) {
358 return is_trim_height(y) && x > allocation.width - TRIM_WIDTH &&
359 x < allocation.width;
362 public void select() {
364 selection_request(this, true);
368 public void snap(int64 amount) {
369 snap_amount = time_provider.time_to_xsize(amount);