X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=gtk2_ardour%2Feditor_canvas.cc;h=4fbe0d844404746c3d6b531578f8b1b4a36668a4;hb=cf89f645ab4a7fae37abd0241c02203353f359ff;hp=a1d378037472ddf838f672511d9f938a8933aba7;hpb=6b78532dd5a75e2d13dba2b1fd07f099dde706a8;p=ardour.git diff --git a/gtk2_ardour/editor_canvas.cc b/gtk2_ardour/editor_canvas.cc index a1d3780374..4fbe0d8444 100644 --- a/gtk2_ardour/editor_canvas.cc +++ b/gtk2_ardour/editor_canvas.cc @@ -21,23 +21,24 @@ #include "gtk2ardour-config.h" #endif -#include - #include "gtkmm2ext/utils.h" #include "ardour/profile.h" #include "ardour/rc_configuration.h" #include "ardour/smf_source.h" +#include "pbd/error.h" + #include "canvas/canvas.h" #include "canvas/rectangle.h" #include "canvas/pixbuf.h" +#include "canvas/scroll_group.h" #include "canvas/text.h" #include "canvas/debug.h" #include "ardour_ui.h" +#include "automation_time_axis.h" #include "editor.h" -#include "global_signals.h" #include "editing.h" #include "rgb_macros.h" #include "utils.h" @@ -50,142 +51,160 @@ #include "keyboard.h" #include "editor_cursors.h" #include "mouse_cursors.h" +#include "ui_config.h" #include "verbose_cursor.h" -#include "i18n.h" +#include "pbd/i18n.h" using namespace std; using namespace ARDOUR; +using namespace ARDOUR_UI_UTILS; using namespace PBD; using namespace Gtk; using namespace Glib; using namespace Gtkmm2ext; using namespace Editing; -/* XXX this is a hack. it ought to be the maximum value of an framepos_t */ - -const double max_canvas_coordinate = (double) JACK_MAX_FRAMES; - void Editor::initialize_canvas () { _track_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, vertical_adjustment); _track_canvas = _track_canvas_viewport->canvas (); - _time_bars_canvas_viewport = new ArdourCanvas::GtkCanvasViewport (horizontal_adjustment, unused_adjustment); - _time_bars_canvas = _time_bars_canvas_viewport->canvas (); - - _verbose_cursor = new VerboseCursor (this); + _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base")); - /* on the bottom, an image */ + /* scroll group for items that should not automatically scroll + * (e.g verbose cursor). It shares the canvas coordinate space. + */ + no_scroll_group = new ArdourCanvas::Container (_track_canvas->root()); - if (Profile->get_sae()) { - Image img (::get_icon (X_("saelogo"))); - // logo_item = new ArdourCanvas::Pixbuf (_track_canvas->root(), 0.0, 0.0, img.get_pixbuf()); - // logo_item->property_height_in_pixels() = true; - // logo_item->property_width_in_pixels() = true; - // logo_item->property_height_set() = true; - // logo_item->property_width_set() = true; - logo_item->show (); - } + ArdourCanvas::ScrollGroup* hsg; + ArdourCanvas::ScrollGroup* hg; + ArdourCanvas::ScrollGroup* cg; + + h_scroll_group = hg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally); + CANVAS_DEBUG_NAME (h_scroll_group, "canvas h scroll"); + _track_canvas->add_scroller (*hg); + + hv_scroll_group = hsg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), + ArdourCanvas::ScrollGroup::ScrollSensitivity (ArdourCanvas::ScrollGroup::ScrollsVertically| + ArdourCanvas::ScrollGroup::ScrollsHorizontally)); + CANVAS_DEBUG_NAME (hv_scroll_group, "canvas hv scroll"); + _track_canvas->add_scroller (*hsg); - /* a group to hold time (measure) lines */ - time_line_group = new ArdourCanvas::Group (_track_canvas->root()); + cursor_scroll_group = cg = new ArdourCanvas::ScrollGroup (_track_canvas->root(), ArdourCanvas::ScrollGroup::ScrollsHorizontally); + CANVAS_DEBUG_NAME (cursor_scroll_group, "canvas cursor scroll"); + _track_canvas->add_scroller (*cg); - transport_loop_range_rect = new ArdourCanvas::Rectangle (time_line_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX)); - transport_loop_range_rect->set_outline_width (1); + _verbose_cursor = new VerboseCursor (this); + + /*a group to hold global rects like punch/loop indicators */ + global_rect_group = new ArdourCanvas::Container (hv_scroll_group); + CANVAS_DEBUG_NAME (global_rect_group, "global rect group"); + + transport_loop_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX)); + CANVAS_DEBUG_NAME (transport_loop_range_rect, "loop rect"); transport_loop_range_rect->hide(); - transport_punch_range_rect = new ArdourCanvas::Rectangle (time_line_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX)); - transport_punch_range_rect->set_outline_width (0); + transport_punch_range_rect = new ArdourCanvas::Rectangle (global_rect_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, ArdourCanvas::COORD_MAX)); + CANVAS_DEBUG_NAME (transport_punch_range_rect, "punch rect"); transport_punch_range_rect->hide(); - _trackview_group = new ArdourCanvas::Group (_track_canvas->root()); + /*a group to hold time (measure) lines */ + time_line_group = new ArdourCanvas::Container (h_scroll_group); + CANVAS_DEBUG_NAME (time_line_group, "time line group"); + + _trackview_group = new ArdourCanvas::Container (hv_scroll_group); CANVAS_DEBUG_NAME (_trackview_group, "Canvas TrackViews"); - _region_motion_group = new ArdourCanvas::Group (_trackview_group); - CANVAS_DEBUG_NAME (_region_motion_group, "Canvas Region Motion"); - - meter_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - meter_bar = new ArdourCanvas::Rectangle (meter_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - meter_bar->set_outline_width (1); - meter_bar->set_outline_what (0x8); - - tempo_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - tempo_bar = new ArdourCanvas::Rectangle (tempo_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - tempo_bar->set_outline_width (1); - tempo_bar->set_outline_what (0x8); - - range_marker_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - range_marker_bar = new ArdourCanvas::Rectangle (range_marker_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - range_marker_bar->set_outline_width (1); - range_marker_bar->set_outline_what (0x8); - - transport_marker_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - transport_marker_bar = new ArdourCanvas::Rectangle (transport_marker_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - transport_marker_bar->set_outline_width (1); - transport_marker_bar->set_outline_what (0x8); - - marker_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - marker_bar = new ArdourCanvas::Rectangle (marker_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - marker_bar->set_outline_width (1); - marker_bar->set_outline_what (0x8); - - cd_marker_bar_group = new ArdourCanvas::Group (_time_bars_canvas->root ()); - cd_marker_bar = new ArdourCanvas::Rectangle (cd_marker_bar_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height - 1)); - cd_marker_bar->set_outline_width (1); - cd_marker_bar->set_outline_what (0x8); - - _time_markers_group = new ArdourCanvas::Group (_time_bars_canvas->root()); - - meter_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height * 5.0)); - tempo_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height * 4.0)); - range_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height * 3.0)); - transport_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height * 2.0)); - marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height)); - cd_marker_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0)); - videotl_group = new ArdourCanvas::Group (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0)); + + // used as rubberband rect + rubberband_rect = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0)); + rubberband_rect->hide(); + + /* a group to hold stuff while it gets dragged around. Must be the + * uppermost (last) group with hv_scroll_group as a parent + */ + _drag_motion_group = new ArdourCanvas::Container (hv_scroll_group); + CANVAS_DEBUG_NAME (_drag_motion_group, "Canvas Drag Motion"); + + /* TIME BAR CANVAS */ + + _time_markers_group = new ArdourCanvas::Container (h_scroll_group); + CANVAS_DEBUG_NAME (_time_markers_group, "time bars"); + + cd_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, 0.0)); + CANVAS_DEBUG_NAME (cd_marker_group, "cd marker group"); + /* the vide is temporarily placed a the same location as the + cd_marker_group, but is moved later. + */ + videotl_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple(0.0, 0.0)); + CANVAS_DEBUG_NAME (videotl_group, "videotl group"); + marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, timebar_height + 1.0)); + CANVAS_DEBUG_NAME (marker_group, "marker group"); + transport_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 2.0) + 1.0)); + CANVAS_DEBUG_NAME (transport_marker_group, "transport marker group"); + range_marker_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 3.0) + 1.0)); + CANVAS_DEBUG_NAME (range_marker_group, "range marker group"); + tempo_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 4.0) + 1.0)); + CANVAS_DEBUG_NAME (tempo_group, "tempo group"); + meter_group = new ArdourCanvas::Container (_time_markers_group, ArdourCanvas::Duple (0.0, (timebar_height * 5.0) + 1.0)); + CANVAS_DEBUG_NAME (meter_group, "meter group"); + + meter_bar = new ArdourCanvas::Rectangle (meter_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (meter_bar, "meter Bar"); + meter_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); + + tempo_bar = new ArdourCanvas::Rectangle (tempo_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (tempo_bar, "Tempo Bar"); + tempo_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); + + range_marker_bar = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (range_marker_bar, "Range Marker Bar"); + range_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); + + transport_marker_bar = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (transport_marker_bar, "transport Marker Bar"); + transport_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); + + marker_bar = new ArdourCanvas::Rectangle (marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (marker_bar, "Marker Bar"); + marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); + + cd_marker_bar = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, timebar_height)); + CANVAS_DEBUG_NAME (cd_marker_bar, "CD Marker Bar"); + cd_marker_bar->set_outline_what (ArdourCanvas::Rectangle::BOTTOM); ARDOUR_UI::instance()->video_timeline = new VideoTimeLine(this, videotl_group, (timebar_height * videotl_bar_height)); cd_marker_bar_drag_rect = new ArdourCanvas::Rectangle (cd_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height)); + CANVAS_DEBUG_NAME (cd_marker_bar_drag_rect, "cd marker drag"); cd_marker_bar_drag_rect->set_outline (false); cd_marker_bar_drag_rect->hide (); range_bar_drag_rect = new ArdourCanvas::Rectangle (range_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height)); + CANVAS_DEBUG_NAME (range_bar_drag_rect, "range drag"); range_bar_drag_rect->set_outline (false); range_bar_drag_rect->hide (); transport_bar_drag_rect = new ArdourCanvas::Rectangle (transport_marker_group, ArdourCanvas::Rect (0.0, 0.0, 100, timebar_height)); + CANVAS_DEBUG_NAME (transport_bar_drag_rect, "transport drag"); transport_bar_drag_rect->set_outline (false); transport_bar_drag_rect->hide (); - transport_punchin_line = new ArdourCanvas::Line (_track_canvas->root()); + transport_punchin_line = new ArdourCanvas::Line (hv_scroll_group); transport_punchin_line->set_x0 (0); transport_punchin_line->set_y0 (0); transport_punchin_line->set_x1 (0); transport_punchin_line->set_y1 (ArdourCanvas::COORD_MAX); transport_punchin_line->hide (); - transport_punchout_line = new ArdourCanvas::Line (_track_canvas->root()); + transport_punchout_line = new ArdourCanvas::Line (hv_scroll_group); transport_punchout_line->set_x0 (0); transport_punchout_line->set_y0 (0); transport_punchout_line->set_x1 (0); transport_punchout_line->set_y1 (ArdourCanvas::COORD_MAX); transport_punchout_line->hide(); - // used to show zoom mode active zooming - zoom_rect = new ArdourCanvas::Rectangle (_track_canvas->root(), ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0)); - zoom_rect->set_outline_width (1); - zoom_rect->hide(); - - zoom_rect->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_zoom_rect_event), (ArdourCanvas::Item*) 0)); - - // used as rubberband rect - rubberband_rect = new ArdourCanvas::Rectangle (_trackview_group, ArdourCanvas::Rect (0.0, 0.0, 0.0, 0.0)); - - rubberband_rect->set_outline_width (1); - rubberband_rect->hide(); - tempo_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_tempo_bar_event), tempo_bar)); meter_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_meter_bar_event), meter_bar)); marker_bar->Event.connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_marker_bar_event), marker_bar)); @@ -196,12 +215,17 @@ Editor::initialize_canvas () playhead_cursor = new EditorCursor (*this, &Editor::canvas_playhead_cursor_event); - if (logo_item) { - logo_item->lower_to_bottom (); - } - /* need to handle 4 specific types of events as catch-alls */ + _canvas_drop_zone = new ArdourCanvas::Rectangle (hv_scroll_group, ArdourCanvas::Rect (0.0, 0.0, ArdourCanvas::COORD_MAX, 0.0)); + /* this thing is transparent */ + _canvas_drop_zone->set_fill (false); + _canvas_drop_zone->set_outline (false); + _canvas_drop_zone->Event.connect (sigc::mem_fun (*this, &Editor::canvas_drop_zone_event)); - _track_canvas->signal_scroll_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_scroll_event)); + /* these signals will initially be delivered to the canvas itself, but if they end up remaining unhandled, they are passed to Editor-level + handlers. + */ + + _track_canvas->signal_scroll_event().connect (sigc::bind (sigc::mem_fun (*this, &Editor::canvas_scroll_event), true)); _track_canvas->signal_motion_notify_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_motion_notify_event)); _track_canvas->signal_button_press_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_press_event)); _track_canvas->signal_button_release_event().connect (sigc::mem_fun (*this, &Editor::track_canvas_button_release_event)); @@ -231,7 +255,9 @@ Editor::initialize_canvas () _track_canvas_viewport->signal_size_allocate().connect (sigc::mem_fun(*this, &Editor::track_canvas_viewport_allocate)); - ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler)); + initialize_rulers (); + + UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &Editor::color_handler)); color_handler(); } @@ -243,7 +269,7 @@ Editor::track_canvas_viewport_allocate (Gtk::Allocation alloc) track_canvas_viewport_size_allocated (); } -bool +void Editor::track_canvas_viewport_size_allocated () { bool height_changed = _visible_canvas_height != _canvas_viewport_allocation.get_height(); @@ -251,6 +277,8 @@ Editor::track_canvas_viewport_size_allocated () _visible_canvas_width = _canvas_viewport_allocation.get_width (); _visible_canvas_height = _canvas_viewport_allocation.get_height (); + _canvas_drop_zone->set_y1 (_canvas_drop_zone->y0() + (_visible_canvas_height - 20.0)); + // SHOWTRACKS if (height_changed) { @@ -267,25 +295,25 @@ Editor::track_canvas_viewport_size_allocated () */ vertical_adjustment.set_value (_full_canvas_height - _visible_canvas_height); } + + set_visible_track_count (_visible_track_count); } update_fixed_rulers(); redisplay_tempo (false); _summary->set_overlays_dirty (); - - return false; } void Editor::reset_controls_layout_width () { - GtkRequisition req; + GtkRequisition req = { 0, 0 }; gint w; edit_controls_vbox.size_request (req); w = req.width; - if (_group_tabs->is_mapped()) { + if (_group_tabs->is_visible()) { _group_tabs->size_request (req); w += req.width; } @@ -301,19 +329,33 @@ Editor::reset_controls_layout_width () void Editor::reset_controls_layout_height (int32_t h) { + /* ensure that the rect that represents the "bottom" of the canvas + * (the drag-n-drop zone) is, in fact, at the bottom. + */ + + _canvas_drop_zone->set_position (ArdourCanvas::Duple (0, h)); + + /* track controls layout must span the full height of "h" (all tracks) + * plus the bottom rect. + */ + + h += _canvas_drop_zone->height (); + /* set the height of the scrollable area (i.e. the sum of all contained widgets) + * for the controls layout. The size request is set elsewhere. */ controls_layout.property_height() = h; - /* size request is set elsewhere, see ::track_canvas_allocate() */ } bool Editor::track_canvas_map_handler (GdkEventAny* /*ev*/) { - if (current_canvas_cursor) { - set_canvas_cursor (current_canvas_cursor); + if (!_cursor_stack.empty()) { + set_canvas_cursor (get_canvas_cursor()); + } else { + PBD::error << "cursor stack is empty" << endmsg; } return false; } @@ -343,7 +385,7 @@ void Editor::drop_paths_part_two (const vector& paths, framepos_t frame, double ypos, bool copy) { RouteTimeAxisView* tv; - + /* MIDI files must always be imported, because we consider them * writable. So split paths into two vectors, and follow the import * path on the MIDI part. @@ -361,16 +403,16 @@ Editor::drop_paths_part_two (const vector& paths, framepos_t frame, doub } - std::pair const tvp = trackview_by_y_position (ypos); + std::pair const tvp = trackview_by_y_position (ypos, false); if (tvp.first == 0) { /* drop onto canvas background: create new tracks */ frame = 0; + InstrumentSelector is; // instantiation builds instrument-list and sets default. + do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, frame, is.selected_instrument()); - do_import (midi_paths, Editing::ImportDistinctFiles, ImportAsTrack, SrcBest, frame); - - if (Profile->get_sae() || Config->get_only_copy_imported_files() || copy) { + if (UIConfiguration::instance().get_only_copy_imported_files() || copy) { do_import (audio_paths, Editing::ImportDistinctFiles, Editing::ImportAsTrack, SrcBest, frame); } else { do_embed (audio_paths, Editing::ImportDistinctFiles, ImportAsTrack, frame); @@ -386,7 +428,7 @@ Editor::drop_paths_part_two (const vector& paths, framepos_t frame, doub do_import (midi_paths, Editing::ImportSerializeFiles, ImportToTrack, SrcBest, frame); - if (Profile->get_sae() || Config->get_only_copy_imported_files() || copy) { + if (UIConfiguration::instance().get_only_copy_imported_files() || copy) { do_import (audio_paths, Editing::ImportSerializeFiles, Editing::ImportToTrack, SrcBest, frame); } else { do_embed (audio_paths, Editing::ImportSerializeFiles, ImportToTrack, frame); @@ -408,19 +450,19 @@ Editor::drop_paths (const RefPtr& context, if (convert_drop_to_paths (paths, context, x, y, data, info, time) == 0) { - /* D-n-D coordinates are window-relative, so convert to "world" coordinates + /* D-n-D coordinates are window-relative, so convert to canvas coordinates */ ev.type = GDK_BUTTON_RELEASE; ev.button.x = x; ev.button.y = y; - frame = window_event_frame (&ev, 0, &cy); + frame = window_event_sample (&ev, 0, &cy); snap_to (frame); bool copy = ((context->get_actions() & (Gdk::ACTION_COPY | Gdk::ACTION_LINK | Gdk::ACTION_MOVE)) == Gdk::ACTION_COPY); -#ifdef GTKOSX +#ifdef __APPLE__ /* We are not allowed to call recursive main event loops from within the main event loop with GTK/Quartz. Since import/embed wants to push up a progress dialog, defer all this till we go idle. @@ -434,258 +476,318 @@ Editor::drop_paths (const RefPtr& context, context->drag_finish (true, false, time); } -/** If the editor window is arranged such that the edge of the trackview is right up - * against the edge of the screen, autoscroll will not work very well. In this situation, - * we start autoscrolling some distance in from the right-hand-side of the screen edge; - * this is the distance at which that happens. - */ -int -Editor::autoscroll_fudge_threshold () const -{ - return current_page_samples() / 6; -} - /** @param allow_horiz true to allow horizontal autoscroll, otherwise false. + * * @param allow_vert true to allow vertical autoscroll, otherwise false. - * @param moving_left true if we are moving left, so we only want to autoscroll on the left of the canvas, - * otherwise false, so we only want to autoscroll on the right of the canvas. - * @param moving_up true if we are moving up, so we only want to autoscroll at the top of the canvas, - * otherwise false, so we only want to autoscroll at the bottom of the canvas. + * */ void -Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool moving_left, bool moving_up) +Editor::maybe_autoscroll (bool allow_horiz, bool allow_vert, bool from_headers) { - if (!Config->get_autoscroll_editor ()) { + Gtk::Window* toplevel = dynamic_cast(contents().get_toplevel()); + + if (!toplevel) { return; } - - bool startit = false; - /* Work out the distance between the right hand edge of the trackview and the edge of - the monitor that it is on. - */ + if (!UIConfiguration::instance().get_autoscroll_editor () || autoscroll_active ()) { + return; + } - Glib::RefPtr gdk_window = get_window (); - Gdk::Rectangle window_rect; - gdk_window->get_frame_extents (window_rect); - - Glib::RefPtr screen = get_screen (); - Gdk::Rectangle root_rect; - screen->get_root_window()->get_frame_extents (root_rect); + /* define a rectangular boundary for scrolling. If the mouse moves + * outside of this area and/or continue to be outside of this area, + * then we will continuously auto-scroll the canvas in the appropriate + * direction(s) + * + * the boundary is defined in coordinates relative to the toplevel + * window since that is what we're going to call ::get_pointer() on + * during autoscrolling to determine if we're still outside the + * boundary or not. + */ - Gtk::Allocation editor_list = _the_notebook.get_allocation (); + ArdourCanvas::Rect scrolling_boundary; + Gtk::Allocation alloc; - framecnt_t distance = pixel_to_sample (root_rect.get_x() + root_rect.get_width() - window_rect.get_x() - window_rect.get_width()); - if (_the_notebook.is_visible ()) { - distance += pixel_to_sample (editor_list.get_width()); - } + if (from_headers) { + alloc = controls_layout.get_allocation (); + } else { + alloc = _track_canvas_viewport->get_allocation (); - /* Note whether we're fudging the autoscroll (see autoscroll_fudge_threshold) */ - _autoscroll_fudging = (distance < autoscroll_fudge_threshold ()); + /* reduce height by the height of the timebars, which happens + to correspond to the position of the hv_scroll_group. + */ - double const ty = _drags->current_pointer_y(); + alloc.set_height (alloc.get_height() - hv_scroll_group->position().y); + alloc.set_y (alloc.get_y() + hv_scroll_group->position().y); - autoscroll_y = 0; - autoscroll_x = 0; - if (ty < 0 && moving_up && allow_vert) { - autoscroll_y = -1; - startit = true; - } else if (ty > _visible_canvas_height && !moving_up && allow_vert) { - autoscroll_y = 1; - startit = true; - } + /* now reduce it again so that we start autoscrolling before we + * move off the top or bottom of the canvas + */ - framepos_t rightmost_frame = leftmost_frame + current_page_samples(); - if (_autoscroll_fudging) { - rightmost_frame -= autoscroll_fudge_threshold (); - } + alloc.set_height (alloc.get_height() - 20); + alloc.set_y (alloc.get_y() + 10); - if (_drags->current_pointer_frame() > rightmost_frame && allow_horiz) { - if (rightmost_frame < max_framepos && !moving_left) { - autoscroll_x = 1; - startit = true; - } - } else if (_drags->current_pointer_frame() < leftmost_frame && allow_horiz) { - if (leftmost_frame > 0 && moving_left) { - autoscroll_x = -1; - startit = true; + /* the effective width of the autoscroll boundary so + that we start scrolling before we hit the edge. + + this helps when the window is slammed up against the + right edge of the screen, making it hard to scroll + effectively. + */ + + if (alloc.get_width() > 20) { + alloc.set_width (alloc.get_width() - 20); + alloc.set_x (alloc.get_x() + 10); } - } - if (autoscroll_active && ((autoscroll_x != last_autoscroll_x) || (autoscroll_y != last_autoscroll_y) || (autoscroll_x == 0 && autoscroll_y == 0))) { - stop_canvas_autoscroll (); } - if (startit && autoscroll_timeout_tag < 0) { - start_canvas_autoscroll (autoscroll_x, autoscroll_y); - } + scrolling_boundary = ArdourCanvas::Rect (alloc.get_x(), alloc.get_y(), alloc.get_x() + alloc.get_width(), alloc.get_y() + alloc.get_height()); - last_autoscroll_x = autoscroll_x; - last_autoscroll_y = autoscroll_y; + int x, y; + Gdk::ModifierType mask; + + toplevel->get_window()->get_pointer (x, y, mask); + + if ((allow_horiz && ((x < scrolling_boundary.x0 && leftmost_frame > 0) || x >= scrolling_boundary.x1)) || + (allow_vert && ((y < scrolling_boundary.y0 && vertical_adjustment.get_value() > 0)|| y >= scrolling_boundary.y1))) { + start_canvas_autoscroll (allow_horiz, allow_vert, scrolling_boundary); + } } -gint -Editor::_autoscroll_canvas (void *arg) +bool +Editor::autoscroll_active () const { - return ((Editor *) arg)->autoscroll_canvas (); + return autoscroll_connection.connected (); } bool Editor::autoscroll_canvas () { - framepos_t new_frame; - framepos_t limit = max_framepos - current_page_samples(); - double new_pixel; - double target_pixel; - - if (autoscroll_x_distance != 0) { - - if (autoscroll_x > 0) { - autoscroll_x_distance = (_drags->current_pointer_frame() - (leftmost_frame + current_page_samples())) / 3; - if (_autoscroll_fudging) { - autoscroll_x_distance += autoscroll_fudge_threshold () / 3; - } - } else if (autoscroll_x < 0) { - autoscroll_x_distance = (leftmost_frame - _drags->current_pointer_frame()) / 3; + int x, y; + Gdk::ModifierType mask; + frameoffset_t dx = 0; + bool no_stop = false; + Gtk::Window* toplevel = dynamic_cast(contents().get_toplevel()); - } + if (!toplevel) { + return false; } - if (autoscroll_y_distance != 0) { - if (autoscroll_y > 0) { - autoscroll_y_distance = (_drags->current_pointer_y() - _visible_canvas_height) / 3; - } else if (autoscroll_y < 0) { + toplevel->get_window()->get_pointer (x, y, mask); - autoscroll_y_distance = (vertical_adjustment.get_value () - _drags->current_pointer_y()) / 3; - } - } + VisualChange vc; + bool vertical_motion = false; - if (autoscroll_x < 0) { - if (leftmost_frame < autoscroll_x_distance) { - new_frame = 0; - } else { - new_frame = leftmost_frame - autoscroll_x_distance; + if (autoscroll_horizontal_allowed) { + + framepos_t new_frame = leftmost_frame; + + /* horizontal */ + + if (x > autoscroll_boundary.x1) { + + /* bring it back into view */ + dx = x - autoscroll_boundary.x1; + dx += 10 + (2 * (autoscroll_cnt/2)); + + dx = pixel_to_sample (dx); + + if (leftmost_frame < max_framepos - dx) { + new_frame = leftmost_frame + dx; + } else { + new_frame = max_framepos; + } + + no_stop = true; + + } else if (x < autoscroll_boundary.x0) { + + dx = autoscroll_boundary.x0 - x; + dx += 10 + (2 * (autoscroll_cnt/2)); + + dx = pixel_to_sample (dx); + + if (leftmost_frame >= dx) { + new_frame = leftmost_frame - dx; + } else { + new_frame = 0; + } + + no_stop = true; } - } else if (autoscroll_x > 0) { - if (leftmost_frame > limit - autoscroll_x_distance) { - new_frame = limit; - } else { - new_frame = leftmost_frame + autoscroll_x_distance; + + if (new_frame != leftmost_frame) { + vc.time_origin = new_frame; + vc.add (VisualChange::TimeOrigin); } - } else { - new_frame = leftmost_frame; } - double vertical_pos = vertical_adjustment.get_value(); + if (autoscroll_vertical_allowed) { - if (autoscroll_y < 0) { + // const double vertical_pos = vertical_adjustment.get_value(); + const int speed_factor = 10; - if (vertical_pos < autoscroll_y_distance) { - new_pixel = 0; - } else { - new_pixel = vertical_pos - autoscroll_y_distance; - } + /* vertical */ - target_pixel = _drags->current_pointer_y() - autoscroll_y_distance; - target_pixel = max (target_pixel, 0.0); + if (y < autoscroll_boundary.y0) { - } else if (autoscroll_y > 0) { + /* scroll to make higher tracks visible */ - double const top_of_bottom_of_canvas = _full_canvas_height - _visible_canvas_height; + if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { + scroll_up_one_track (); + vertical_motion = true; + } + no_stop = true; - if (vertical_pos > _full_canvas_height - autoscroll_y_distance) { - new_pixel = _full_canvas_height; - } else { - new_pixel = vertical_pos + autoscroll_y_distance; + } else if (y > autoscroll_boundary.y1) { + + if (autoscroll_cnt && (autoscroll_cnt % speed_factor == 0)) { + scroll_down_one_track (); + vertical_motion = true; + } + no_stop = true; } - new_pixel = min (top_of_bottom_of_canvas, new_pixel); + } + + if (vc.pending || vertical_motion) { - target_pixel = _drags->current_pointer_y() + autoscroll_y_distance; + /* change horizontal first */ - /* don't move to the full canvas height because the item will be invisible - (its top edge will line up with the bottom of the visible canvas. + if (vc.pending) { + visual_changer (vc); + } + + /* now send a motion event to notify anyone who cares + that we have moved to a new location (because we scrolled) */ - target_pixel = min (target_pixel, _full_canvas_height - 10); + GdkEventMotion ev; - } else { - target_pixel = _drags->current_pointer_y(); - new_pixel = vertical_pos; - } + ev.type = GDK_MOTION_NOTIFY; + ev.state = Gdk::BUTTON1_MASK; - if ((new_frame == 0 || new_frame == limit) && (new_pixel == 0 || new_pixel == DBL_MAX)) { - /* we are done */ - return false; - } + /* the motion handler expects events in canvas coordinate space */ - if (new_frame != leftmost_frame) { - reset_x_origin (new_frame); - } + /* we asked for the mouse position above (::get_pointer()) via + * our own top level window (we being the Editor). Convert into + * coordinates within the canvas window. + */ - vertical_adjustment.set_value (new_pixel); + int cx; + int cy; - /* fake an event. */ + toplevel->translate_coordinates (*_track_canvas, x, y, cx, cy); - Glib::RefPtr canvas_window = const_cast(this)->_track_canvas->get_window(); - gint x, y; - Gdk::ModifierType mask; - GdkEventMotion ev; - canvas_window->get_pointer (x, y, mask); - ev.type = GDK_MOTION_NOTIFY; - ev.state = Gdk::BUTTON1_MASK; + /* clamp x and y to remain within the autoscroll boundary, + * which is defined in window coordinates + */ - /* the motion handler expects events in canvas coordinate space */ - ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (x, y)); - ev.x = d.x; - ev.y = d.y; + x = min (max ((ArdourCanvas::Coord) cx, autoscroll_boundary.x0), autoscroll_boundary.x1); + y = min (max ((ArdourCanvas::Coord) cy, autoscroll_boundary.y0), autoscroll_boundary.y1); - motion_handler (0, (GdkEvent*) &ev, true); + /* now convert from Editor window coordinates to canvas + * window coordinates + */ - autoscroll_cnt++; + ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy)); + ev.x = d.x; + ev.y = d.y; + ev.state = mask; - if (autoscroll_cnt == 1) { + motion_handler (0, (GdkEvent*) &ev, true); - /* connect the timeout so that we get called repeatedly */ + } else if (no_stop) { - autoscroll_timeout_tag = g_idle_add ( _autoscroll_canvas, this); - return false; + /* not changing visual state but pointer is outside the scrolling boundary + * so we still need to deliver a fake motion event + */ + + GdkEventMotion ev; + + ev.type = GDK_MOTION_NOTIFY; + ev.state = Gdk::BUTTON1_MASK; + + /* the motion handler expects events in canvas coordinate space */ + + /* first convert from Editor window coordinates to canvas + * window coordinates + */ + int cx; + int cy; + + /* clamp x and y to remain within the visible area. except + * .. if horizontal scrolling is allowed, always allow us to + * move back to zero + */ + + if (autoscroll_horizontal_allowed) { + x = min (max ((ArdourCanvas::Coord) x, 0.0), autoscroll_boundary.x1); + } else { + x = min (max ((ArdourCanvas::Coord) x, autoscroll_boundary.x0), autoscroll_boundary.x1); + } + y = min (max ((ArdourCanvas::Coord) y, autoscroll_boundary.y0), autoscroll_boundary.y1); + + toplevel->translate_coordinates (*_track_canvas_viewport, x, y, cx, cy); + + ArdourCanvas::Duple d = _track_canvas->window_to_canvas (ArdourCanvas::Duple (cx, cy)); + ev.x = d.x; + ev.y = d.y; + ev.state = mask; + + motion_handler (0, (GdkEvent*) &ev, true); + + } else { + stop_canvas_autoscroll (); + return false; } - return true; + autoscroll_cnt++; + + return true; /* call me again */ } void -Editor::start_canvas_autoscroll (int dx, int dy) +Editor::start_canvas_autoscroll (bool allow_horiz, bool allow_vert, const ArdourCanvas::Rect& boundary) { - if (!_session || autoscroll_active) { + if (!_session) { return; } stop_canvas_autoscroll (); - autoscroll_active = true; - autoscroll_x = dx; - autoscroll_y = dy; - autoscroll_x_distance = (framepos_t) floor (current_page_samples()/50.0); - autoscroll_y_distance = fabs (dy * 5); /* pixels */ - autoscroll_cnt = 0; + autoscroll_horizontal_allowed = allow_horiz; + autoscroll_vertical_allowed = allow_vert; + autoscroll_boundary = boundary; - /* do it right now, which will start the repeated callbacks */ + /* do the first scroll right now + */ autoscroll_canvas (); + + /* scroll again at very very roughly 30FPS */ + + autoscroll_connection = Glib::signal_timeout().connect (sigc::mem_fun (*this, &Editor::autoscroll_canvas), 30); } void Editor::stop_canvas_autoscroll () { - if (autoscroll_timeout_tag >= 0) { - g_source_remove (autoscroll_timeout_tag); - autoscroll_timeout_tag = -1; - } + autoscroll_connection.disconnect (); + autoscroll_cnt = 0; +} - autoscroll_active = false; +Editor::EnterContext* +Editor::get_enter_context(ItemType type) +{ + for (ssize_t i = _enter_stack.size() - 1; i >= 0; --i) { + if (_enter_stack[i].item_type == type) { + return &_enter_stack[i]; + } + } + return NULL; } bool @@ -693,7 +795,6 @@ Editor::left_track_canvas (GdkEventCrossing */*ev*/) { DropDownKeys (); within_track_canvas = false; - //cerr << "left track canvas\n"; set_entered_track (0); set_entered_regionview (0); reset_canvas_action_sensitivity (false); @@ -703,26 +804,54 @@ Editor::left_track_canvas (GdkEventCrossing */*ev*/) bool Editor::entered_track_canvas (GdkEventCrossing */*ev*/) { - //cerr << "entered track canvas\n"; within_track_canvas = true; reset_canvas_action_sensitivity (true); return FALSE; } void -Editor::ensure_time_axis_view_is_visible (const TimeAxisView& tav) +Editor::ensure_time_axis_view_is_visible (TimeAxisView const & track, bool at_top) { - double begin = tav.y_position(); + if (track.hidden()) { + return; + } - double v = vertical_adjustment.get_value (); + /* compute visible area of trackview group, as offsets from top of + * trackview group. + */ + + double const current_view_min_y = vertical_adjustment.get_value(); + double const current_view_max_y = current_view_min_y + vertical_adjustment.get_page_size(); + + double const track_min_y = track.y_position (); + double const track_max_y = track.y_position () + track.effective_height (); - if (begin < v || begin + tav.current_height() > v + _visible_canvas_height) { - /* try to put the TimeAxisView roughly central */ - if (begin >= _visible_canvas_height/2.0) { - begin -= _visible_canvas_height/2.0; + if (!at_top && + (track_min_y >= current_view_min_y && + track_max_y < current_view_max_y)) { + /* already visible, and caller did not ask to place it at the + * top of the track canvas + */ + return; + } + + double new_value; + + if (at_top) { + new_value = track_min_y; + } else { + if (track_min_y < current_view_min_y) { + // Track is above the current view + new_value = track_min_y; + } else if (track_max_y > current_view_max_y) { + // Track is below the current view + new_value = track.y_position () + track.effective_height() - vertical_adjustment.get_page_size(); + } else { + new_value = track_min_y; } - vertical_adjustment.set_value (begin); } + + vertical_adjustment.set_value(new_value); } /** Called when the main vertical_adjustment has changed */ @@ -749,65 +878,75 @@ Editor::set_horizontal_position (double p) } update_video_timeline(); - - HorizontalPositionChanged (); /* EMIT SIGNAL */ } void Editor::color_handler() { - playhead_cursor->set_color (ARDOUR_UI::config()->canvasvar_PlayHead.get()); - _verbose_cursor->set_color (ARDOUR_UI::config()->canvasvar_VerboseCanvasCursor.get()); + ArdourCanvas::Color base = UIConfiguration::instance().color ("ruler base"); + ArdourCanvas::Color text = UIConfiguration::instance().color ("ruler text"); + timecode_ruler->set_fill_color (base); + timecode_ruler->set_outline_color (text); + minsec_ruler->set_fill_color (base); + minsec_ruler->set_outline_color (text); + samples_ruler->set_fill_color (base); + samples_ruler->set_outline_color (text); + bbt_ruler->set_fill_color (base); + bbt_ruler->set_outline_color (text); - meter_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_MeterBar.get()); - meter_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + playhead_cursor->set_color (UIConfiguration::instance().color ("play head")); - tempo_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_TempoBar.get()); - tempo_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + meter_bar->set_fill_color (UIConfiguration::instance().color_mod ("meter bar", "marker bar")); + meter_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - marker_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_MarkerBar.get()); - marker_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + tempo_bar->set_fill_color (UIConfiguration::instance().color_mod ("tempo bar", "marker bar")); + tempo_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - cd_marker_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_CDMarkerBar.get()); - cd_marker_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("marker bar", "marker bar")); + marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - range_marker_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_RangeMarkerBar.get()); - range_marker_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + cd_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("cd marker bar", "marker bar")); + cd_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - transport_marker_bar->set_fill_color (ARDOUR_UI::config()->canvasvar_TransportMarkerBar.get()); - transport_marker_bar->set_outline_color (ARDOUR_UI::config()->canvasvar_MarkerBarSeparator.get()); + range_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("range marker bar", "marker bar")); + range_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - cd_marker_bar_drag_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_RangeDragBarRect.get()); - cd_marker_bar_drag_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_RangeDragBarRect.get()); + transport_marker_bar->set_fill_color (UIConfiguration::instance().color_mod ("transport marker bar", "marker bar")); + transport_marker_bar->set_outline_color (UIConfiguration::instance().color ("marker bar separator")); - range_bar_drag_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_RangeDragBarRect.get()); - range_bar_drag_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_RangeDragBarRect.get()); + cd_marker_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect")); + cd_marker_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect")); - transport_bar_drag_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_TransportDragRect.get()); - transport_bar_drag_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_TransportDragRect.get()); + range_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("range drag bar rect")); + range_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("range drag bar rect")); - transport_loop_range_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_TransportLoopRect.get()); - transport_loop_range_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_TransportLoopRect.get()); + transport_bar_drag_rect->set_fill_color (UIConfiguration::instance().color ("transport drag rect")); + transport_bar_drag_rect->set_outline_color (UIConfiguration::instance().color ("transport drag rect")); - transport_punch_range_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_TransportPunchRect.get()); - transport_punch_range_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_TransportPunchRect.get()); + transport_loop_range_rect->set_fill_color (UIConfiguration::instance().color_mod ("transport loop rect", "loop rectangle")); + transport_loop_range_rect->set_outline_color (UIConfiguration::instance().color ("transport loop rect")); - transport_punchin_line->set_outline_color (ARDOUR_UI::config()->canvasvar_PunchLine.get()); - transport_punchout_line->set_outline_color (ARDOUR_UI::config()->canvasvar_PunchLine.get()); + transport_punch_range_rect->set_fill_color (UIConfiguration::instance().color ("transport punch rect")); + transport_punch_range_rect->set_outline_color (UIConfiguration::instance().color ("transport punch rect")); - zoom_rect->set_fill_color (ARDOUR_UI::config()->canvasvar_ZoomRect.get()); - zoom_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_ZoomRect.get()); + transport_punchin_line->set_outline_color (UIConfiguration::instance().color ("punch line")); + transport_punchout_line->set_outline_color (UIConfiguration::instance().color ("punch line")); - rubberband_rect->set_outline_color (ARDOUR_UI::config()->canvasvar_RubberBandRect.get()); - rubberband_rect->set_fill_color ((guint32) ARDOUR_UI::config()->canvasvar_RubberBandRect.get()); + rubberband_rect->set_outline_color (UIConfiguration::instance().color ("rubber band rect")); + rubberband_rect->set_fill_color (UIConfiguration::instance().color_mod ("rubber band rect", "selection rect")); - location_marker_color = ARDOUR_UI::config()->canvasvar_LocationMarker.get(); - location_range_color = ARDOUR_UI::config()->canvasvar_LocationRange.get(); - location_cd_marker_color = ARDOUR_UI::config()->canvasvar_LocationCDMarker.get(); - location_loop_color = ARDOUR_UI::config()->canvasvar_LocationLoop.get(); - location_punch_color = ARDOUR_UI::config()->canvasvar_LocationPunch.get(); + location_marker_color = UIConfiguration::instance().color ("location marker"); + location_range_color = UIConfiguration::instance().color ("location range"); + location_cd_marker_color = UIConfiguration::instance().color ("location cd marker"); + location_loop_color = UIConfiguration::instance().color ("location loop"); + location_punch_color = UIConfiguration::instance().color ("location punch"); refresh_location_display (); + + /* redraw the whole thing */ + _track_canvas->set_background_color (UIConfiguration::instance().color ("arrange base")); + _track_canvas->queue_draw (); + /* redisplay_tempo (true); @@ -822,38 +961,15 @@ Editor::horizontal_position () const return sample_to_pixel (leftmost_frame); } -void -Editor::set_canvas_cursor (Gdk::Cursor* cursor, bool save) -{ - if (save) { - current_canvas_cursor = cursor; - } - - Glib::RefPtr win = _track_canvas->get_window(); - - if (win) { - _track_canvas->get_window()->set_cursor (*cursor); - } -} - bool Editor::track_canvas_key_press (GdkEventKey*) { - /* XXX: event does not report the modifier key pressed down, AFAICS, so use the Keyboard object instead */ - if (mouse_mode == Editing::MouseZoom && Keyboard::the_keyboard().key_is_down (GDK_Control_L)) { - set_canvas_cursor (_cursors->zoom_out, true); - } - return false; } bool Editor::track_canvas_key_release (GdkEventKey*) { - if (mouse_mode == Editing::MouseZoom && !Keyboard::the_keyboard().key_is_down (GDK_Control_L)) { - set_canvas_cursor (_cursors->zoom_in, true); - } - return false; } @@ -876,26 +992,389 @@ Editor::clamp_verbose_cursor_y (double y) return y; } -ArdourCanvas::Group* -Editor::get_time_bars_group () const +ArdourCanvas::GtkCanvasViewport* +Editor::get_track_canvas() const { - return _time_bars_canvas->root(); + return _track_canvas_viewport; } -ArdourCanvas::Group* -Editor::get_track_canvas_group() const +Gdk::Cursor* +Editor::get_canvas_cursor () const { - return _track_canvas->root(); + /* The top of the cursor stack is always the currently visible cursor. */ + return _cursor_stack.back(); } -ArdourCanvas::GtkCanvasViewport* -Editor::get_time_bars_canvas() const +void +Editor::set_canvas_cursor (Gdk::Cursor* cursor) +{ + Glib::RefPtr win = _track_canvas->get_window(); + + if (win && !_cursors->is_invalid (cursor)) { + /* glibmm 2.4 doesn't allow null cursor pointer because it uses + a Gdk::Cursor& as the argument to Gdk::Window::set_cursor(). + But a null pointer just means "use parent window cursor", + and so should be allowed. Gtkmm 3.x has fixed this API. + + For now, drop down and use C API + */ + gdk_window_set_cursor (win->gobj(), cursor ? cursor->gobj() : 0); + } +} + +size_t +Editor::push_canvas_cursor (Gdk::Cursor* cursor) { - return _time_bars_canvas_viewport; + if (!_cursors->is_invalid (cursor)) { + _cursor_stack.push_back (cursor); + set_canvas_cursor (cursor); + } + return _cursor_stack.size() - 1; } -ArdourCanvas::GtkCanvasViewport* -Editor::get_track_canvas() const +void +Editor::pop_canvas_cursor () { - return _track_canvas_viewport; + while (true) { + if (_cursor_stack.size() <= 1) { + PBD::error << "attempt to pop default cursor" << endmsg; + return; + } + + _cursor_stack.pop_back(); + if (_cursor_stack.back()) { + /* Popped to an existing cursor, we're done. Otherwise, the + context that created this cursor has been destroyed, so we need + to skip to the next down the stack. */ + set_canvas_cursor (_cursor_stack.back()); + return; + } + } +} + +Gdk::Cursor* +Editor::which_grabber_cursor () const +{ + Gdk::Cursor* c = _cursors->grabber; + + switch (_edit_point) { + case EditAtMouse: + c = _cursors->grabber_edit_point; + break; + default: + boost::shared_ptr m = _movable.lock(); + if (m && m->locked()) { + c = _cursors->speaker; + } + break; + } + + return c; +} + +Gdk::Cursor* +Editor::which_trim_cursor (bool left) const +{ + if (!entered_regionview) { + return 0; + } + + Trimmable::CanTrim ct = entered_regionview->region()->can_trim (); + + if (left) { + + if (ct & Trimmable::FrontTrimEarlier) { + return _cursors->left_side_trim; + } else { + return _cursors->left_side_trim_right_only; + } + } else { + if (ct & Trimmable::EndTrimLater) { + return _cursors->right_side_trim; + } else { + return _cursors->right_side_trim_left_only; + } + } +} + +Gdk::Cursor* +Editor::which_mode_cursor () const +{ + Gdk::Cursor* mode_cursor = MouseCursors::invalid_cursor (); + + switch (mouse_mode) { + case MouseRange: + mode_cursor = _cursors->selector; + break; + + case MouseCut: + mode_cursor = _cursors->scissors; + break; + + case MouseObject: + case MouseContent: + /* don't use mode cursor, pick a grabber cursor based on the item */ + break; + + case MouseDraw: + mode_cursor = _cursors->midi_pencil; + break; + + case MouseTimeFX: + mode_cursor = _cursors->time_fx; // just use playhead + break; + + case MouseAudition: + mode_cursor = _cursors->speaker; + break; + } + + /* up-down cursor as a cue that automation can be dragged up and down when in join object/range mode */ + if (get_smart_mode()) { + + double x, y; + get_pointer_position (x, y); + + if (x >= 0 && y >= 0) { + + vector items; + + /* Note how we choose a specific scroll group to get + * items from. This could be problematic. + */ + + hv_scroll_group->add_items_at_point (ArdourCanvas::Duple (x,y), items); + + // first item will be the upper most + + if (!items.empty()) { + const ArdourCanvas::Item* i = items.front(); + + if (i && i->parent() && i->parent()->get_data (X_("timeselection"))) { + pair tvp = trackview_by_y_position (_last_motion_y); + if (dynamic_cast (tvp.first)) { + mode_cursor = _cursors->up_down; + } + } + } + } + } + + return mode_cursor; +} + +Gdk::Cursor* +Editor::which_track_cursor () const +{ + Gdk::Cursor* cursor = MouseCursors::invalid_cursor(); + + switch (_join_object_range_state) { + case JOIN_OBJECT_RANGE_NONE: + case JOIN_OBJECT_RANGE_OBJECT: + cursor = which_grabber_cursor (); + break; + case JOIN_OBJECT_RANGE_RANGE: + cursor = _cursors->selector; + break; + } + + return cursor; +} + +Gdk::Cursor* +Editor::which_canvas_cursor(ItemType type) const +{ + Gdk::Cursor* cursor = which_mode_cursor (); + + if (mouse_mode == MouseRange) { + switch (type) { + case StartSelectionTrimItem: + cursor = _cursors->left_side_trim; + break; + case EndSelectionTrimItem: + cursor = _cursors->right_side_trim; + break; + default: + break; + } + } + + if ((mouse_mode == MouseObject || get_smart_mode ()) || + mouse_mode == MouseContent) { + + /* find correct cursor to use in object/smart mode */ + + switch (type) { + case RegionItem: + /* We don't choose a cursor for these items on top of a region view, + because this would push a new context on the enter stack which + means switching the region context for things like smart mode + won't actualy change the cursor. */ + // case RegionViewNameHighlight: + // case RegionViewName: + // case WaveItem: + case StreamItem: + case AutomationTrackItem: + cursor = which_track_cursor (); + break; + case PlayheadCursorItem: + switch (_edit_point) { + case EditAtMouse: + cursor = _cursors->grabber_edit_point; + break; + default: + cursor = _cursors->grabber; + break; + } + break; + case SelectionItem: + cursor = _cursors->selector; + break; + case ControlPointItem: + cursor = _cursors->fader; + break; + case GainLineItem: + cursor = _cursors->cross_hair; + break; + case AutomationLineItem: + cursor = _cursors->cross_hair; + break; + case StartSelectionTrimItem: + cursor = _cursors->left_side_trim; + break; + case EndSelectionTrimItem: + cursor = _cursors->right_side_trim; + break; + case FadeInItem: + cursor = _cursors->fade_in; + break; + case FadeInHandleItem: + cursor = _cursors->fade_in; + break; + case FadeInTrimHandleItem: + cursor = _cursors->fade_in; + break; + case FadeOutItem: + cursor = _cursors->fade_out; + break; + case FadeOutHandleItem: + cursor = _cursors->fade_out; + break; + case FadeOutTrimHandleItem: + cursor = _cursors->fade_out; + break; + case FeatureLineItem: + cursor = _cursors->cross_hair; + break; + case LeftFrameHandle: + if ( effective_mouse_mode() == MouseObject ) // (smart mode): if the user is in the btm half, show the trim cursor + cursor = which_trim_cursor (true); + else + cursor = _cursors->selector; // (smart mode): in the top half, just show the selection (range) cursor + break; + case RightFrameHandle: + if ( effective_mouse_mode() == MouseObject ) //see above + cursor = which_trim_cursor (false); + else + cursor = _cursors->selector; + break; + case StartCrossFadeItem: + cursor = _cursors->fade_in; + break; + case EndCrossFadeItem: + cursor = _cursors->fade_out; + break; + case CrossfadeViewItem: + cursor = _cursors->cross_hair; + break; + case NoteItem: + cursor = _cursors->grabber_note; + default: + break; + } + + } else if (mouse_mode == MouseDraw) { + + /* ControlPointItem is not really specific to region gain mode + but it is the same cursor so don't worry about this for now. + The result is that we'll see the fader cursor if we enter + non-region-gain-line control points while in MouseDraw + mode, even though we can't edit them in this mode. + */ + + switch (type) { + case GainLineItem: + case ControlPointItem: + cursor = _cursors->fader; + break; + case NoteItem: + cursor = _cursors->grabber_note; + default: + break; + } + } + + switch (type) { + /* These items use the timebar cursor at all times */ + case TimecodeRulerItem: + case MinsecRulerItem: + case BBTRulerItem: + case SamplesRulerItem: + cursor = _cursors->timebar; + break; + + /* These items use the grabber cursor at all times */ + case MeterMarkerItem: + case TempoMarkerItem: + case MeterBarItem: + case TempoBarItem: + case MarkerItem: + case MarkerBarItem: + case RangeMarkerBarItem: + case CdMarkerBarItem: + case VideoBarItem: + case TransportMarkerBarItem: + case DropZoneItem: + cursor = which_grabber_cursor(); + break; + + default: + break; + } + + return cursor; +} + +void +Editor::choose_canvas_cursor_on_entry (ItemType type) +{ + if (_drags->active()) { + return; + } + + Gdk::Cursor* cursor = which_canvas_cursor(type); + + if (!_cursors->is_invalid (cursor)) { + // Push a new enter context + const EnterContext ctx = { type, CursorContext::create(*this, cursor) }; + _enter_stack.push_back(ctx); + } +} + +void +Editor::update_all_enter_cursors () +{ + for (std::vector::iterator i = _enter_stack.begin(); i != _enter_stack.end(); ++i) { + i->cursor_ctx->change(which_canvas_cursor(i->item_type)); + } +} + +double +Editor::trackviews_height() const +{ + if (!_trackview_group) { + return 0; + } + + return _visible_canvas_height - _trackview_group->canvas_origin().y; }