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 ClipLibraryView : Gtk.EventBox {
10 public static Gtk.Menu context_menu;
11 Model.Project project;
12 Gtk.TreeView tree_view;
13 Gtk.TreeSelection selection;
14 Gtk.Label label = null;
15 Gtk.ListStore list_store;
17 Gee.ArrayList<string> files_dragging = new Gee.ArrayList<string>();
19 Gtk.IconTheme icon_theme;
21 Gdk.Pixbuf default_audio_icon;
22 Gdk.Pixbuf default_video_icon;
23 Gdk.Pixbuf default_error_icon;
37 public signal void selection_changed(bool selected);
40 Model.TimeSystem time_provider;
42 public ClipLibraryView(Model.Project p, Model.TimeSystem time_provider, string? drag_message,
43 Gdk.DragAction actions) {
44 Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, drag_target_entries, Gdk.DragAction.COPY);
46 this.time_provider = time_provider;
48 icon_theme = Gtk.IconTheme.get_default();
50 list_store = new Gtk.ListStore(4, typeof (Gdk.Pixbuf), typeof (string),
51 typeof (string), typeof (string), -1);
53 tree_view = new Gtk.TreeView.with_model(list_store);
55 add_column(ColumnType.THUMBNAIL);
56 Gtk.TreeViewColumn name_column = add_column(ColumnType.NAME);
57 add_column(ColumnType.DURATION);
58 list_store.set_default_sort_func(name_sort);
59 list_store.set_sort_column_id(name_column.get_sort_column_id(), Gtk.SortType.ASCENDING);
62 if (drag_message != null) {
63 label = new Gtk.Label(drag_message);
64 label.modify_fg(Gtk.StateType.NORMAL, parse_color("#fff"));
67 modify_bg(Gtk.StateType.NORMAL, parse_color("#444"));
68 tree_view.modify_base(Gtk.StateType.NORMAL, parse_color("#444"));
70 tree_view.set_headers_visible(false);
71 project.clipfile_added.connect(on_clipfile_added);
72 project.clipfile_removed.connect(on_clipfile_removed);
73 project.cleared.connect(on_remove_all_rows);
74 project.time_signature_changed.connect(on_time_signature_changed);
76 Gtk.drag_source_set(tree_view, Gdk.ModifierType.BUTTON1_MASK, drag_target_entries, actions);
77 tree_view.drag_begin.connect(on_drag_begin);
78 tree_view.drag_data_get.connect(on_drag_data_get);
79 tree_view.cursor_changed.connect(on_cursor_changed);
81 selection = tree_view.get_selection();
82 selection.set_mode(Gtk.SelectionMode.MULTIPLE);
87 // We have to have our own button press and release handlers
88 // since the normal drag-selection handling does not allow you
89 // to click outside any cell in the library to clear your selection,
90 // and also does not allow dragging multiple clips from the library
92 tree_view.button_press_event.connect(on_button_pressed);
93 tree_view.button_release_event.connect(on_button_released);
97 icon_theme.load_icon("audio-x-generic", 32, (Gtk.IconLookupFlags) 0);
99 icon_theme.load_icon("video-x-generic", 32, (Gtk.IconLookupFlags) 0);
101 icon_theme.load_icon("error", 32, (Gtk.IconLookupFlags) 0);
102 } catch (GLib.Error e) {
103 // TODO: what shall we do if these icons are not available?
106 sort_mode = SortMode.ABC;
109 Gtk.TreePath? find_first_selected() {
111 Gtk.TreeModel model = tree_view.get_model();
113 bool b = model.get_iter_first(out it);
115 Gtk.TreePath path = model.get_path(it);
116 if (selection.path_is_selected(path))
119 b = model.iter_next(ref it);
124 bool on_button_pressed(Gdk.EventButton b) {
125 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_button_pressed");
131 tree_view.get_path_at_pos((int) b.x, (int) b.y, out path, null, out cell_x, out cell_y);
134 selection.unselect_all();
138 bool shift_pressed = (b.state & Gdk.ModifierType.SHIFT_MASK) != 0;
139 bool control_pressed = (b.state & Gdk.ModifierType.CONTROL_MASK) != 0;
141 if (!control_pressed &&
143 if (!selection.path_is_selected(path))
144 selection.unselect_all();
147 Gtk.TreePath first = find_first_selected();
150 selection.select_range(first, path);
153 selection.select_path(path);
156 selection_changed(true);
157 context_menu.select_first(true);
158 context_menu.popup(null, null, null, 0, b.time);
160 context_menu.popdown();
166 bool on_button_released(Gdk.EventButton b) {
167 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_button_released");
169 Gtk.TreeViewColumn column;
174 tree_view.get_path_at_pos((int) b.x, (int) b.y, out path,
175 out column, out cell_x, out cell_y);
177 // The check for cell_x == 0 and cell_y == 0 is here since for whatever reason, this
178 // function is called when we drop some clips onto the timeline. We only need to mess with
179 // the selection if we've actually clicked in the tree view, but I cannot find a way to
180 // guarantee this, since the coordinates that the Gdk.EventButton structure and the
181 // (cell_x, cell_y) pair give are always 0, 0 when this happens.
182 // I can assume that clicking on 0, 0 exactly is next to impossible, so I feel this
183 // strange check is okay.
186 (cell_x == 0 && cell_y == 0)) {
187 selection_changed(false);
191 bool shift_pressed = (b.state & Gdk.ModifierType.SHIFT_MASK) != 0;
192 bool control_pressed = (b.state & Gdk.ModifierType.CONTROL_MASK) != 0;
194 if (!control_pressed &&
196 if (selection.path_is_selected(path))
197 selection.unselect_all();
199 selection.select_path(path);
200 selection_changed(true);
205 void on_cursor_changed() {
206 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_cursor_changed");
207 selection_changed(has_selection());
210 public void unselect_all() {
211 selection.unselect_all();
212 selection_changed(false);
215 public override void drag_data_received(Gdk.DragContext context, int x, int y,
216 Gtk.SelectionData selection_data, uint drag_info,
218 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_drag_data_received");
219 string[] a = selection_data.get_uris();
220 Gtk.drag_finish(context, true, false, time);
222 project.create_clip_importer(null, false, 0, false, (Gtk.Window) get_toplevel(), a.length);
225 foreach (string s in a) {
228 filename = GLib.Filename.from_uri(s);
229 } catch (GLib.ConvertError e) { continue; }
230 project.importer.add_file(filename);
232 project.importer.start();
234 project.error_occurred("Error importing", e.message);
239 void on_drag_data_get(Gdk.DragContext context, Gtk.SelectionData data,
240 uint info, uint time) {
241 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_drag_data_get");
243 string[] uri_array = new string[0];
245 foreach (string s in files_dragging) {
247 uri = GLib.Filename.to_uri(s);
248 } catch (GLib.ConvertError e) {
250 warning("Cannot get URI for %s! (%s)\n", s, e.message);
254 data.set_uris(uri_array);
256 Gtk.drag_set_icon_default(context);
259 int get_selected_rows(out GLib.List<Gtk.TreePath> paths) {
260 paths = selection.get_selected_rows(null);
261 return (int) paths.length();
264 void on_drag_begin(Gdk.DragContext c) {
265 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_drag_begin");
266 GLib.List<Gtk.TreePath> paths;
267 if (get_selected_rows(out paths) > 0) {
268 bool set_pixbuf = false;
269 files_dragging.clear();
270 foreach (Gtk.TreePath t in paths) {
272 list_store.get_iter(out iter, t);
275 list_store.get(iter, ColumnType.FILENAME, out filename, -1);
276 files_dragging.add(filename);
280 list_store.get(iter, ColumnType.THUMBNAIL, out pixbuf, -1);
282 Gtk.drag_set_icon_pixbuf(c, pixbuf, 0, 0);
289 Gtk.TreeViewColumn add_column(ColumnType c) {
290 Gtk.TreeViewColumn column = new Gtk.TreeViewColumn();
291 Gtk.CellRenderer renderer;
293 if (c == ColumnType.THUMBNAIL) {
294 renderer = new Gtk.CellRendererPixbuf();
296 renderer = new Gtk.CellRendererText();
297 Gdk.Color color = parse_color("#FFF");
298 renderer.set("foreground-gdk", &color);
301 column.pack_start(renderer, true);
302 column.set_resizable(true);
304 if (c == ColumnType.THUMBNAIL) {
305 column.add_attribute(renderer, "pixbuf", tree_view.append_column(column) - 1);
307 column.add_attribute(renderer, "text", tree_view.append_column(column) - 1);
312 void update_iter(Gtk.TreeIter it, Model.ClipFile clip_file) {
315 if (clip_file.is_online()) {
316 if (clip_file.thumbnail == null)
317 icon = (clip_file.is_of_type(Model.MediaType.VIDEO) ?
318 default_video_icon : default_audio_icon);
320 icon = clip_file.thumbnail;
323 icon = default_error_icon;
326 list_store.set(it, ColumnType.THUMBNAIL, icon,
327 ColumnType.NAME, isolate_filename(clip_file.filename),
328 ColumnType.DURATION, time_provider.get_time_duration(clip_file.length),
329 ColumnType.FILENAME, clip_file.filename, -1);
332 void on_clipfile_added(Model.ClipFile f) {
333 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_file_added");
336 if (find_clipfile(f, out it) >= 0) {
337 list_store.remove(it);
339 if (num_clipfiles == 0) {
349 list_store.append(out it);
353 int find_clipfile(Model.ClipFile f, out Gtk.TreeIter iter) {
354 Gtk.TreeModel model = tree_view.get_model();
356 bool b = model.get_iter_first(out iter);
361 model.get(iter, ColumnType.FILENAME, out filename);
363 if (filename == f.filename)
367 b = model.iter_next(ref iter);
372 public void on_clipfile_removed(Model.ClipFile f) {
373 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_clip_file_removed");
376 if (find_clipfile(f, out it) >= 0) {
381 bool remove_row(ref Gtk.TreeIter it) {
382 bool b = list_store.remove(it);
384 if (num_clipfiles == 0) {
394 void on_remove_all_rows() {
395 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_remove_all_rows");
396 Gtk.TreeModel model = tree_view.get_model();
399 bool b = model.get_iter_first(out iter);
402 b = remove_row(ref iter);
406 void on_time_signature_changed(Fraction time_signature) {
407 emit(this, Facility.SIGNAL_HANDLERS, Level.INFO, "on_time_signature_changed");
409 bool more_items = list_store.get_iter_first(out iter);
412 list_store.get(iter, ColumnType.FILENAME, out filename, -1);
413 Model.ClipFile clip_file = project.find_clipfile(filename);
414 list_store.set(iter, ColumnType.DURATION,
415 time_provider.get_time_duration(clip_file.length), -1);
416 more_items = list_store.iter_next(ref iter);
420 void delete_row(Gtk.TreeModel model, Gtk.TreePath path) {
422 if (list_store.get_iter(out it, path)) {
424 model.get(it, ColumnType.FILENAME, out filename, -1);
425 if (project.clipfile_on_track(filename)) {
426 if (DialogUtils.delete_cancel("Clip is in use. Delete anyway?") !=
427 Gtk.ResponseType.YES)
431 project.remove_clipfile(filename);
433 if (Path.get_dirname(filename) == project.get_audio_path()) {
434 if (DialogUtils.delete_keep("Delete clip from disk? This action is not undoable.")
435 == Gtk.ResponseType.YES) {
436 if (FileUtils.unlink(filename) != 0) {
437 project.error_occurred("Could not delete %s", filename);
439 project.undo_manager.reset();
445 public bool has_selection() {
446 GLib.List<Gtk.TreePath> paths;
447 return get_selected_rows(out paths) != 0;
450 public Gee.ArrayList<string> get_selected_files() {
451 GLib.List<Gtk.TreePath> paths;
452 Gee.ArrayList<string> return_value = new Gee.ArrayList<string>();
453 if (get_selected_rows(out paths) != 0) {
454 foreach (Gtk.TreePath path in paths) {
456 if (list_store.get_iter(out iter, path)) {
458 list_store.get(iter, ColumnType.FILENAME, out name, -1);
459 return_value.add(name);
466 public void delete_selection() {
467 GLib.List<Gtk.TreePath> paths;
468 project.undo_manager.start_transaction("Delete Clips From Library");
469 if (get_selected_rows(out paths) > 0) {
470 for (int i = (int) paths.length() - 1; i >= 0; i--)
471 delete_row(list_store, paths.nth_data(i));
473 project.undo_manager.end_transaction("Delete Clips From Library");
476 public void select_all() {
477 selection.select_all();
480 int name_sort(Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
483 model.get(a, ColumnType.NAME, out left);
484 model.get(b, ColumnType.NAME, out right);
485 return stricmp(left, right);