2 Copyright (C) 2000-2009 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 #include "pbd/unknown_type.h"
28 #include "pbd/unwind.h"
30 #include "ardour/debug.h"
31 #include "ardour/route.h"
32 #include "ardour/midi_track.h"
33 #include "ardour/session.h"
35 #include "gtkmm2ext/cell_renderer_pixbuf_multi.h"
36 #include "gtkmm2ext/cell_renderer_pixbuf_toggle.h"
37 #include "gtkmm2ext/treeutils.h"
41 #include "ardour_ui.h"
42 #include "audio_time_axis.h"
43 #include "midi_time_axis.h"
44 #include "mixer_strip.h"
45 #include "gui_thread.h"
48 #include "route_sorter.h"
49 #include "editor_group_tabs.h"
50 #include "editor_routes.h"
55 using namespace ARDOUR;
58 using namespace Gtkmm2ext;
60 using Gtkmm2ext::Keyboard;
68 EditorRoutes::EditorRoutes (Editor* e)
70 , _ignore_reorder (false)
71 , _no_redisplay (false)
72 , _adding_routes (false)
75 , selection_countdown (0)
78 static const int column_width = 22;
80 _scroller.add (_display);
81 _scroller.set_policy (POLICY_NEVER, POLICY_AUTOMATIC);
83 _model = ListStore::create (_columns);
84 _display.set_model (_model);
86 // Record enable toggle
87 CellRendererPixbufMulti* rec_col_renderer = manage (new CellRendererPixbufMulti());
89 rec_col_renderer->set_pixbuf (0, ::get_icon("record-normal-disabled"));
90 rec_col_renderer->set_pixbuf (1, ::get_icon("record-normal-in-progress"));
91 rec_col_renderer->set_pixbuf (2, ::get_icon("record-normal-enabled"));
92 rec_col_renderer->set_pixbuf (3, ::get_icon("record-step"));
93 rec_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_rec_enable_changed));
95 TreeViewColumn* rec_state_column = manage (new TreeViewColumn("R", *rec_col_renderer));
97 rec_state_column->add_attribute(rec_col_renderer->property_state(), _columns.rec_state);
98 rec_state_column->add_attribute(rec_col_renderer->property_visible(), _columns.is_track);
100 rec_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
101 rec_state_column->set_alignment(ALIGN_CENTER);
102 rec_state_column->set_expand(false);
103 rec_state_column->set_fixed_width(column_width);
107 CellRendererPixbufMulti* input_active_col_renderer = manage (new CellRendererPixbufMulti());
108 input_active_col_renderer->set_pixbuf (0, ::get_icon("midi-input-inactive"));
109 input_active_col_renderer->set_pixbuf (1, ::get_icon("midi-input-active"));
110 input_active_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_input_active_changed));
112 TreeViewColumn* input_active_column = manage (new TreeViewColumn ("I", *input_active_col_renderer));
114 input_active_column->add_attribute(input_active_col_renderer->property_state(), _columns.is_input_active);
115 input_active_column->add_attribute (input_active_col_renderer->property_visible(), _columns.is_midi);
117 input_active_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
118 input_active_column->set_alignment(ALIGN_CENTER);
119 input_active_column->set_expand(false);
120 input_active_column->set_fixed_width(column_width);
122 // Mute enable toggle
123 CellRendererPixbufMulti* mute_col_renderer = manage (new CellRendererPixbufMulti());
125 mute_col_renderer->set_pixbuf (Gtkmm2ext::Off, ::get_icon("mute-disabled"));
126 mute_col_renderer->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon("muted-by-others"));
127 mute_col_renderer->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon("mute-enabled"));
128 mute_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_mute_enable_toggled));
130 TreeViewColumn* mute_state_column = manage (new TreeViewColumn("M", *mute_col_renderer));
132 mute_state_column->add_attribute(mute_col_renderer->property_state(), _columns.mute_state);
133 mute_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
134 mute_state_column->set_alignment(ALIGN_CENTER);
135 mute_state_column->set_expand(false);
136 mute_state_column->set_fixed_width(15);
138 // Solo enable toggle
139 CellRendererPixbufMulti* solo_col_renderer = manage (new CellRendererPixbufMulti());
141 solo_col_renderer->set_pixbuf (Gtkmm2ext::Off, ::get_icon("solo-disabled"));
142 solo_col_renderer->set_pixbuf (Gtkmm2ext::ExplicitActive, ::get_icon("solo-enabled"));
143 solo_col_renderer->set_pixbuf (Gtkmm2ext::ImplicitActive, ::get_icon("soloed-by-others"));
144 solo_col_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_enable_toggled));
146 TreeViewColumn* solo_state_column = manage (new TreeViewColumn("S", *solo_col_renderer));
148 solo_state_column->add_attribute(solo_col_renderer->property_state(), _columns.solo_state);
149 solo_state_column->add_attribute(solo_col_renderer->property_visible(), _columns.solo_visible);
150 solo_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
151 solo_state_column->set_alignment(ALIGN_CENTER);
152 solo_state_column->set_expand(false);
153 solo_state_column->set_fixed_width(column_width);
155 // Solo isolate toggle
156 CellRendererPixbufMulti* solo_iso_renderer = manage (new CellRendererPixbufMulti());
158 solo_iso_renderer->set_pixbuf (0, ::get_icon("solo-isolate-disabled"));
159 solo_iso_renderer->set_pixbuf (1, ::get_icon("solo-isolate-enabled"));
160 solo_iso_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_isolate_toggled));
162 TreeViewColumn* solo_isolate_state_column = manage (new TreeViewColumn("SI", *solo_iso_renderer));
164 solo_isolate_state_column->add_attribute(solo_iso_renderer->property_state(), _columns.solo_isolate_state);
165 solo_isolate_state_column->add_attribute(solo_iso_renderer->property_visible(), _columns.solo_visible);
166 solo_isolate_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
167 solo_isolate_state_column->set_alignment(ALIGN_CENTER);
168 solo_isolate_state_column->set_expand(false);
169 solo_isolate_state_column->set_fixed_width(column_width);
172 CellRendererPixbufMulti* solo_safe_renderer = manage (new CellRendererPixbufMulti ());
174 solo_safe_renderer->set_pixbuf (0, ::get_icon("solo-safe-disabled"));
175 solo_safe_renderer->set_pixbuf (1, ::get_icon("solo-safe-enabled"));
176 solo_safe_renderer->signal_changed().connect (sigc::mem_fun (*this, &EditorRoutes::on_tv_solo_safe_toggled));
178 TreeViewColumn* solo_safe_state_column = manage (new TreeViewColumn(_("SS"), *solo_safe_renderer));
179 solo_safe_state_column->add_attribute(solo_safe_renderer->property_state(), _columns.solo_safe_state);
180 solo_safe_state_column->add_attribute(solo_safe_renderer->property_visible(), _columns.solo_visible);
181 solo_safe_state_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
182 solo_safe_state_column->set_alignment(ALIGN_CENTER);
183 solo_safe_state_column->set_expand(false);
184 solo_safe_state_column->set_fixed_width(column_width);
186 _name_column = _display.append_column ("", _columns.text) - 1;
187 _visible_column = _display.append_column ("", _columns.visible) - 1;
188 _active_column = _display.append_column ("", _columns.active) - 1;
190 _display.append_column (*input_active_column);
191 _display.append_column (*rec_state_column);
192 _display.append_column (*mute_state_column);
193 _display.append_column (*solo_state_column);
194 _display.append_column (*solo_isolate_state_column);
195 _display.append_column (*solo_safe_state_column);
202 { 0, _("Name"), _("Track/Bus Name") },
203 { 1, _("V"), _("Track/Bus visible ?") },
204 { 2, _("A"), _("Track/Bus active ?") },
205 { 3, _("I"), _("MIDI input enabled") },
206 { 4, _("R"), _("Record enabled") },
207 { 5, _("M"), _("Muted") },
208 { 6, _("S"), _("Soloed") },
209 { 7, _("SI"), _("Solo Isolated") },
210 { 8, _("SS"), _("Solo Safe (Locked)") },
214 for (int i = 0; ci[i].index >= 0; ++i) {
215 col = _display.get_column (ci[i].index);
216 l = manage (new Label (ci[i].label));
217 ARDOUR_UI::instance()->set_tip (*l, ci[i].tooltip);
218 col->set_widget (*l);
222 _display.set_headers_visible (true);
223 _display.get_selection()->set_mode (SELECTION_SINGLE);
224 _display.get_selection()->set_select_function (sigc::mem_fun (*this, &EditorRoutes::selection_filter));
225 _display.set_reorderable (true);
226 _display.set_name (X_("EditGroupList"));
227 _display.set_rules_hint (true);
228 _display.set_size_request (100, -1);
229 _display.add_object_drag (_columns.route.index(), "routes");
231 CellRendererText* name_cell = dynamic_cast<CellRendererText*> (_display.get_column_cell_renderer (_name_column));
234 name_cell->signal_editing_started().connect (sigc::mem_fun (*this, &EditorRoutes::name_edit_started));
236 TreeViewColumn* name_column = _display.get_column (_name_column);
238 assert (name_column);
240 name_column->add_attribute (name_cell->property_editable(), _columns.name_editable);
241 name_column->set_sizing(TREE_VIEW_COLUMN_FIXED);
242 name_column->set_expand(true);
243 name_column->set_min_width(50);
245 name_cell->property_editable() = true;
246 name_cell->signal_edited().connect (sigc::mem_fun (*this, &EditorRoutes::name_edit));
248 // Set the visible column cell renderer to radio toggle
249 CellRendererToggle* visible_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (_visible_column));
251 visible_cell->property_activatable() = true;
252 visible_cell->property_radio() = false;
253 visible_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRoutes::visible_changed));
255 TreeViewColumn* visible_col = dynamic_cast<TreeViewColumn*> (_display.get_column (_visible_column));
256 visible_col->set_expand(false);
257 visible_col->set_sizing(TREE_VIEW_COLUMN_FIXED);
258 visible_col->set_fixed_width(30);
259 visible_col->set_alignment(ALIGN_CENTER);
261 CellRendererToggle* active_cell = dynamic_cast<CellRendererToggle*> (_display.get_column_cell_renderer (_active_column));
263 active_cell->property_activatable() = true;
264 active_cell->property_radio() = false;
265 active_cell->signal_toggled().connect (sigc::mem_fun (*this, &EditorRoutes::active_changed));
267 TreeViewColumn* active_col = dynamic_cast<TreeViewColumn*> (_display.get_column (_active_column));
268 active_col->set_expand (false);
269 active_col->set_sizing (TREE_VIEW_COLUMN_FIXED);
270 active_col->set_fixed_width (30);
271 active_col->set_alignment (ALIGN_CENTER);
273 _model->signal_row_deleted().connect (sigc::mem_fun (*this, &EditorRoutes::route_deleted));
274 _model->signal_rows_reordered().connect (sigc::mem_fun (*this, &EditorRoutes::reordered));
276 _display.signal_button_press_event().connect (sigc::mem_fun (*this, &EditorRoutes::button_press), false);
277 _scroller.signal_key_press_event().connect (sigc::mem_fun(*this, &EditorRoutes::key_press), false);
279 _scroller.signal_focus_in_event().connect (sigc::mem_fun (*this, &EditorRoutes::focus_in), false);
280 _scroller.signal_focus_out_event().connect (sigc::mem_fun (*this, &EditorRoutes::focus_out));
282 _display.signal_enter_notify_event().connect (sigc::mem_fun (*this, &EditorRoutes::enter_notify), false);
283 _display.signal_leave_notify_event().connect (sigc::mem_fun (*this, &EditorRoutes::leave_notify), false);
285 _display.set_enable_search (false);
287 Route::SyncOrderKeys.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::sync_treeview_from_order_keys, this, _1), gui_context());
291 EditorRoutes::focus_in (GdkEventFocus*)
293 Window* win = dynamic_cast<Window*> (_scroller.get_toplevel ());
296 old_focus = win->get_focus ();
303 /* try to do nothing on focus in (doesn't work, hence selection_count nonsense) */
308 EditorRoutes::focus_out (GdkEventFocus*)
311 old_focus->grab_focus ();
319 EditorRoutes::enter_notify (GdkEventCrossing*)
325 /* arm counter so that ::selection_filter() will deny selecting anything for the
326 next two attempts to change selection status.
328 selection_countdown = 2;
329 _scroller.grab_focus ();
330 Keyboard::magic_widget_grab_focus ();
335 EditorRoutes::leave_notify (GdkEventCrossing*)
337 selection_countdown = 0;
340 old_focus->grab_focus ();
344 Keyboard::magic_widget_drop_focus ();
349 EditorRoutes::set_session (Session* s)
351 SessionHandlePtr::set_session (s);
356 _session->SoloChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::solo_changed_so_update_mute, this), gui_context());
357 _session->RecordStateChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context());
362 EditorRoutes::on_input_active_changed (std::string const & path_string)
364 // Get the model row that has been toggled.
365 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
367 TimeAxisView* tv = row[_columns.tv];
368 RouteTimeAxisView *rtv = dynamic_cast<RouteTimeAxisView*> (tv);
371 boost::shared_ptr<MidiTrack> mt;
372 mt = rtv->midi_track();
374 mt->set_input_active (!mt->input_active());
380 EditorRoutes::on_tv_rec_enable_changed (std::string const & path_string)
382 // Get the model row that has been toggled.
383 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
385 TimeAxisView* tv = row[_columns.tv];
386 RouteTimeAxisView *rtv = dynamic_cast<RouteTimeAxisView*> (tv);
388 if (rtv && rtv->track()) {
389 boost::shared_ptr<RouteList> rl (new RouteList);
390 rl->push_back (rtv->route());
391 _session->set_record_enabled (rl, !rtv->track()->record_enabled(), Session::rt_cleanup);
396 EditorRoutes::on_tv_mute_enable_toggled (std::string const & path_string)
398 // Get the model row that has been toggled.
399 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
401 TimeAxisView *tv = row[_columns.tv];
402 RouteTimeAxisView *rtv = dynamic_cast<RouteTimeAxisView*> (tv);
405 boost::shared_ptr<RouteList> rl (new RouteList);
406 rl->push_back (rtv->route());
407 _session->set_mute (rl, !rtv->route()->muted(), Session::rt_cleanup);
412 EditorRoutes::on_tv_solo_enable_toggled (std::string const & path_string)
414 // Get the model row that has been toggled.
415 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
417 TimeAxisView *tv = row[_columns.tv];
418 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
421 boost::shared_ptr<RouteList> rl (new RouteList);
422 rl->push_back (rtv->route());
423 if (Config->get_solo_control_is_listen_control()) {
424 _session->set_listen (rl, !rtv->route()->listening_via_monitor(), Session::rt_cleanup);
426 _session->set_solo (rl, !rtv->route()->self_soloed(), Session::rt_cleanup);
432 EditorRoutes::on_tv_solo_isolate_toggled (std::string const & path_string)
434 // Get the model row that has been toggled.
435 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
437 TimeAxisView *tv = row[_columns.tv];
438 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
441 rtv->route()->set_solo_isolated (!rtv->route()->solo_isolated(), this);
446 EditorRoutes::on_tv_solo_safe_toggled (std::string const & path_string)
448 // Get the model row that has been toggled.
449 Gtk::TreeModel::Row row = *_model->get_iter (Gtk::TreeModel::Path (path_string));
451 TimeAxisView *tv = row[_columns.tv];
452 RouteTimeAxisView* rtv = dynamic_cast<RouteTimeAxisView*> (tv);
455 rtv->route()->set_solo_safe (!rtv->route()->solo_safe(), this);
460 EditorRoutes::build_menu ()
462 using namespace Menu_Helpers;
467 MenuList& items = _menu->items();
468 _menu->set_name ("ArdourContextMenu");
470 items.push_back (MenuElem (_("Show All"), sigc::mem_fun (*this, &EditorRoutes::show_all_routes)));
471 items.push_back (MenuElem (_("Hide All"), sigc::mem_fun (*this, &EditorRoutes::hide_all_routes)));
472 items.push_back (MenuElem (_("Show All Audio Tracks"), sigc::mem_fun (*this, &EditorRoutes::show_all_audiotracks)));
473 items.push_back (MenuElem (_("Hide All Audio Tracks"), sigc::mem_fun (*this, &EditorRoutes::hide_all_audiotracks)));
474 items.push_back (MenuElem (_("Show All Audio Busses"), sigc::mem_fun (*this, &EditorRoutes::show_all_audiobus)));
475 items.push_back (MenuElem (_("Hide All Audio Busses"), sigc::mem_fun (*this, &EditorRoutes::hide_all_audiobus)));
476 items.push_back (MenuElem (_("Show All Midi Tracks"), sigc::mem_fun (*this, &EditorRoutes::show_all_miditracks)));
477 items.push_back (MenuElem (_("Hide All Midi Tracks"), sigc::mem_fun (*this, &EditorRoutes::hide_all_miditracks)));
478 items.push_back (MenuElem (_("Show Tracks With Regions Under Playhead"), sigc::mem_fun (*this, &EditorRoutes::show_tracks_with_regions_at_playhead)));
482 EditorRoutes::show_menu ()
488 _menu->popup (1, gtk_get_current_event_time());
492 EditorRoutes::redisplay ()
494 if (_no_redisplay || !_session || _session->deletion_in_progress()) {
498 TreeModel::Children rows = _model->children();
499 TreeModel::Children::iterator i;
502 /* n will be the count of tracks plus children (updated by TimeAxisView::show_at),
503 so we will use that to know where to put things.
507 for (n = 0, position = 0, i = rows.begin(); i != rows.end(); ++i) {
508 TimeAxisView *tv = (*i)[_columns.tv];
509 boost::shared_ptr<Route> route = (*i)[_columns.route];
512 // just a "title" row
516 bool visible = tv->marked_for_display ();
518 /* show or hide the TimeAxisView */
520 position += tv->show_at (position, n, &_editor->edit_controls_vbox);
529 /* whenever we go idle, update the track view list to reflect the new order.
530 we can't do this here, because we could mess up something that is traversing
531 the track order and has caused a redisplay of the list.
533 Glib::signal_idle().connect (sigc::mem_fun (*_editor, &Editor::sync_track_view_list_and_routes));
535 _editor->reset_controls_layout_height (position);
536 _editor->reset_controls_layout_width ();
537 _editor->_full_canvas_height = position;
539 if ((_editor->vertical_adjustment.get_value() + _editor->_visible_canvas_height) > _editor->vertical_adjustment.get_upper()) {
541 We're increasing the size of the canvas while the bottom is visible.
542 We scroll down to keep in step with the controls layout.
544 _editor->vertical_adjustment.set_value (_editor->_full_canvas_height - _editor->_visible_canvas_height);
549 EditorRoutes::route_deleted (Gtk::TreeModel::Path const &)
551 /* this happens as the second step of a DnD within the treeview as well
552 as when a row/route is actually deleted.
554 DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview row deleted\n");
555 sync_order_keys_from_treeview ();
559 EditorRoutes::reordered (TreeModel::Path const &, TreeModel::iterator const &, int* /*what*/)
561 DEBUG_TRACE (DEBUG::OrderKeys, "editor routes treeview reordered\n");
562 sync_order_keys_from_treeview ();
566 EditorRoutes::visible_changed (std::string const & path)
568 if (_session && _session->deletion_in_progress()) {
574 if ((iter = _model->get_iter (path))) {
575 TimeAxisView* tv = (*iter)[_columns.tv];
577 bool visible = (*iter)[_columns.visible];
579 if (tv->set_marked_for_display (!visible)) {
580 update_visibility ();
587 EditorRoutes::active_changed (std::string const & path)
589 if (_session && _session->deletion_in_progress ()) {
593 Gtk::TreeModel::Row row = *_model->get_iter (path);
594 boost::shared_ptr<Route> route = row[_columns.route];
595 bool const active = row[_columns.active];
596 route->set_active (!active, this);
600 EditorRoutes::routes_added (list<RouteTimeAxisView*> routes)
603 PBD::Unwinder<bool> at (_adding_routes, true);
605 suspend_redisplay ();
607 _display.set_model (Glib::RefPtr<ListStore>());
609 for (list<RouteTimeAxisView*>::iterator x = routes.begin(); x != routes.end(); ++x) {
611 boost::shared_ptr<MidiTrack> midi_trk = boost::dynamic_pointer_cast<MidiTrack> ((*x)->route());
613 row = *(_model->append ());
615 row[_columns.text] = (*x)->route()->name();
616 row[_columns.visible] = (*x)->marked_for_display();
617 row[_columns.active] = (*x)->route()->active ();
618 row[_columns.tv] = *x;
619 row[_columns.route] = (*x)->route ();
620 row[_columns.is_track] = (boost::dynamic_pointer_cast<Track> ((*x)->route()) != 0);
623 row[_columns.is_input_active] = midi_trk->input_active ();
624 row[_columns.is_midi] = true;
626 row[_columns.is_input_active] = false;
627 row[_columns.is_midi] = false;
630 row[_columns.mute_state] = (*x)->route()->muted() ? Gtkmm2ext::ExplicitActive : Gtkmm2ext::Off;
631 row[_columns.solo_state] = RouteUI::solo_active_state ((*x)->route());
632 row[_columns.solo_visible] = !(*x)->route()->is_master ();
633 row[_columns.solo_isolate_state] = (*x)->route()->solo_isolated();
634 row[_columns.solo_safe_state] = (*x)->route()->solo_safe();
635 row[_columns.name_editable] = true;
637 boost::weak_ptr<Route> wr ((*x)->route());
639 (*x)->route()->gui_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::handle_gui_changes, this, _1, _2), gui_context());
640 (*x)->route()->PropertyChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::route_property_changed, this, _1, wr), gui_context());
642 if ((*x)->is_track()) {
643 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> ((*x)->route());
644 t->RecordEnableChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context());
647 if ((*x)->is_midi_track()) {
648 boost::shared_ptr<MidiTrack> t = boost::dynamic_pointer_cast<MidiTrack> ((*x)->route());
649 t->StepEditStatusChange.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_rec_display, this), gui_context());
650 t->InputActiveChanged.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_input_active_display, this), gui_context());
653 (*x)->route()->mute_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_mute_display, this), gui_context());
654 (*x)->route()->solo_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_display, this, _1), gui_context());
655 (*x)->route()->listen_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_display, this, _1), gui_context());
656 (*x)->route()->solo_isolated_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_isolate_display, this), gui_context());
657 (*x)->route()->solo_safe_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_solo_safe_display, this), gui_context());
658 (*x)->route()->active_changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&EditorRoutes::update_active_display, this), gui_context ());
662 update_rec_display ();
663 update_mute_display ();
664 update_solo_display (true);
665 update_solo_isolate_display ();
666 update_solo_safe_display ();
667 update_input_active_display ();
668 update_active_display ();
671 _display.set_model (_model);
673 /* now update route order keys from the treeview/track display order */
675 sync_order_keys_from_treeview ();
679 EditorRoutes::handle_gui_changes (string const & what, void*)
681 if (_adding_routes) {
685 if (what == "track_height") {
686 /* Optional :make tracks change height while it happens, instead
692 if (what == "visible_tracks") {
698 EditorRoutes::route_removed (TimeAxisView *tv)
700 ENSURE_GUI_THREAD (*this, &EditorRoutes::route_removed, tv)
702 TreeModel::Children rows = _model->children();
703 TreeModel::Children::iterator ri;
705 for (ri = rows.begin(); ri != rows.end(); ++ri) {
706 if ((*ri)[_columns.tv] == tv) {
712 /* the deleted signal for the treeview/model will take
718 EditorRoutes::route_property_changed (const PropertyChange& what_changed, boost::weak_ptr<Route> r)
720 if (!what_changed.contains (ARDOUR::Properties::name)) {
724 ENSURE_GUI_THREAD (*this, &EditorRoutes::route_name_changed, r)
726 boost::shared_ptr<Route> route = r.lock ();
732 TreeModel::Children rows = _model->children();
733 TreeModel::Children::iterator i;
735 for (i = rows.begin(); i != rows.end(); ++i) {
736 boost::shared_ptr<Route> t = (*i)[_columns.route];
738 (*i)[_columns.text] = route->name();
745 EditorRoutes::update_active_display ()
747 TreeModel::Children rows = _model->children();
748 TreeModel::Children::iterator i;
750 for (i = rows.begin(); i != rows.end(); ++i) {
751 boost::shared_ptr<Route> route = (*i)[_columns.route];
752 (*i)[_columns.active] = route->active ();
757 EditorRoutes::update_visibility ()
759 TreeModel::Children rows = _model->children();
760 TreeModel::Children::iterator i;
762 suspend_redisplay ();
764 for (i = rows.begin(); i != rows.end(); ++i) {
765 TimeAxisView *tv = (*i)[_columns.tv];
766 (*i)[_columns.visible] = tv->marked_for_display ();
769 /* force route order keys catch up with visibility changes
772 sync_order_keys_from_treeview ();
778 EditorRoutes::hide_track_in_display (TimeAxisView& tv)
780 TreeModel::Children rows = _model->children();
781 TreeModel::Children::iterator i;
783 for (i = rows.begin(); i != rows.end(); ++i) {
784 if ((*i)[_columns.tv] == &tv) {
785 tv.set_marked_for_display (false);
786 (*i)[_columns.visible] = false;
794 EditorRoutes::show_track_in_display (TimeAxisView& tv)
796 TreeModel::Children rows = _model->children();
797 TreeModel::Children::iterator i;
800 for (i = rows.begin(); i != rows.end(); ++i) {
801 if ((*i)[_columns.tv] == &tv) {
802 tv.set_marked_for_display (true);
803 (*i)[_columns.visible] = true;
811 EditorRoutes::reset_remote_control_ids ()
813 if (Config->get_remote_model() != EditorOrdered || !_session || _session->deletion_in_progress()) {
817 TreeModel::Children rows = _model->children();
824 DEBUG_TRACE (DEBUG::OrderKeys, "editor reset remote control ids\n");
826 TreeModel::Children::iterator ri;
827 bool rid_change = false;
829 uint32_t invisible_key = UINT32_MAX;
831 for (ri = rows.begin(); ri != rows.end(); ++ri) {
833 boost::shared_ptr<Route> route = (*ri)[_columns.route];
834 bool visible = (*ri)[_columns.visible];
837 if (!route->is_master() && !route->is_monitor()) {
839 uint32_t new_rid = (visible ? rid : invisible_key--);
841 if (new_rid != route->remote_control_id()) {
842 route->set_remote_control_id_from_order_key (EditorSort, new_rid);
854 /* tell the world that we changed the remote control IDs */
855 _session->notify_remote_id_change ();
861 EditorRoutes::sync_order_keys_from_treeview ()
863 if (_ignore_reorder || !_session || _session->deletion_in_progress()) {
867 TreeModel::Children rows = _model->children();
874 DEBUG_TRACE (DEBUG::OrderKeys, "editor sync order keys from treeview\n");
876 TreeModel::Children::iterator ri;
877 bool changed = false;
878 bool rid_change = false;
881 uint32_t invisible_key = UINT32_MAX;
883 for (ri = rows.begin(); ri != rows.end(); ++ri) {
885 boost::shared_ptr<Route> route = (*ri)[_columns.route];
886 bool visible = (*ri)[_columns.visible];
888 uint32_t old_key = route->order_key (EditorSort);
890 if (order != old_key) {
891 route->set_order_key (EditorSort, order);
896 if ((Config->get_remote_model() == EditorOrdered) && !route->is_master() && !route->is_monitor()) {
898 uint32_t new_rid = (visible ? rid : invisible_key--);
900 if (new_rid != route->remote_control_id()) {
901 route->set_remote_control_id_from_order_key (EditorSort, new_rid);
915 /* tell the world that we changed the editor sort keys */
916 _session->sync_order_keys (EditorSort);
920 /* tell the world that we changed the remote control IDs */
921 _session->notify_remote_id_change ();
926 EditorRoutes::sync_treeview_from_order_keys (RouteSortOrderKey src)
928 /* Some route order key(s) for `src' has been changed, make sure that
929 we update out tree/list model and GUI to reflect the change.
932 if (!_session || _session->deletion_in_progress()) {
936 DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("editor sync model from order keys, src = %1\n", enum_2_string (src)));
938 if (src == MixerSort) {
940 if (!Config->get_sync_all_route_ordering()) {
941 /* mixer sort keys changed - we don't care */
945 DEBUG_TRACE (DEBUG::OrderKeys, "reset editor order key to match mixer\n");
947 /* mixer sort keys were changed, update the editor sort
948 * keys since "sync mixer+editor order" is enabled.
951 boost::shared_ptr<RouteList> r = _session->get_routes ();
953 for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
954 (*i)->sync_order_keys (src);
958 /* we could get here after either a change in the Mixer or Editor sort
959 * order, but either way, the mixer order keys reflect the intended
960 * order for the GUI, so reorder the treeview model to match it.
963 vector<int> neworder;
964 TreeModel::Children rows = _model->children();
965 uint32_t old_order = 0;
966 bool changed = false;
972 OrderKeySortedRoutes sorted_routes;
974 for (TreeModel::Children::iterator ri = rows.begin(); ri != rows.end(); ++ri, ++old_order) {
975 boost::shared_ptr<Route> route = (*ri)[_columns.route];
976 sorted_routes.push_back (RoutePlusOrderKey (route, old_order, route->order_key (EditorSort)));
979 SortByNewDisplayOrder cmp;
981 sort (sorted_routes.begin(), sorted_routes.end(), cmp);
982 neworder.assign (sorted_routes.size(), 0);
986 for (OrderKeySortedRoutes::iterator sr = sorted_routes.begin(); sr != sorted_routes.end(); ++sr, ++n) {
988 neworder[n] = sr->old_display_order;
990 if (sr->old_display_order != n) {
994 DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("EDITOR change order for %1 from %2 to %3\n",
995 sr->route->name(), sr->old_display_order, n));
999 Unwinder<bool> uw (_ignore_reorder, true);
1000 _model->reorder (neworder);
1007 EditorRoutes::hide_all_tracks (bool /*with_select*/)
1009 TreeModel::Children rows = _model->children();
1010 TreeModel::Children::iterator i;
1012 suspend_redisplay ();
1014 for (i = rows.begin(); i != rows.end(); ++i) {
1016 TreeModel::Row row = (*i);
1017 TimeAxisView *tv = row[_columns.tv];
1023 row[_columns.visible] = false;
1026 resume_redisplay ();
1028 /* XXX this seems like a hack and half, but its not clear where to put this
1032 //reset_scrolling_region ();
1036 EditorRoutes::set_all_tracks_visibility (bool yn)
1038 TreeModel::Children rows = _model->children();
1039 TreeModel::Children::iterator i;
1041 suspend_redisplay ();
1043 for (i = rows.begin(); i != rows.end(); ++i) {
1045 TreeModel::Row row = (*i);
1046 TimeAxisView* tv = row[_columns.tv];
1052 tv->set_marked_for_display (yn);
1053 (*i)[_columns.visible] = yn;
1056 /* force route order keys catch up with visibility changes
1059 sync_order_keys_from_treeview ();
1061 resume_redisplay ();
1065 EditorRoutes::set_all_audio_midi_visibility (int tracks, bool yn)
1067 TreeModel::Children rows = _model->children();
1068 TreeModel::Children::iterator i;
1070 suspend_redisplay ();
1072 for (i = rows.begin(); i != rows.end(); ++i) {
1074 TreeModel::Row row = (*i);
1075 TimeAxisView* tv = row[_columns.tv];
1077 AudioTimeAxisView* atv;
1078 MidiTimeAxisView* mtv;
1084 if ((atv = dynamic_cast<AudioTimeAxisView*>(tv)) != 0) {
1087 (*i)[_columns.visible] = yn;
1091 if (atv->is_audio_track()) {
1092 (*i)[_columns.visible] = yn;
1097 if (!atv->is_audio_track()) {
1098 (*i)[_columns.visible] = yn;
1103 else if ((mtv = dynamic_cast<MidiTimeAxisView*>(tv)) != 0) {
1106 (*i)[_columns.visible] = yn;
1110 if (mtv->is_midi_track()) {
1111 (*i)[_columns.visible] = yn;
1118 /* force route order keys catch up with visibility changes
1121 sync_order_keys_from_treeview ();
1123 resume_redisplay ();
1127 EditorRoutes::hide_all_routes ()
1129 set_all_tracks_visibility (false);
1133 EditorRoutes::show_all_routes ()
1135 set_all_tracks_visibility (true);
1139 EditorRoutes::show_all_audiotracks()
1141 set_all_audio_midi_visibility (1, true);
1144 EditorRoutes::hide_all_audiotracks ()
1146 set_all_audio_midi_visibility (1, false);
1150 EditorRoutes::show_all_audiobus ()
1152 set_all_audio_midi_visibility (2, true);
1155 EditorRoutes::hide_all_audiobus ()
1157 set_all_audio_midi_visibility (2, false);
1161 EditorRoutes::show_all_miditracks()
1163 set_all_audio_midi_visibility (3, true);
1166 EditorRoutes::hide_all_miditracks ()
1168 set_all_audio_midi_visibility (3, false);
1172 EditorRoutes::key_press (GdkEventKey* ev)
1174 TreeViewColumn *col;
1175 boost::shared_ptr<RouteList> rl (new RouteList);
1178 switch (ev->keyval) {
1180 case GDK_ISO_Left_Tab:
1182 /* If we appear to be editing something, leave that cleanly and appropriately.
1184 if (name_editable) {
1185 name_editable->editing_done ();
1189 col = _display.get_column (_name_column); // select&focus on name column
1191 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
1192 treeview_select_previous (_display, _model, col);
1194 treeview_select_next (_display, _model, col);
1201 if (get_relevant_routes (rl)) {
1202 _session->set_mute (rl, !rl->front()->muted(), Session::rt_cleanup);
1208 if (get_relevant_routes (rl)) {
1209 if (Config->get_solo_control_is_listen_control()) {
1210 _session->set_listen (rl, !rl->front()->listening_via_monitor(), Session::rt_cleanup);
1212 _session->set_solo (rl, !rl->front()->self_soloed(), Session::rt_cleanup);
1219 if (get_relevant_routes (rl)) {
1220 _session->set_record_enabled (rl, !rl->front()->record_enabled(), Session::rt_cleanup);
1232 EditorRoutes::get_relevant_routes (boost::shared_ptr<RouteList> rl)
1235 RouteTimeAxisView* rtv;
1236 RefPtr<TreeSelection> selection = _display.get_selection();
1240 if (selection->count_selected_rows() != 0) {
1244 RefPtr<TreeModel> tm = RefPtr<TreeModel>::cast_dynamic (_model);
1245 iter = selection->get_selected (tm);
1248 /* use mouse pointer */
1253 _display.get_pointer (x, y);
1254 _display.convert_widget_to_bin_window_coords (x, y, bx, by);
1256 if (_display.get_path_at_pos (bx, by, path)) {
1257 iter = _model->get_iter (path);
1262 tv = (*iter)[_columns.tv];
1264 rtv = dynamic_cast<RouteTimeAxisView*>(tv);
1266 rl->push_back (rtv->route());
1271 return !rl->empty();
1275 EditorRoutes::button_press (GdkEventButton* ev)
1277 if (Keyboard::is_context_menu_event (ev)) {
1282 TreeModel::Path path;
1283 TreeViewColumn *tvc;
1287 if (!_display.get_path_at_pos ((int) ev->x, (int) ev->y, path, tvc, cell_x, cell_y)) {
1288 /* cancel selection */
1289 _display.get_selection()->unselect_all ();
1290 /* end any editing by grabbing focus */
1291 _display.grab_focus ();
1295 //Scroll editor canvas to selected track
1296 if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
1298 // Get the model row.
1299 Gtk::TreeModel::Row row = *_model->get_iter (path);
1301 TimeAxisView *tv = row[_columns.tv];
1303 int y_pos = tv->y_position();
1305 //Clamp the y pos so that we do not extend beyond the canvas full height.
1306 if (_editor->_full_canvas_height - y_pos < _editor->_visible_canvas_height){
1307 y_pos = _editor->_full_canvas_height - _editor->_visible_canvas_height;
1310 //Only scroll to if the track is visible
1312 _editor->reset_y_origin (y_pos);
1320 EditorRoutes::selection_filter (Glib::RefPtr<TreeModel> const &, TreeModel::Path const&, bool /*selected*/)
1322 if (selection_countdown) {
1323 if (--selection_countdown == 0) {
1326 /* no selection yet ... */
1333 struct EditorOrderRouteSorter {
1334 bool operator() (boost::shared_ptr<Route> a, boost::shared_ptr<Route> b) {
1335 if (a->is_master()) {
1336 /* master before everything else */
1338 } else if (b->is_master()) {
1339 /* everything else before master */
1342 return a->order_key (EditorSort) < b->order_key (EditorSort);
1347 EditorRoutes::initial_display ()
1349 suspend_redisplay ();
1353 resume_redisplay ();
1357 boost::shared_ptr<RouteList> routes = _session->get_routes();
1359 if (ARDOUR_UI::instance()->session_is_new ()) {
1361 /* new session: stamp all routes with the right editor order
1365 _editor->add_routes (*(routes.get()));
1369 /* existing session: sort a copy of the route list by
1370 * editor-order and add its contents to the display.
1373 RouteList r (*routes);
1374 EditorOrderRouteSorter sorter;
1377 _editor->add_routes (r);
1381 resume_redisplay ();
1385 EditorRoutes::display_drag_data_received (const RefPtr<Gdk::DragContext>& context,
1387 const SelectionData& data,
1388 guint info, guint time)
1390 if (data.get_target() == "GTK_TREE_MODEL_ROW") {
1391 _display.on_drag_data_received (context, x, y, data, info, time);
1395 context->drag_finish (true, false, time);
1399 EditorRoutes::move_selected_tracks (bool up)
1401 if (_editor->selection->tracks.empty()) {
1405 typedef std::pair<TimeAxisView*,boost::shared_ptr<Route> > ViewRoute;
1406 std::list<ViewRoute> view_routes;
1407 std::vector<int> neworder;
1408 TreeModel::Children rows = _model->children();
1409 TreeModel::Children::iterator ri;
1411 for (ri = rows.begin(); ri != rows.end(); ++ri) {
1412 TimeAxisView* tv = (*ri)[_columns.tv];
1413 boost::shared_ptr<Route> route = (*ri)[_columns.route];
1415 view_routes.push_back (ViewRoute (tv, route));
1418 list<ViewRoute>::iterator trailing;
1419 list<ViewRoute>::iterator leading;
1423 trailing = view_routes.begin();
1424 leading = view_routes.begin();
1428 while (leading != view_routes.end()) {
1429 if (_editor->selection->selected (leading->first)) {
1430 view_routes.insert (trailing, ViewRoute (leading->first, leading->second));
1431 leading = view_routes.erase (leading);
1440 /* if we could use reverse_iterator in list::insert, this code
1441 would be a beautiful reflection of the code above. but we can't
1442 and so it looks like a bit of a mess.
1445 trailing = view_routes.end();
1446 leading = view_routes.end();
1448 --leading; if (leading == view_routes.begin()) { return; }
1454 if (_editor->selection->selected (leading->first)) {
1455 list<ViewRoute>::iterator tmp;
1457 /* need to insert *after* trailing, not *before* it,
1458 which is what insert (iter, val) normally does.
1464 view_routes.insert (tmp, ViewRoute (leading->first, leading->second));
1466 /* can't use iter = cont.erase (iter); form here, because
1467 we need iter to move backwards.
1475 if (leading == view_routes.begin()) {
1476 /* the one we've just inserted somewhere else
1477 was the first in the list. erase this copy,
1478 and then break, because we're done.
1483 view_routes.erase (leading);
1492 if (leading == view_routes.begin()) {
1501 for (leading = view_routes.begin(); leading != view_routes.end(); ++leading) {
1502 uint32_t order = leading->second->order_key (EditorSort);
1503 neworder.push_back (order);
1507 DEBUG_TRACE (DEBUG::OrderKeys, "New order after moving tracks:\n");
1508 for (vector<int>::iterator i = neworder.begin(); i != neworder.end(); ++i) {
1509 DEBUG_TRACE (DEBUG::OrderKeys, string_compose ("\t%1\n", *i));
1511 DEBUG_TRACE (DEBUG::OrderKeys, "-------\n");
1513 for (vector<int>::iterator i = neworder.begin(); i != neworder.end(); ++i) {
1514 if (*i >= (int) neworder.size()) {
1515 cerr << "Trying to move something to " << *i << " of " << neworder.size() << endl;
1517 assert (*i < (int) neworder.size ());
1521 _model->reorder (neworder);
1525 EditorRoutes::update_input_active_display ()
1527 TreeModel::Children rows = _model->children();
1528 TreeModel::Children::iterator i;
1530 for (i = rows.begin(); i != rows.end(); ++i) {
1531 boost::shared_ptr<Route> route = (*i)[_columns.route];
1533 if (boost::dynamic_pointer_cast<Track> (route)) {
1534 boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (route);
1537 (*i)[_columns.is_input_active] = mt->input_active();
1544 EditorRoutes::update_rec_display ()
1546 TreeModel::Children rows = _model->children();
1547 TreeModel::Children::iterator i;
1549 for (i = rows.begin(); i != rows.end(); ++i) {
1550 boost::shared_ptr<Route> route = (*i)[_columns.route];
1552 if (boost::dynamic_pointer_cast<Track> (route)) {
1553 boost::shared_ptr<MidiTrack> mt = boost::dynamic_pointer_cast<MidiTrack> (route);
1555 if (route->record_enabled()) {
1556 if (_session->record_status() == Session::Recording) {
1557 (*i)[_columns.rec_state] = 1;
1559 (*i)[_columns.rec_state] = 2;
1561 } else if (mt && mt->step_editing()) {
1562 (*i)[_columns.rec_state] = 3;
1564 (*i)[_columns.rec_state] = 0;
1567 (*i)[_columns.name_editable] = !route->record_enabled ();
1573 EditorRoutes::update_mute_display ()
1575 TreeModel::Children rows = _model->children();
1576 TreeModel::Children::iterator i;
1578 for (i = rows.begin(); i != rows.end(); ++i) {
1579 boost::shared_ptr<Route> route = (*i)[_columns.route];
1580 (*i)[_columns.mute_state] = RouteUI::mute_active_state (_session, route);
1585 EditorRoutes::update_solo_display (bool /* selfsoloed */)
1587 TreeModel::Children rows = _model->children();
1588 TreeModel::Children::iterator i;
1590 for (i = rows.begin(); i != rows.end(); ++i) {
1591 boost::shared_ptr<Route> route = (*i)[_columns.route];
1592 (*i)[_columns.solo_state] = RouteUI::solo_active_state (route);
1597 EditorRoutes::update_solo_isolate_display ()
1599 TreeModel::Children rows = _model->children();
1600 TreeModel::Children::iterator i;
1602 for (i = rows.begin(); i != rows.end(); ++i) {
1603 boost::shared_ptr<Route> route = (*i)[_columns.route];
1604 (*i)[_columns.solo_isolate_state] = RouteUI::solo_isolate_active_state (route) ? 1 : 0;
1609 EditorRoutes::update_solo_safe_display ()
1611 TreeModel::Children rows = _model->children();
1612 TreeModel::Children::iterator i;
1614 for (i = rows.begin(); i != rows.end(); ++i) {
1615 boost::shared_ptr<Route> route = (*i)[_columns.route];
1616 (*i)[_columns.solo_safe_state] = RouteUI::solo_safe_active_state (route) ? 1 : 0;
1621 EditorRoutes::views () const
1623 list<TimeAxisView*> v;
1624 for (TreeModel::Children::iterator i = _model->children().begin(); i != _model->children().end(); ++i) {
1625 v.push_back ((*i)[_columns.tv]);
1632 EditorRoutes::clear ()
1634 _display.set_model (Glib::RefPtr<Gtk::TreeStore> (0));
1636 _display.set_model (_model);
1640 EditorRoutes::name_edit_started (CellEditable* ce, const Glib::ustring&)
1644 /* give it a special name */
1646 Gtk::Entry *e = dynamic_cast<Gtk::Entry*> (ce);
1649 e->set_name (X_("RouteNameEditorEntry"));
1654 EditorRoutes::name_edit (std::string const & path, std::string const & new_text)
1658 TreeIter iter = _model->get_iter (path);
1664 boost::shared_ptr<Route> route = (*iter)[_columns.route];
1666 if (route && route->name() != new_text) {
1667 route->set_name (new_text);
1672 EditorRoutes::solo_changed_so_update_mute ()
1674 update_mute_display ();
1678 EditorRoutes::show_tracks_with_regions_at_playhead ()
1680 boost::shared_ptr<RouteList> const r = _session->get_routes_with_regions_at (_session->transport_frame ());
1682 set<TimeAxisView*> show;
1683 for (RouteList::const_iterator i = r->begin(); i != r->end(); ++i) {
1684 TimeAxisView* tav = _editor->axis_view_from_route (*i);
1690 suspend_redisplay ();
1692 TreeModel::Children rows = _model->children ();
1693 for (TreeModel::Children::iterator i = rows.begin(); i != rows.end(); ++i) {
1694 TimeAxisView* tv = (*i)[_columns.tv];
1695 (*i)[_columns.visible] = (show.find (tv) != show.end());
1698 resume_redisplay ();