--- /dev/null
+/* Copyright 2009-2010 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+using Logging;
+
+public class GapView : Gtk.DrawingArea {
+ public Model.Gap gap;
+ Gdk.Color fill_color;
+
+ public GapView(int64 start, int64 length, int width, int height) {
+
+ gap = new Model.Gap(start, start + length);
+
+ Gdk.Color.parse("#777", out fill_color);
+
+ set_flags(Gtk.WidgetFlags.NO_WINDOW);
+
+ set_size_request(width, height);
+ }
+
+ public signal void removed(GapView gap_view);
+ public signal void unselected(GapView gap_view);
+
+ public void remove() {
+ removed(this);
+ }
+
+ public void unselect() {
+ unselected(this);
+ }
+
+ public override bool expose_event(Gdk.EventExpose e) {
+ draw_rounded_rectangle(window, fill_color, true, allocation.x, allocation.y,
+ allocation.width - 1, allocation.height - 1);
+ return true;
+ }
+}
+
+public class ClipView : Gtk.DrawingArea {
+ enum MotionMode {
+ NONE,
+ DRAGGING,
+ LEFT_TRIM,
+ RIGHT_TRIM
+ }
+
+ public Model.Clip clip;
+ public int64 initial_time;
+ weak Model.TimeSystem time_provider;
+ public bool is_selected;
+ public int height; // TODO: We request size of height, but we aren't allocated this height.
+ // We should be using the allocated height, not the requested height.
+ public static Gtk.Menu context_menu;
+ TransportDelegate transport_delegate;
+ Gdk.Color color_black;
+ Gdk.Color color_normal;
+ Gdk.Color color_selected;
+ int drag_point;
+ int snap_amount;
+ bool snapped;
+ MotionMode motion_mode = MotionMode.NONE;
+ bool button_down = false;
+ bool pending_selection;
+ const int MIN_DRAG = 5;
+ const int TRIM_WIDTH = 10;
+ public const int SNAP_DELTA = 10;
+
+ static Gdk.Cursor left_trim_cursor = new Gdk.Cursor(Gdk.CursorType.LEFT_SIDE);
+ static Gdk.Cursor right_trim_cursor = new Gdk.Cursor(Gdk.CursorType.RIGHT_SIDE);
+ static Gdk.Cursor hand_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-none");
+ // will be used for drag
+ static Gdk.Cursor plus_cursor = new Gdk.Cursor.from_name(Gdk.Display.get_default(), "dnd-copy");
+
+ public signal void clip_deleted(Model.Clip clip);
+ public signal void clip_moved(ClipView clip);
+ public signal void selection_request(ClipView clip_view, bool extend_selection);
+ public signal void move_request(ClipView clip_view, int64 delta);
+ public signal void move_commit(ClipView clip_view, int64 delta);
+ public signal void move_begin(ClipView clip_view, bool copy);
+ public signal void trim_begin(ClipView clip_view, Gdk.WindowEdge edge);
+ public signal void trim_commit(ClipView clip_view, Gdk.WindowEdge edge);
+
+ public ClipView(TransportDelegate transport_delegate, Model.Clip clip,
+ Model.TimeSystem time_provider, int height) {
+ this.transport_delegate = transport_delegate;
+ this.clip = clip;
+ this.time_provider = time_provider;
+ this.height = height;
+ is_selected = false;
+
+ clip.moved.connect(on_clip_moved);
+ clip.updated.connect(on_clip_updated);
+
+ Gdk.Color.parse("000", out color_black);
+ get_clip_colors();
+
+ set_flags(Gtk.WidgetFlags.NO_WINDOW);
+
+ adjust_size(height);
+ }
+
+ void get_clip_colors() {
+ if (clip.clipfile.is_online()) {
+ Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#d82" : "#84a",
+ out color_selected);
+ Gdk.Color.parse(clip.type == Model.MediaType.VIDEO ? "#da5" : "#b9d",
+ out color_normal);
+ } else {
+ Gdk.Color.parse("red", out color_selected);
+ Gdk.Color.parse("#AA0000", out color_normal);
+ }
+ }
+
+ void on_clip_updated() {
+ emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_updated");
+ get_clip_colors();
+ queue_draw();
+ }
+
+ // Note that a view's size may vary slightly (by a single pixel) depending on its
+ // starting position. This is because the clip's length may not be an integer number of
+ // pixels, and may get rounded either up or down depending on the clip position.
+ public void adjust_size(int height) {
+ int width = time_provider.time_to_xpos(clip.start + clip.duration) -
+ time_provider.time_to_xpos(clip.start);
+ set_size_request(width + 1, height);
+ }
+
+ public void on_clip_moved(Model.Clip clip) {
+ emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_moved");
+ adjust_size(height);
+ clip_moved(this);
+ }
+
+ public void delete_clip() {
+ clip_deleted(clip);
+ }
+
+ public void draw() {
+ weak Gdk.Color fill = is_selected ? color_selected : color_normal;
+
+ bool left_trimmed = clip.media_start != 0 && !clip.is_recording;
+
+ bool right_trimmed = clip.clipfile.is_online() ?
+ (clip.media_start + clip.duration != clip.clipfile.length) : false;
+
+ if (!left_trimmed && !right_trimmed) {
+ draw_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
+ allocation.width - 2, allocation.height - 2);
+ draw_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
+ allocation.width - 1, allocation.height - 1);
+
+ } else if (!left_trimmed && right_trimmed) {
+ draw_left_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
+ allocation.width - 2, allocation.height - 2);
+ draw_left_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
+ allocation.width - 1, allocation.height - 1);
+
+ } else if (left_trimmed && !right_trimmed) {
+ draw_right_rounded_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
+ allocation.width - 2, allocation.height - 2);
+ draw_right_rounded_rectangle(window, color_black, false, allocation.x, allocation.y,
+ allocation.width - 1, allocation.height - 1);
+
+ } else {
+ draw_square_rectangle(window, fill, true, allocation.x + 1, allocation.y + 1,
+ allocation.width - 2, allocation.height - 2);
+ draw_square_rectangle(window, color_black, false, allocation.x, allocation.y,
+ allocation.width - 1, allocation.height - 1);
+ }
+
+ Gdk.GC gc = new Gdk.GC(window);
+ Gdk.Rectangle r = { 0, 0, 0, 0 };
+
+ // Due to a Vala compiler bug, we have to do this initialization here...
+ r.x = allocation.x;
+ r.y = allocation.y;
+ r.width = allocation.width;
+ r.height = allocation.height;
+
+ gc.set_clip_rectangle(r);
+
+ Pango.Layout layout;
+ if (clip.is_recording) {
+ layout = create_pango_layout("Recording");
+ } else if (!clip.clipfile.is_online()) {
+ layout = create_pango_layout("%s (Offline)".printf(clip.name));
+ }
+ else {
+ layout = create_pango_layout("%s".printf(clip.name));
+ }
+ int width, height;
+ layout.get_pixel_size(out width, out height);
+ Gdk.draw_layout(window, gc, allocation.x + 10, allocation.y + height, layout);
+ }
+
+ public override bool expose_event(Gdk.EventExpose event) {
+ draw();
+ return true;
+ }
+
+ public override bool button_press_event(Gdk.EventButton event) {
+ if (!transport_delegate.is_stopped()) {
+ return true;
+ }
+
+ event.x -= allocation.x;
+ bool primary_press = event.button == 1;
+ if (primary_press) {
+ button_down = true;
+ drag_point = (int)event.x;
+ snap_amount = 0;
+ snapped = false;
+ }
+
+ bool extend_selection = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0;
+ // The clip is not responsible for changing the selection state.
+ // It may depend upon knowledge of multiple clips. Let anyone who is interested
+ // update our state.
+ if (is_left_trim(event.x, event.y)) {
+ selection_request(this, false);
+ if (primary_press) {
+ trim_begin(this, Gdk.WindowEdge.WEST);
+ motion_mode = MotionMode.LEFT_TRIM;
+ }
+ } else if (is_right_trim(event.x, event.y)){
+ selection_request(this, false);
+ if (primary_press) {
+ trim_begin(this, Gdk.WindowEdge.EAST);
+ motion_mode = MotionMode.RIGHT_TRIM;
+ }
+ } else {
+ if (!is_selected) {
+ pending_selection = false;
+ selection_request(this, extend_selection);
+ } else {
+ pending_selection = true;
+ }
+ }
+
+ if (event.button == 3) {
+ context_menu.select_first(true);
+ context_menu.popup(null, null, null, event.button, event.time);
+ } else {
+ context_menu.popdown();
+ }
+
+ return true;
+ }
+
+ public override bool button_release_event(Gdk.EventButton event) {
+ if (!transport_delegate.is_stopped()) {
+ return true;
+ }
+
+ event.x -= allocation.x;
+ button_down = false;
+ if (event.button == 1) {
+ switch (motion_mode) {
+ case MotionMode.NONE: {
+ if (pending_selection) {
+ selection_request(this, true);
+ }
+ }
+ break;
+ case MotionMode.DRAGGING: {
+ int64 delta = time_provider.xsize_to_time((int) event.x - drag_point);
+ if (motion_mode == MotionMode.DRAGGING) {
+ move_commit(this, delta);
+ }
+ }
+ break;
+ case MotionMode.LEFT_TRIM:
+ trim_commit(this, Gdk.WindowEdge.WEST);
+ break;
+ case MotionMode.RIGHT_TRIM:
+ trim_commit(this, Gdk.WindowEdge.EAST);
+ break;
+ }
+ }
+ motion_mode = MotionMode.NONE;
+ return true;
+ }
+
+ public override bool motion_notify_event(Gdk.EventMotion event) {
+ if (!transport_delegate.is_stopped()) {
+ return true;
+ }
+
+ event.x -= allocation.x;
+ int delta_pixels = (int)(event.x - drag_point) - snap_amount;
+ if (snapped) {
+ snap_amount += delta_pixels;
+ if (snap_amount.abs() < SNAP_DELTA) {
+ return true;
+ }
+ delta_pixels += snap_amount;
+ snap_amount = 0;
+ snapped = false;
+ }
+
+ int64 delta_time = time_provider.xsize_to_time(delta_pixels);
+
+ switch (motion_mode) {
+ case MotionMode.NONE:
+ if (!button_down && is_left_trim(event.x, event.y)) {
+ window.set_cursor(left_trim_cursor);
+ } else if (!button_down && is_right_trim(event.x, event.y)) {
+ window.set_cursor(right_trim_cursor);
+ } else if (is_selected && button_down) {
+ if (delta_pixels.abs() > MIN_DRAG) {
+ bool do_copy = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0;
+ if (do_copy) {
+ window.set_cursor(plus_cursor);
+ } else {
+ window.set_cursor(hand_cursor);
+ }
+ motion_mode = MotionMode.DRAGGING;
+ move_begin(this, do_copy);
+ }
+ } else {
+ window.set_cursor(null);
+ }
+ break;
+ case MotionMode.RIGHT_TRIM:
+ case MotionMode.LEFT_TRIM:
+ if (button_down) {
+ if (motion_mode == MotionMode.LEFT_TRIM) {
+ clip.trim(delta_time, Gdk.WindowEdge.WEST);
+ } else {
+ int64 duration = clip.duration;
+ clip.trim(delta_time, Gdk.WindowEdge.EAST);
+ if (duration != clip.duration) {
+ drag_point += (int)delta_pixels;
+ }
+ }
+ }
+ return true;
+ case MotionMode.DRAGGING:
+ move_request(this, delta_time);
+ return true;
+ }
+ return false;
+ }
+
+ bool is_trim_height(double y) {
+ return y - allocation.y > allocation.height / 2;
+ }
+
+ bool is_left_trim(double x, double y) {
+ return is_trim_height(y) && x > 0 && x < TRIM_WIDTH;
+ }
+
+ bool is_right_trim(double x, double y) {
+ return is_trim_height(y) && x > allocation.width - TRIM_WIDTH &&
+ x < allocation.width;
+ }
+
+ public void select() {
+ if (!is_selected) {
+ selection_request(this, true);
+ }
+ }
+
+ public void snap(int64 amount) {
+ snap_amount = time_provider.time_to_xsize(amount);
+ snapped = true;
+ }
+}