+void
+Editor::playhead_forward_to_grid ()
+{
+ if (!session) return;
+ nframes64_t pos = playhead_cursor->current_frame;
+ if (pos < max_frames - 1) {
+ pos += 2;
+ snap_to_internal (pos, 1, false);
+ session->request_locate (pos);
+ }
+}
+
+
+void
+Editor::playhead_backward_to_grid ()
+{
+ if (!session) return;
+ nframes64_t pos = playhead_cursor->current_frame;
+ if (pos > 2) {
+ pos -= 2;
+ snap_to_internal (pos, -1, false);
+ session->request_locate (pos);
+ }
+}
+
+void
+Editor::set_track_height (uint32_t h)
+{
+ TrackSelection& ts (selection->tracks);
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ (*x)->set_height (h);
+ }
+}
+
+void
+Editor::toggle_tracks_active ()
+{
+ TrackSelection& ts (selection->tracks);
+ bool first = true;
+ bool target = false;
+
+ if (ts.empty()) {
+ return;
+ }
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*>(*x);
+
+ if (rtv) {
+ if (first) {
+ target = !rtv->_route->active();
+ first = false;
+ }
+ rtv->_route->set_active (target);
+ }
+ }
+}
+
+void
+Editor::remove_tracks ()
+{
+ TrackSelection& ts (selection->tracks);
+
+ if (ts.empty()) {
+ return;
+ }
+
+ vector<string> choices;
+ string prompt;
+ int ntracks = 0;
+ int nbusses = 0;
+ const char* trackstr;
+ const char* busstr;
+ vector<boost::shared_ptr<Route> > routes;
+
+ for (TrackSelection::iterator x = ts.begin(); x != ts.end(); ++x) {
+ RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (rtv) {
+ if (rtv->is_track()) {
+ ntracks++;
+ } else {
+ nbusses++;
+ }
+ }
+ routes.push_back (rtv->_route);
+ }
+
+ if (ntracks + nbusses == 0) {
+ return;
+ }
+
+ if (ntracks > 1) {
+ trackstr = _("tracks");
+ } else {
+ trackstr = _("track");
+ }
+
+ if (nbusses > 1) {
+ busstr = _("busses");
+ } else {
+ busstr = _("bus");
+ }
+
+ if (ntracks) {
+ if (nbusses) {
+ prompt = string_compose (_("Do you really want to remove %1 %2 and %3 %4?\n"
+ "(You may also lose the playlists associated with the %2)\n\n"
+ "This action cannot be undone!"),
+ ntracks, trackstr, nbusses, busstr);
+ } else {
+ prompt = string_compose (_("Do you really want to remove %1 %2?\n"
+ "(You may also lose the playlists associated with the %2)\n\n"
+ "This action cannot be undone!"),
+ ntracks, trackstr);
+ }
+ } else if (nbusses) {
+ prompt = string_compose (_("Do you really want to remove %1 %2?"),
+ nbusses, busstr);
+ }
+
+ choices.push_back (_("No, do nothing."));
+ if (ntracks + nbusses > 1) {
+ choices.push_back (_("Yes, remove them."));
+ } else {
+ choices.push_back (_("Yes, remove it."));
+ }
+
+ Choice prompter (prompt, choices);
+
+ if (prompter.run () != 1) {
+ return;
+ }
+
+ for (vector<boost::shared_ptr<Route> >::iterator x = routes.begin(); x != routes.end(); ++x) {
+ session->remove_route (*x);
+ }
+}
+
+void
+Editor::do_insert_time ()
+{
+ if (selection->tracks.empty()) {
+ return;
+ }
+
+ ArdourDialog d (*this, _("Insert Time"));
+
+ nframes64_t const pos = get_preferred_edit_position ();
+
+ d.get_vbox()->set_border_width (12);
+ d.get_vbox()->set_spacing (4);
+
+ Table table (2, 2);
+ table.set_spacings (4);
+
+ Label time_label (_("Time to insert:"));
+ time_label.set_alignment (1, 0.5);
+ table.attach (time_label, 0, 1, 0, 1, FILL | EXPAND);
+ AudioClock clock ("insertTimeClock", true, X_("InsertTimeClock"), true, true, true);
+ clock.set (0);
+ clock.set_session (session);
+ clock.set_bbt_reference (pos);
+ table.attach (clock, 1, 2, 0, 1);
+
+ Label intersected_label (_("Intersected regions should:"));
+ intersected_label.set_alignment (1, 0.5);
+ table.attach (intersected_label, 0, 1, 1, 2, FILL | EXPAND);
+ ComboBoxText intersected_combo;
+ intersected_combo.append_text (_("stay in position"));
+ intersected_combo.append_text (_("move"));
+ intersected_combo.append_text (_("be split"));
+ intersected_combo.set_active (0);
+ table.attach (intersected_combo, 1, 2, 1, 2);
+
+ d.get_vbox()->pack_start (table);
+
+ CheckButton move_glued (_("Move glued regions"));
+ d.get_vbox()->pack_start (move_glued);
+ CheckButton move_markers (_("Move markers"));
+ d.get_vbox()->pack_start (move_markers);
+ CheckButton move_tempos (_("Move tempo and meter changes"));
+ d.get_vbox()->pack_start (move_tempos);
+
+ d.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+ d.add_button (_("Insert time"), Gtk::RESPONSE_OK);
+ d.show_all ();
+
+ int response = d.run ();
+
+ if (response != RESPONSE_OK) {
+ return;
+ }
+
+ nframes64_t distance = clock.current_duration (pos);
+
+ if (distance == 0) {
+ return;
+ }
+
+ InsertTimeOption opt;
+
+ switch (intersected_combo.get_active_row_number ()) {
+ case 0:
+ opt = LeaveIntersected;
+ break;
+ case 1:
+ opt = MoveIntersected;
+ break;
+ case 2:
+ opt = SplitIntersected;
+ break;
+ }
+
+ insert_time (pos, distance, opt, move_glued.get_active(), move_markers.get_active(), move_tempos.get_active());
+}
+
+void
+Editor::insert_time (nframes64_t pos, nframes64_t frames, InsertTimeOption opt,
+ bool ignore_music_glue, bool markers_too, bool tempo_too)
+{
+ bool commit = false;
+
+ if (Config->get_edit_mode() == Lock) {
+ return;
+ }
+
+ begin_reversible_command (_("insert time"));
+
+ for (TrackSelection::iterator x = selection->tracks.begin(); x != selection->tracks.end(); ++x) {
+ /* regions */
+ boost::shared_ptr<Playlist> pl = (*x)->playlist();
+
+ if (pl) {
+
+ XMLNode &before = pl->get_state();
+
+ if (opt == SplitIntersected) {
+ pl->split (pos);
+ }
+
+ pl->shift (pos, frames, (opt == MoveIntersected), ignore_music_glue);
+
+ XMLNode &after = pl->get_state();
+
+ session->add_command (new MementoCommand<Playlist> (*pl, &before, &after));
+ commit = true;
+ }
+
+ /* automation */
+ RouteTimeAxisView* rtav = dynamic_cast<RouteTimeAxisView*> (*x);
+ if (rtav) {
+ rtav->route ()->shift (pos, frames);
+ commit = true;
+ }
+ }
+
+ /* markers */
+ if (markers_too) {
+ bool moved = false;
+ XMLNode& before (session->locations()->get_state());
+ Locations::LocationList copy (session->locations()->list());
+
+ for (Locations::LocationList::iterator i = copy.begin(); i != copy.end(); ++i) {
+
+ Locations::LocationList::const_iterator tmp;
+
+ if ((*i)->start() >= pos) {
+ (*i)->set_start ((*i)->start() + frames);
+ if (!(*i)->is_mark()) {
+ (*i)->set_end ((*i)->end() + frames);
+ }
+ moved = true;
+ }
+ }
+
+ if (moved) {
+ XMLNode& after (session->locations()->get_state());
+ session->add_command (new MementoCommand<Locations>(*session->locations(), &before, &after));
+ }
+ }
+
+ if (tempo_too) {
+ session->tempo_map().insert_time (pos, frames);
+ }
+
+ if (commit) {
+ commit_reversible_command ();
+ }
+}
+
+void
+Editor::fit_selected_tracks ()
+{
+ fit_tracks (selection->tracks);
+}
+
+void
+Editor::fit_tracks (TrackSelection & tracks)
+{
+ if (tracks.empty()) {
+ return;
+ }
+
+ uint32_t child_heights = 0;
+
+ for (TrackSelection::iterator t = tracks.begin(); t != tracks.end(); ++t) {
+
+ if (!(*t)->marked_for_display()) {
+ continue;
+ }
+
+ child_heights += (*t)->effective_height() - (*t)->current_height();
+ }
+
+ uint32_t h = (uint32_t) floor ((_canvas_height - child_heights - canvas_timebars_vsize) / tracks.size());
+ double first_y_pos = DBL_MAX;
+
+ if (h < TimeAxisView::hSmall) {
+ MessageDialog msg (*this, _("There are too many tracks to fit in the current window"));
+ /* too small to be displayed */
+ return;
+ }
+
+ undo_visual_stack.push_back (current_visual_state());
+
+ /* operate on all tracks, hide unselected ones that are in the middle of selected ones */
+
+ bool prev_was_selected = false;
+ bool is_selected = tracks.contains (track_views.front());
+ bool next_is_selected;
+
+ for (TrackViewList::iterator t = track_views.begin(); t != track_views.end(); ++t) {
+
+ TrackViewList::iterator next;
+
+ next = t;
+ ++next;
+
+ if (next != track_views.end()) {
+ next_is_selected = tracks.contains (*next);
+ } else {
+ next_is_selected = false;
+ }
+
+ if (is_selected) {
+ (*t)->set_height (h);
+ first_y_pos = std::min ((*t)->y_position (), first_y_pos);
+ } else {
+ if (prev_was_selected && next_is_selected) {
+ hide_track_in_display (**t);
+ }
+ }
+
+ prev_was_selected = is_selected;
+ is_selected = next_is_selected;
+ }
+
+ /*
+ set the controls_layout height now, because waiting for its size
+ request signal handler will cause the vertical adjustment setting to fail
+ */
+
+ controls_layout.property_height () = full_canvas_height - canvas_timebars_vsize;
+ vertical_adjustment.set_value (first_y_pos);
+
+ redo_visual_stack.push_back (current_visual_state());
+}
+
+void
+Editor::save_visual_state (uint32_t n)
+{
+ while (visual_states.size() <= n) {
+ visual_states.push_back (0);
+ }
+
+ delete visual_states[n];
+
+ visual_states[n] = current_visual_state (true);
+ gdk_beep ();
+}
+
+void
+Editor::goto_visual_state (uint32_t n)
+{
+ if (visual_states.size() <= n) {
+ return;
+ }
+
+ if (visual_states[n] == 0) {
+ return;
+ }
+
+ use_visual_state (*visual_states[n]);
+}
+
+void
+Editor::start_visual_state_op (uint32_t n)
+{
+ cerr << "Start visual op\n";
+ if (visual_state_op_connection.empty()) {
+ visual_state_op_connection = Glib::signal_timeout().connect (bind (mem_fun (*this, &Editor::end_visual_state_op), n), 1000);
+ cerr << "\tqueued new timeout\n";
+ }
+}
+
+void
+Editor::cancel_visual_state_op (uint32_t n)
+{
+ if (!visual_state_op_connection.empty()) {
+ cerr << "cancel visual op, time to goto\n";
+ visual_state_op_connection.disconnect();
+ goto_visual_state (n);
+ } else {
+ cerr << "cancel visual op, do nothing\n";
+ }
+}
+
+bool
+Editor::end_visual_state_op (uint32_t n)
+{
+ visual_state_op_connection.disconnect();
+ save_visual_state (n);
+
+ PopUp* pup = new PopUp (WIN_POS_MOUSE, 1000, true);
+ char buf[32];
+ snprintf (buf, sizeof (buf), _("Saved view %u"), n+1);
+ pup->set_text (buf);
+ pup->touch();
+
+ return false; // do not call again
+}
+