Fixup for comments editor blocking presses to its button
[ardour.git] / gtk2_ardour / mixer_strip.cc
1 /*
2     Copyright (C) 2000-2002 Paul Davis
3
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.
8
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.
13
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.
17
18     $Id$
19 */
20
21 #include <cmath>
22 #include <glib.h>
23
24 #include <sigc++/bind.h>
25
26 #include <gtkmm2ext/gtk_ui.h>
27 #include <gtkmm2ext/utils.h>
28 #include <gtkmm2ext/choice.h>
29 #include <gtkmm2ext/slider_controller.h>
30 #include <gtkmm2ext/stop_signal.h>
31 #include <gtkmm2ext/bindable_button.h>
32 #include <gtkmm2ext/doi.h>
33
34 #include <ardour/ardour.h>
35 #include <ardour/session.h>
36 #include <ardour/audioengine.h>
37 #include <ardour/route.h>
38 #include <ardour/audio_track.h>
39 #include <ardour/diskstream.h>
40 #include <ardour/panner.h>
41 #include <ardour/send.h>
42 #include <ardour/insert.h>
43 #include <ardour/ladspa_plugin.h>
44 #include <ardour/connection.h>
45 #include <ardour/session_connection.h>
46
47 #include "ardour_ui.h"
48 #include "ardour_dialog.h"
49 #include "mixer_strip.h"
50 #include "mixer_ui.h"
51 #include "keyboard.h"
52 #include "plugin_selector.h"
53 #include "public_editor.h"
54
55 #include "plugin_ui.h"
56 #include "send_ui.h"
57 #include "io_selector.h"
58 #include "utils.h"
59 #include "gui_thread.h"
60
61 #include "i18n.h"
62
63 using namespace sigc;
64 using namespace ARDOUR;
65 using namespace Gtk;
66 using namespace Gtkmm2ext;
67
68 #ifdef VARISPEED_IN_MIXER_STRIP
69 static void 
70 speed_printer (char buf[32], Gtk::Adjustment& adj, void* arg)
71 {
72         float val = adj.get_value ();
73
74         if (val == 1.0) {
75                 strcpy (buf, "1");
76         } else {
77                 snprintf (buf, 32, "%.3f", val);
78         }
79 }
80 #endif 
81
82 MixerStrip::MixerStrip (Mixer_UI& mx, Session& sess, Route& rt, bool in_mixer)
83         : AxisView(sess),
84           RouteUI (rt, sess, _("mute"), _("solo"), _("RECORD")),
85           _mixer(mx),
86           pre_redirect_box (PreFader, sess, rt, mx.plugin_selector(), mx.selection(), in_mixer),
87           post_redirect_box (PostFader, sess, rt, mx.plugin_selector(), mx.selection(), in_mixer),
88           gpm (_route, sess),
89           panners (_route, sess),
90           button_table (6, 2),
91           gain_automation_style_button (""),
92           gain_automation_state_button (""),
93           pan_automation_style_button (""),
94           pan_automation_state_button (""),
95           comment_button (_("Comments")),
96           speed_adjustment (1.0, 0.001, 4.0, 0.001, 0.1),
97           speed_spinner (&speed_adjustment, "MixerStripSpeedBase", true)
98
99 {
100         if (set_color_from_route()) {
101                 set_color (unique_random_color());
102         }
103
104         input_selector = 0;
105         output_selector = 0;
106         group_menu = 0;
107         _marked_for_display = false;
108         route_ops_menu = 0;
109         ignore_comment_edit = false;
110         ignore_toggle = false;
111         ignore_speed_adjustment = false;
112         comment_window = 0;
113         comment_area = 0;
114
115         width_button.add (*(manage (new Gtk::Image (get_xpm("lr.xpm")))));
116         hide_button.add (*(manage (new Gtk::Image (get_xpm("small_x.xpm")))));
117
118
119         input_label.set_text (_("INPUT"));
120         input_button.add (input_label);
121         input_button.set_name ("MixerIOButton");
122         input_label.set_name ("MixerIOButtonLabel");
123
124         output_label.set_text (_("OUTPUT"));
125         output_button.add (output_label);
126         output_button.set_name ("MixerIOButton");
127         output_label.set_name ("MixerIOButtonLabel");
128
129         rec_enable_button->set_name ("MixerRecordEnableButton");
130         rec_enable_button->unset_flags (Gtk::CAN_FOCUS);
131
132         solo_button->set_name ("MixerSoloButton");
133         mute_button->set_name ("MixerMuteButton");
134         gain_automation_style_button.set_name ("MixerAutomationModeButton");
135         gain_automation_state_button.set_name ("MixerAutomationPlaybackButton");
136         pan_automation_style_button.set_name ("MixerAutomationModeButton");
137         pan_automation_state_button.set_name ("MixerAutomationPlaybackButton");
138
139         ARDOUR_UI::instance()->tooltips().set_tip (pan_automation_state_button, _("Pan automation mode"));
140         ARDOUR_UI::instance()->tooltips().set_tip (gain_automation_state_button, _("Gain automation mode"));
141
142         ARDOUR_UI::instance()->tooltips().set_tip (pan_automation_style_button, _("Pan automation type"));
143         ARDOUR_UI::instance()->tooltips().set_tip (gain_automation_style_button, _("Gain automation type"));
144
145         hide_button.set_events (hide_button.get_events() & ~(Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK));
146
147         width_button.unset_flags (Gtk::CAN_FOCUS);
148         hide_button.unset_flags (Gtk::CAN_FOCUS);
149         input_button.unset_flags (Gtk::CAN_FOCUS);
150         output_button.unset_flags (Gtk::CAN_FOCUS);
151         solo_button->unset_flags (Gtk::CAN_FOCUS);
152         mute_button->unset_flags (Gtk::CAN_FOCUS);
153         gain_automation_style_button.unset_flags (Gtk::CAN_FOCUS);
154         gain_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
155         pan_automation_style_button.unset_flags (Gtk::CAN_FOCUS);
156         pan_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
157
158         button_table.set_homogeneous (true);
159         button_table.set_spacings (0);
160
161         button_table.attach (name_button, 0, 2, 0, 1);
162         button_table.attach (group_button, 0, 2, 1, 2);
163         button_table.attach (input_button, 0, 2, 2, 3);
164
165         button_table.attach (*solo_button, 0, 1, 3, 4);
166         button_table.attach (*mute_button, 1, 2, 3, 4);
167
168         button_table.attach (gain_automation_state_button, 0, 1, 4, 5);
169         button_table.attach (pan_automation_state_button, 1, 2, 4, 5);
170
171         using namespace Menu_Helpers;
172         
173         gain_astate_menu.items().push_back (MenuElem (_("off"), 
174                                                       bind (mem_fun (_route, &IO::set_gain_automation_state), (AutoState) Off)));
175         gain_astate_menu.items().push_back (MenuElem (_("play"),
176                                                       bind (mem_fun (_route, &IO::set_gain_automation_state), (AutoState) Play)));
177         gain_astate_menu.items().push_back (MenuElem (_("write"),
178                                                       bind (mem_fun (_route, &IO::set_gain_automation_state), (AutoState) Write)));
179         gain_astate_menu.items().push_back (MenuElem (_("touch"),
180                                                       bind (mem_fun (_route, &IO::set_gain_automation_state), (AutoState) Touch)));
181         
182         gain_astyle_menu.items().push_back (MenuElem (_("trim")));
183         gain_astyle_menu.items().push_back (MenuElem (_("abs")));
184
185         pan_astate_menu.items().push_back (MenuElem (_("off"), 
186                                                      bind (mem_fun (_route.panner(), &Panner::set_automation_state), (AutoState) Off)));
187         pan_astate_menu.items().push_back (MenuElem (_("play"),
188                                                      bind (mem_fun (_route.panner(), &Panner::set_automation_state), (AutoState) Play)));
189         pan_astate_menu.items().push_back (MenuElem (_("write"),
190                                                      bind (mem_fun (_route.panner(), &Panner::set_automation_state), (AutoState) Write)));
191         pan_astate_menu.items().push_back (MenuElem (_("touch"),
192                                                      bind (mem_fun (_route.panner(), &Panner::set_automation_state), (AutoState) Touch)));
193
194         pan_astyle_menu.items().push_back (MenuElem (_("trim")));
195         pan_astyle_menu.items().push_back (MenuElem (_("abs")));
196         
197         gain_astate_menu.set_name ("ArdourContextMenu");
198         gain_astyle_menu.set_name ("ArdourContextMenu");
199         pan_astate_menu.set_name ("ArdourContextMenu");
200         pan_astyle_menu.set_name ("ArdourContextMenu");
201
202         ARDOUR_UI::instance()->tooltips().set_tip (gain_automation_style_button, _("gain automation mode"));
203         ARDOUR_UI::instance()->tooltips().set_tip (pan_automation_style_button, _("pan automation mode"));
204         ARDOUR_UI::instance()->tooltips().set_tip (gain_automation_state_button, _("gain automation state"));
205         ARDOUR_UI::instance()->tooltips().set_tip (pan_automation_state_button, _("pan automation state"));
206
207         if (is_audio_track()) {
208                 
209                 AudioTrack* at = dynamic_cast<AudioTrack*>(&_route);
210
211                 at->FreezeChange.connect (mem_fun(*this, &MixerStrip::map_frozen));
212
213 #ifdef VARISPEED_IN_MIXER_STRIP
214                 speed_adjustment.signal_value_changed().connect (mem_fun(*this, &MixerStrip::speed_adjustment_changed));
215                 
216                 speed_frame.set_name ("BaseFrame");
217                 speed_frame.set_shadow_type (Gtk::SHADOW_IN);
218                 speed_frame.add (speed_spinner);
219                 
220                 speed_spinner.set_print_func (speed_printer, 0);
221
222                 ARDOUR_UI::instance()->tooltips().set_tip (speed_spinner, _("varispeed"));
223
224                 button_table.attach (speed_frame, 0, 2, 5, 6);
225 #endif /* VARISPEED_IN_MIXER_STRIP */
226
227                 button_table.attach (*rec_enable_button, 0, 2, 5, 6);
228         }
229
230         name_button.add (name_label);
231         name_button.set_name ("MixerNameButton");
232         Gtkmm2ext::set_size_request_to_display_given_text (name_button, "longest label", 2, 2);
233
234         name_label.set_name ("MixerNameButtonLabel");
235         name_label.set_text (_route.name());
236
237         group_button.add (group_label);
238         group_button.set_name ("MixerGroupButton");
239         group_label.set_name ("MixerGroupButtonLabel");
240
241         comment_button.set_name ("MixerCommentButton");
242
243         ARDOUR_UI::instance()->tooltips().set_tip (comment_button, _route.comment()=="" ?
244                                                         _("Click to Add/Edit Comments"):
245                                                         _route.comment());
246
247         comment_button.signal_clicked().connect (mem_fun(*this, &MixerStrip::comment_button_clicked));
248         
249         global_vpacker.set_border_width (0);
250         global_vpacker.set_spacing (0);
251
252         Gtk::VBox *whvbox = manage (new Gtk::VBox);
253
254         width_button.set_name ("MixerWidthButton");
255         hide_button.set_name ("MixerHideButton");
256
257         width_button.signal_clicked().connect (mem_fun(*this, &MixerStrip::width_clicked));
258         hide_button.signal_clicked().connect (mem_fun(*this, &MixerStrip::hide_clicked));
259
260         width_hide_box.pack_start (width_button, false, true);
261         width_hide_box.pack_end (hide_button, false, true);
262
263         whvbox->pack_start (width_hide_box, true, true);
264
265         global_vpacker.pack_start (*whvbox, Gtk::PACK_SHRINK);
266         global_vpacker.pack_start (button_table,Gtk::PACK_SHRINK);
267         global_vpacker.pack_start (pre_redirect_box, true, true);
268         global_vpacker.pack_start (gpm, Gtk::PACK_SHRINK, 4);
269         global_vpacker.pack_start (post_redirect_box, true, true);
270         global_vpacker.pack_start (panners, Gtk::PACK_SHRINK);
271         global_vpacker.pack_start (output_button, Gtk::PACK_SHRINK);
272         global_vpacker.pack_start (comment_button, Gtk::PACK_SHRINK);
273
274         global_frame.add (global_vpacker);
275         global_frame.set_shadow_type (Gtk::SHADOW_IN);
276         global_frame.set_name ("BaseFrame");
277
278         add (global_frame);
279
280         /* force setting of visible selected status */
281
282         _selected = true;
283         set_selected (false);
284
285         _packed = false;
286         _embedded = false;
287
288         _route.input_changed.connect (mem_fun(*this, &MixerStrip::input_changed));
289         _route.output_changed.connect (mem_fun(*this, &MixerStrip::output_changed));
290         _route.mute_changed.connect (mem_fun(*this, &RouteUI::mute_changed));
291         _route.solo_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
292         _route.solo_safe_changed.connect (mem_fun(*this, &RouteUI::solo_changed));
293         _route.mix_group_changed.connect (mem_fun(*this, &MixerStrip::mix_group_changed));
294         _route.gain_automation_curve().automation_state_changed.connect (mem_fun(*this, &MixerStrip::gain_automation_state_changed));
295         _route.gain_automation_curve().automation_style_changed.connect (mem_fun(*this, &MixerStrip::gain_automation_style_changed));
296         _route.panner().Changed.connect (mem_fun(*this, &MixerStrip::connect_to_pan));
297
298         if (is_audio_track()) {
299                 audio_track()->diskstream_changed.connect (mem_fun(*this, &MixerStrip::diskstream_changed));
300                 get_diskstream()->speed_changed.connect (mem_fun(*this, &MixerStrip::speed_changed));
301         }
302
303         _route.name_changed.connect (mem_fun(*this, &RouteUI::name_changed));
304         _route.comment_changed.connect (mem_fun(*this, &MixerStrip::comment_changed));
305         _route.gui_changed.connect (mem_fun(*this, &MixerStrip::route_gui_changed));
306
307         input_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::input_press), false);
308         output_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::output_press), false);
309
310         rec_enable_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::rec_enable_press));
311         solo_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::solo_press), false);
312         solo_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::solo_release), false);
313         mute_button->signal_button_press_event().connect (mem_fun(*this, &RouteUI::mute_press), false);
314         mute_button->signal_button_release_event().connect (mem_fun(*this, &RouteUI::mute_release), false);
315
316         gain_automation_style_button.signal_button_press_event().connect (mem_fun(*this, &MixerStrip::gain_automation_style_button_event), false);
317         gain_automation_style_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::gain_automation_style_button_event), false);
318         pan_automation_style_button.signal_button_press_event().connect (mem_fun(*this, &MixerStrip::pan_automation_style_button_event), false);
319         pan_automation_style_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::pan_automation_style_button_event), false);
320
321         gain_automation_state_button.signal_button_press_event().connect (mem_fun(*this, &MixerStrip::gain_automation_state_button_event), false);
322         gain_automation_state_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::gain_automation_state_button_event), false);
323         pan_automation_state_button.signal_button_press_event().connect (mem_fun(*this, &MixerStrip::pan_automation_state_button_event), false);
324         pan_automation_state_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::pan_automation_state_button_event), false);
325
326         name_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::name_button_button_release), false);
327
328         group_button.signal_button_release_event().connect (mem_fun(*this, &MixerStrip::select_mix_group), false);
329
330         _width = (Width) -1;
331         set_stuff_from_route ();
332
333         /* start off as a passthru strip. we'll correct this, if necessary,
334            in update_diskstream_display().
335         */
336
337         set_name ("AudioTrackStripBase");
338
339         /* now force an update of all the various elements */
340
341         pre_redirect_box.update();
342         post_redirect_box.update();
343         mute_changed (0);
344         solo_changed (0);
345         name_changed (0);
346         comment_changed (0);
347         mix_group_changed (0);
348         gain_automation_state_changed ();
349         pan_automation_state_changed ();
350         connect_to_pan ();
351
352         panners.setup_pan ();
353
354         if (is_audio_track()) {
355                 speed_changed ();
356         }
357
358         update_diskstream_display ();
359         update_input_display ();
360         update_output_display ();
361
362         add_events (Gdk::BUTTON_RELEASE_MASK);
363 }
364
365 MixerStrip::~MixerStrip ()
366 {
367         GoingAway(); /* EMIT_SIGNAL */
368
369         if (input_selector) {
370                 delete input_selector;
371         }
372
373         if (output_selector) {
374                 delete output_selector;
375         }
376 }
377
378 void
379 MixerStrip::set_stuff_from_route ()
380 {
381         XMLProperty *prop;
382         
383         ensure_xml_node ();
384
385         if ((prop = xml_node->property ("strip_width")) != 0) {
386                 if (prop->value() == "wide") {
387                         set_width (Wide);
388                 } else if (prop->value() == "narrow") {
389                         set_width (Narrow);
390                 }
391                 else {
392                         error << string_compose(_("unknown strip width \"%1\" in XML GUI information"), prop->value()) << endmsg;
393                         set_width (Wide);
394                 }
395         }
396         else {
397                 set_width (Wide);
398         }
399
400         if ((prop = xml_node->property ("shown_mixer")) != 0) {
401                 if (prop->value() == "no") {
402                         _marked_for_display = false;
403                 } else {
404                         _marked_for_display = true;
405                 }
406         }
407         else {
408                 /* backwards compatibility */
409                 _marked_for_display = true;
410         }
411 }
412
413 void
414 MixerStrip::set_width (Width w)
415 {
416         /* always set the gpm width again, things may be hidden */
417         gpm.set_width (w);
418         panners.set_width (w);
419         pre_redirect_box.set_width (w);
420         post_redirect_box.set_width (w);
421         
422         if (_width == w) {
423                 return;
424         }
425
426         ensure_xml_node ();
427         
428         _width = w;
429
430         switch (w) {
431         case Wide:
432                 set_size_request (-1, -1);
433                 xml_node->add_property ("strip_width", "wide");
434
435                 rec_enable_button->set_label (_("RECORD"));
436                 mute_button->set_label  (_("mute"));
437                 solo_button->set_label (_("solo"));
438
439                 if (_route.comment() == "") {
440                        comment_button.set_label (_("Comments"));
441                 } else {
442                        comment_button.set_label (_("*Comments*"));
443                 }
444
445                 gain_automation_style_button.set_label (astyle_string(_route.gain_automation_curve().automation_style()));
446                 gain_automation_state_button.set_label (astate_string(_route.gain_automation_curve().automation_state()));
447                 pan_automation_style_button.set_label (astyle_string(_route.panner().automation_style()));
448                 pan_automation_state_button.set_label (astate_string(_route.panner().automation_state()));
449                 Gtkmm2ext::set_size_request_to_display_given_text (name_button, "long", 2, 2);
450                 break;
451
452         case Narrow:
453                 set_size_request (50, -1);
454                 xml_node->add_property ("strip_width", "narrow");
455
456                 rec_enable_button->set_label (_("REC"));
457                 mute_button->set_label (_("m"));
458                 solo_button->set_label (_("s"));
459
460                 if (_route.comment() == "") {
461                        comment_button.set_label (_("Cmt"));
462                 } else {
463                        comment_button.set_label (_("*Cmt*"));
464                 }
465
466                 gain_automation_style_button.set_label (short_astyle_string(_route.gain_automation_curve().automation_style()));
467                 gain_automation_state_button.set_label (short_astate_string(_route.gain_automation_curve().automation_state()));
468                 pan_automation_style_button.set_label (short_astyle_string(_route.panner().automation_style()));
469                 pan_automation_state_button.set_label (short_astate_string(_route.panner().automation_state()));
470                 Gtkmm2ext::set_size_request_to_display_given_text (name_button, "longest label", 2, 2);
471                 break;
472         }
473
474         update_input_display ();
475         update_output_display ();
476         mix_group_changed (0);
477         name_changed (0);
478
479 }
480
481 void
482 MixerStrip::set_packed (bool yn)
483 {
484         _packed = yn;
485
486         ensure_xml_node ();
487
488         if (_packed) {
489                 xml_node->add_property ("shown_mixer", "yes");
490         } else {
491                 xml_node->add_property ("shown_mixer", "no");
492         }
493 }
494
495
496 gint
497 MixerStrip::output_press (GdkEventButton *ev)
498 {
499         using namespace Menu_Helpers;
500
501         if (!_session.engine().connected()) {
502                 MessageDialog msg (_("Not connected to JACK - no I/O changes are possible"));
503                 msg.run ();
504                 return true;
505         }
506
507         MenuList& citems = output_menu.items();
508         output_menu.set_name ("ArdourContextMenu");
509         citems.clear();
510
511         citems.push_back (MenuElem (_("Edit"), mem_fun(*this, &MixerStrip::edit_output_configuration)));
512         citems.push_back (SeparatorElem());
513         citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_output)));
514         citems.push_back (SeparatorElem());
515
516         _session.foreach_connection (this, &MixerStrip::add_connection_to_output_menu);
517
518         output_menu.popup (1, ev->time);
519
520         return TRUE;
521 }
522
523 void
524 MixerStrip::edit_output_configuration ()
525 {
526         if (output_selector == 0) {
527                 output_selector = new IOSelectorWindow (_session, _route, false);
528         } 
529
530         if (output_selector->is_visible()) {
531                 output_selector->get_toplevel()->get_window()->raise();
532         } else {
533                 output_selector->show_all ();
534         }
535 }
536
537 void
538 MixerStrip::edit_input_configuration ()
539 {
540         if (input_selector == 0) {
541                 input_selector = new IOSelectorWindow (_session, _route, true);
542         } 
543
544         if (input_selector->is_visible()) {
545                 input_selector->get_toplevel()->get_window()->raise();
546         } else {
547                 input_selector->show_all ();
548         }
549 }
550
551 gint
552 MixerStrip::input_press (GdkEventButton *ev)
553 {
554         using namespace Menu_Helpers;
555
556         MenuList& citems = input_menu.items();
557         input_menu.set_name ("ArdourContextMenu");
558         citems.clear();
559
560         if (!_session.engine().connected()) {
561                 MessageDialog msg (_("Not connected to JACK - no I/O changes are possible"));
562                 msg.run ();
563                 return true;
564         }
565
566 #if ADVANCED_ROUTE_DISKSTREAM_CONNECTIVITY
567         if (is_audio_track()) {
568                 citems.push_back (MenuElem (_("Track"), mem_fun(*this, &MixerStrip::select_stream_input)));
569         }
570 #endif
571         citems.push_back (MenuElem (_("Edit"), mem_fun(*this, &MixerStrip::edit_input_configuration)));
572         citems.push_back (SeparatorElem());
573         citems.push_back (MenuElem (_("Disconnect"), mem_fun (*(static_cast<RouteUI*>(this)), &RouteUI::disconnect_input)));
574         citems.push_back (SeparatorElem());
575
576         _session.foreach_connection (this, &MixerStrip::add_connection_to_input_menu);
577         input_menu.popup (1, ev->time);
578
579         return TRUE;
580 }
581
582 void
583 MixerStrip::connection_input_chosen (ARDOUR::Connection *c)
584 {
585         if (!ignore_toggle) {
586
587                 try { 
588                         _route.use_input_connection (*c, this);
589                 }
590
591                 catch (AudioEngine::PortRegistrationFailure& err) {
592                         error << _("could not register new ports required for that connection")
593                               << endmsg;
594                 }
595         }
596 }
597
598 void
599 MixerStrip::connection_output_chosen (ARDOUR::Connection *c)
600 {
601         if (!ignore_toggle) {
602
603                 try { 
604                         _route.use_output_connection (*c, this);
605                 }
606
607                 catch (AudioEngine::PortRegistrationFailure& err) {
608                         error << _("could not register new ports required for that connection")
609                               << endmsg;
610                 }
611         }
612 }
613
614 void
615 MixerStrip::add_connection_to_input_menu (ARDOUR::Connection* c)
616 {
617         using namespace Menu_Helpers;
618
619         if (dynamic_cast<InputConnection *> (c) == 0) {
620                 return;
621         }
622
623         MenuList& citems = input_menu.items();
624         
625         if (c->nports() == _route.n_inputs()) {
626
627                 citems.push_back (CheckMenuElem (c->name(), bind (mem_fun(*this, &MixerStrip::connection_input_chosen), c)));
628                 
629                 ARDOUR::Connection *current = _route.input_connection();
630                 
631                 if (current == c) {
632                         ignore_toggle = true;
633                         dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
634                         ignore_toggle = false;
635                 }
636         }
637 }
638
639 void
640 MixerStrip::add_connection_to_output_menu (ARDOUR::Connection* c)
641 {
642         using namespace Menu_Helpers;
643
644         if (dynamic_cast<OutputConnection *> (c) == 0) {
645                 return;
646         }
647
648         if (c->nports() == _route.n_outputs()) {
649
650                 MenuList& citems = output_menu.items();
651                 citems.push_back (CheckMenuElem (c->name(), bind (mem_fun(*this, &MixerStrip::connection_output_chosen), c)));
652                 
653                 ARDOUR::Connection *current = _route.output_connection();
654                 
655                 if (current == c) {
656                         ignore_toggle = true;
657                         dynamic_cast<CheckMenuItem *> (&citems.back())->set_active (true);
658                         ignore_toggle = false;
659                 }
660         }
661 }
662
663 void
664 MixerStrip::select_stream_input ()
665 {
666         using namespace Menu_Helpers;
667
668         Menu *stream_menu = manage (new Menu);
669         MenuList& items = stream_menu->items();
670         stream_menu->set_name ("ArdourContextMenu");
671         
672         Session::DiskStreamList streams = _session.disk_streams();
673
674         for (Session::DiskStreamList::iterator i = streams.begin(); i != streams.end(); ++i) {
675
676                 if (!(*i)->hidden()) {
677
678                         items.push_back (CheckMenuElem ((*i)->name(), bind (mem_fun(*this, &MixerStrip::stream_input_chosen), *i)));
679                         
680                         if (get_diskstream() == *i) {
681                                 ignore_toggle = true;
682                                 static_cast<CheckMenuItem *> (&items.back())->set_active (true);
683                                 ignore_toggle = false;
684                         } 
685                 }
686         }
687         
688         stream_menu->popup (1, 0);
689 }
690
691 void
692 MixerStrip::stream_input_chosen (DiskStream *stream)
693 {
694         if (is_audio_track()) {
695                 audio_track()->set_diskstream (*stream, this);
696         }
697 }
698
699 void
700 MixerStrip::update_diskstream_display ()
701 {
702         if (is_audio_track()) {
703
704                 map_frozen ();
705
706                 update_input_display ();
707
708                 if (input_selector) {
709                         input_selector->hide_all ();
710                 }
711
712                 show_route_color ();
713
714         } else {
715
716                 map_frozen ();
717
718                 update_input_display ();
719                 show_passthru_color ();
720         }
721 }
722
723 void
724 MixerStrip::connect_to_pan ()
725 {
726         ENSURE_GUI_THREAD(mem_fun(*this, &MixerStrip::connect_to_pan));
727         
728         panstate_connection.disconnect ();
729         panstyle_connection.disconnect ();
730
731         if (!_route.panner().empty()) {
732                 StreamPanner* sp = _route.panner().front();
733
734                 panstate_connection = sp->automation().automation_state_changed.connect (mem_fun(*this, &MixerStrip::pan_automation_state_changed));
735                 panstyle_connection = sp->automation().automation_style_changed.connect (mem_fun(*this, &MixerStrip::pan_automation_style_changed));
736         }
737
738         panners.pan_changed (this);
739 }
740
741 void
742 MixerStrip::update_input_display ()
743 {
744         ARDOUR::Connection *c;
745
746         if ((c = _route.input_connection()) != 0) {
747                 input_label.set_text (c->name());
748         } else {
749                 switch (_width) {
750                 case Wide:
751                         input_label.set_text (_("INPUT"));
752                         break;
753                 case Narrow:
754                         input_label.set_text (_("IN"));
755                         break;
756                 }
757         }
758
759         panners.setup_pan ();
760 }
761
762 void
763 MixerStrip::update_output_display ()
764 {
765         ARDOUR::Connection *c;
766
767         if ((c = _route.output_connection()) != 0) {
768                 output_label.set_text (c->name());
769         } else {
770                 switch (_width) {
771                 case Wide:
772                         output_label.set_text (_("OUTPUT"));
773                         break;
774                 case Narrow:
775                         output_label.set_text (_("OUT"));
776                         break;
777                 }
778         }
779
780         gpm.setup_meters ();
781         panners.setup_pan ();
782 }
783
784 void
785 MixerStrip::fast_update ()
786 {
787         gpm.update_meters ();
788 }
789
790 gint
791 MixerStrip::gain_automation_state_button_event (GdkEventButton *ev)
792 {
793         if (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS) {
794                 return TRUE;
795         }
796         
797         switch (ev->button) {
798         case 1:
799                 switch (ev->button) {
800                 case 1:
801                         gain_astate_menu.popup (1, ev->time);
802                         break;
803                 default:
804                         break;
805                 }
806         }
807
808         return TRUE;
809 }
810
811 gint
812 MixerStrip::gain_automation_style_button_event (GdkEventButton *ev)
813 {
814         if (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS) {
815                 return TRUE;
816         }
817
818         switch (ev->button) {
819         case 1:
820                 gain_astyle_menu.popup (1, ev->time);
821                 break;
822         default:
823                 break;
824         }
825         return TRUE;
826 }
827
828 gint
829 MixerStrip::pan_automation_state_button_event (GdkEventButton *ev)
830 {
831         using namespace Menu_Helpers;
832
833         if (ev->type == GDK_BUTTON_PRESS || ev->type == GDK_2BUTTON_PRESS) {
834                 return TRUE;
835         }
836
837         switch (ev->button) {
838         case 1:
839                 pan_astate_menu.popup (1, ev->time);
840                 break;
841         default:
842                 break;
843         }
844
845         return TRUE;
846 }
847
848 gint
849 MixerStrip::pan_automation_style_button_event (GdkEventButton *ev)
850 {
851         switch (ev->button) {
852         case 1:
853                 pan_astyle_menu.popup (1, ev->time);
854                 break;
855         default:
856                 break;
857         }
858         return TRUE;
859 }
860
861 string
862 MixerStrip::astate_string (AutoState state)
863 {
864         return _astate_string (state, false);
865 }
866
867 string
868 MixerStrip::short_astate_string (AutoState state)
869 {
870         return _astate_string (state, true);
871 }
872
873 string
874 MixerStrip::_astate_string (AutoState state, bool shrt)
875 {
876         string sstr;
877
878         switch (state) {
879         case Off:
880                 sstr = (shrt ? "--" : _("off"));
881                 break;
882         case Play:
883                 sstr = (shrt ? "P" : _("aplay"));
884                 break;
885         case Touch:
886                 sstr = (shrt ? "T" : _("touch"));
887                 break;
888         case Write:
889                 sstr = (shrt ? "W" : _("awrite"));
890                 break;
891         }
892
893         return sstr;
894 }
895
896 string
897 MixerStrip::astyle_string (AutoStyle style)
898 {
899         return _astyle_string (style, false);
900 }
901
902 string
903 MixerStrip::short_astyle_string (AutoStyle style)
904 {
905         return _astyle_string (style, true);
906 }
907
908 string
909 MixerStrip::_astyle_string (AutoStyle style, bool shrt)
910 {
911         if (style & Trim) {
912                 return _("trim");
913         } else {
914                 /* XXX it might different in different languages */
915
916                 return (shrt ? _("abs") : _("abs"));
917         }
918 }
919
920 void
921 MixerStrip::diskstream_changed (void *src)
922 {
923         Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &MixerStrip::update_diskstream_display));
924 }       
925
926 void
927 MixerStrip::gain_automation_style_changed ()
928 {
929         switch (_width) {
930         case Wide:
931                 static_cast<Gtk::Label*> (gain_automation_style_button.get_child())->set_text (astyle_string(_route.gain_automation_curve().automation_style()));
932                 break;
933         case Narrow:
934                 static_cast<Gtk::Label*> (gain_automation_style_button.get_child())->set_text (short_astyle_string(_route.gain_automation_curve().automation_style()));
935                 break;
936         }
937 }
938
939 void
940 MixerStrip::gain_automation_state_changed ()
941 {
942         ENSURE_GUI_THREAD(mem_fun(*this, &MixerStrip::gain_automation_state_changed));
943         
944         bool x;
945
946         switch (_width) {
947         case Wide:
948                 static_cast<Gtk::Label*> (gain_automation_state_button.get_child())->set_text (astate_string(_route.gain_automation_curve().automation_state()));
949                 break;
950         case Narrow:
951                 static_cast<Gtk::Label*> (gain_automation_state_button.get_child())->set_text (short_astate_string(_route.gain_automation_curve().automation_state()));
952                 break;
953         }
954
955         x = (_route.gain_automation_state() != Off);
956         
957         if (gain_automation_state_button.get_active() != x) {
958                 ignore_toggle = true;
959                 gain_automation_state_button.set_active (x);
960                 ignore_toggle = false;
961         }
962
963         gpm.update_gain_sensitive ();
964         
965         /* start watching automation so that things move */
966         
967         gain_watching.disconnect();
968
969         if (x) {
970                 gain_watching = ARDOUR_UI::RapidScreenUpdate.connect (mem_fun (gpm, &GainMeter::effective_gain_display));
971         }
972 }
973
974 void
975 MixerStrip::pan_automation_style_changed ()
976 {
977         ENSURE_GUI_THREAD(mem_fun(*this, &MixerStrip::pan_automation_style_changed));
978         
979         switch (_width) {
980         case Wide:
981                 static_cast<Gtk::Label*> (pan_automation_style_button.get_child())->set_text (astyle_string(_route.panner().automation_style()));
982                 break;
983         case Narrow:
984                 static_cast<Gtk::Label*> (pan_automation_style_button.get_child())->set_text (short_astyle_string(_route.panner().automation_style()));
985                 break;
986         }
987 }
988
989 void
990 MixerStrip::pan_automation_state_changed ()
991 {
992         ENSURE_GUI_THREAD(mem_fun(*this, &MixerStrip::pan_automation_state_changed));
993         
994         bool x;
995
996         switch (_width) {
997         case Wide:
998                 static_cast<Gtk::Label*> (pan_automation_state_button.get_child())->set_text (astate_string(_route.panner().automation_state()));
999                 break;
1000         case Narrow:
1001                 static_cast<Gtk::Label*> (pan_automation_state_button.get_child())->set_text (short_astate_string(_route.panner().automation_state()));
1002                 break;
1003         }
1004
1005         /* when creating a new session, we get to create busses (and
1006            sometimes tracks) with no outputs by the time they get
1007            here.
1008         */
1009
1010         if (_route.panner().empty()) {
1011                 return;
1012         }
1013
1014         x = (_route.panner().front()->automation().automation_state() != Off);
1015
1016         if (pan_automation_state_button.get_active() != x) {
1017                 ignore_toggle = true;
1018                 pan_automation_state_button.set_active (x);
1019                 ignore_toggle = false;
1020         }
1021
1022         panners.update_pan_sensitive ();
1023         
1024         /* start watching automation so that things move */
1025         
1026         pan_watching.disconnect();
1027
1028         if (x) {
1029                 pan_watching = ARDOUR_UI::RapidScreenUpdate.connect (mem_fun (panners, &PannerUI::effective_pan_display));
1030         }
1031 }
1032
1033 void
1034 MixerStrip::input_changed (IOChange change, void *src)
1035 {
1036         Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &MixerStrip::update_input_display));
1037 }
1038
1039 void
1040 MixerStrip::output_changed (IOChange change, void *src)
1041 {
1042         Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &MixerStrip::update_output_display));
1043 }
1044
1045 void
1046 MixerStrip::comment_button_clicked ()
1047 {
1048         if (comment_window == 0) {
1049                 setup_comment_editor ();
1050         }
1051
1052         if (comment_window->is_visible()) {
1053                string str =  comment_area->get_buffer()->get_text();
1054                if (_route.comment() != str) {
1055                  _route.set_comment (str, this);
1056
1057                  switch (_width) {
1058                    
1059                  case Wide:
1060                    if (! str.empty()) {
1061                      comment_button.set_label (_("*Comments*"));
1062                    } else {
1063                      comment_button.set_label (_("Comments"));
1064                       }
1065                    break;
1066                    
1067                  case Narrow:
1068                    if (! str.empty()) {
1069                      comment_button.set_label (_("*Cmt*"));
1070                    } else {
1071                      comment_button.set_label (_("Cmt"));
1072                    } 
1073                    break;
1074                  }
1075                  
1076                  ARDOUR_UI::instance()->tooltips().set_tip (comment_button, 
1077                                                             str.empty() ? _("Click to Add/Edit Comments") : str);
1078                }
1079                comment_window->hide ();
1080                return;
1081         } 
1082         
1083         comment_window->set_position (Gtk::WIN_POS_MOUSE);
1084         comment_window->show();
1085         comment_window->present();
1086
1087 }
1088
1089 void
1090 MixerStrip::setup_comment_editor ()
1091 {
1092         string title;
1093         title = _route.name();
1094         title += _(": comment editor");
1095
1096         comment_window = new ArdourDialog (title, false);
1097         comment_area = manage (new TextView());
1098
1099         comment_area->set_name ("MixerTrackCommentArea");
1100         comment_area->set_editable (true);
1101         comment_area->get_buffer()->set_text (_route.comment());
1102         comment_area->set_size_request (200,124);
1103         comment_area->show ();
1104
1105         comment_window->get_vbox()->pack_start (*comment_area);
1106         comment_window->get_action_area()->hide();
1107 }
1108
1109 void
1110 MixerStrip::comment_changed (void *src)
1111 {
1112         ENSURE_GUI_THREAD(bind (mem_fun(*this, &MixerStrip::comment_changed), src));
1113         
1114         if (src != this) {
1115                 ignore_comment_edit = true;
1116                 if (comment_area) {
1117                         comment_area->get_buffer()->set_text (_route.comment());
1118                 }
1119                 ignore_comment_edit = false;
1120         }
1121 }
1122
1123 void
1124 MixerStrip::set_mix_group (RouteGroup *rg)
1125 {
1126         _route.set_mix_group (rg, this);
1127 }
1128
1129 void
1130 MixerStrip::add_mix_group_to_menu (RouteGroup *rg, RadioMenuItem::Group* group)
1131 {
1132         using namespace Menu_Helpers;
1133
1134         MenuList& items = group_menu->items();
1135
1136         items.push_back (RadioMenuElem (*group, rg->name(), bind (mem_fun(*this, &MixerStrip::set_mix_group), rg)));
1137
1138         if (_route.mix_group() == rg) {
1139                 static_cast<RadioMenuItem*>(&items.back())->set_active ();
1140         }
1141 }
1142
1143 bool
1144 MixerStrip::select_mix_group (GdkEventButton *ev)
1145 {
1146         using namespace Menu_Helpers;
1147
1148         if (group_menu == 0) {
1149                 group_menu = new Menu;
1150         } 
1151         group_menu->set_name ("ArdourContextMenu");
1152         MenuList& items = group_menu->items();
1153         RadioMenuItem::Group group;
1154
1155         items.clear ();
1156         items.push_back (RadioMenuElem (group, _("no group"), bind (mem_fun(*this, &MixerStrip::set_mix_group), (RouteGroup *) 0)));
1157
1158         _session.foreach_mix_group (bind (mem_fun (*this, &MixerStrip::add_mix_group_to_menu), &group));
1159         
1160         group_menu->popup (1, 0);
1161
1162         return true;
1163 }       
1164
1165 void
1166 MixerStrip::mix_group_changed (void *ignored)
1167 {
1168         ENSURE_GUI_THREAD(bind (mem_fun(*this, &MixerStrip::mix_group_changed), ignored));
1169         
1170         RouteGroup *rg = _route.mix_group();
1171         
1172         if (rg) {
1173                 group_label.set_text (rg->name());
1174         } else {
1175                 switch (_width) {
1176                 case Wide:
1177                         group_label.set_text (_("no group"));
1178                         break;
1179                 case Narrow:
1180                         group_label.set_text (_("~G"));
1181                         break;
1182                 }
1183         }
1184 }
1185
1186
1187 void 
1188 MixerStrip::route_gui_changed (string what_changed, void* ignored)
1189 {
1190         ENSURE_GUI_THREAD(bind (mem_fun(*this, &MixerStrip::route_gui_changed), what_changed, ignored));
1191         
1192         if (what_changed == "color") {
1193                 if (set_color_from_route () == 0) {
1194                         show_route_color ();
1195                 }
1196         }
1197 }
1198
1199 void
1200 MixerStrip::show_route_color ()
1201 {
1202         name_button.modify_bg (STATE_NORMAL, color());
1203         route_active_changed ();
1204 }
1205
1206 void
1207 MixerStrip::show_passthru_color ()
1208 {
1209         route_active_changed ();
1210 }
1211
1212 void
1213 MixerStrip::build_route_ops_menu ()
1214 {
1215         using namespace Menu_Helpers;
1216
1217         route_ops_menu = manage (new Menu);
1218         route_ops_menu->set_name ("ArdourContextMenu");
1219
1220         MenuList& items = route_ops_menu->items();
1221         
1222         items.push_back (MenuElem (_("Rename"), mem_fun(*this, &RouteUI::route_rename)));
1223         items.push_back (SeparatorElem());
1224         items.push_back (CheckMenuElem (_("Active"), mem_fun (*this, &RouteUI::toggle_route_active)));
1225         route_active_menu_item = dynamic_cast<CheckMenuItem *> (&items.back());
1226         route_active_menu_item->set_active (_route.active());
1227         items.push_back (SeparatorElem());
1228         items.push_back (CheckMenuElem (_("Invert Polarity"), mem_fun (*this, &RouteUI::toggle_polarity)));
1229         polarity_menu_item = dynamic_cast<CheckMenuItem *> (&items.back());
1230         polarity_menu_item->set_active (_route.phase_invert());
1231
1232         build_remote_control_menu ();
1233         
1234         items.push_back (SeparatorElem());
1235         items.push_back (MenuElem (_("Remote Control ID"), *remote_control_menu));
1236
1237         items.push_back (SeparatorElem());
1238         items.push_back (MenuElem (_("Remove"), mem_fun(*this, &RouteUI::remove_this_route)));
1239 }
1240
1241 gint
1242 MixerStrip::name_button_button_release (GdkEventButton* ev)
1243 {
1244         if (ev->button == 3) {
1245                 list_route_operations ();
1246         }
1247         return FALSE;
1248 }
1249
1250 void
1251 MixerStrip::list_route_operations ()
1252 {
1253         if (route_ops_menu == 0) {
1254                 build_route_ops_menu ();
1255         }
1256         
1257         refresh_remote_control_menu();
1258
1259         route_ops_menu->popup (1, 0);
1260 }
1261
1262
1263 void
1264 MixerStrip::speed_adjustment_changed ()
1265 {
1266         /* since there is a usable speed adjustment, there has to be a diskstream */
1267         if (!ignore_speed_adjustment) {
1268                 get_diskstream()->set_speed (speed_adjustment.get_value());
1269         }
1270 }
1271
1272 void
1273 MixerStrip::speed_changed ()
1274 {
1275         Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &MixerStrip::update_speed_display));
1276 }
1277
1278 void
1279 MixerStrip::update_speed_display ()
1280 {
1281         float val;
1282         
1283         val = get_diskstream()->speed();
1284
1285         if (val != 1.0) {
1286                 speed_spinner.set_name ("MixerStripSpeedBaseNotOne");
1287         } else {
1288                 speed_spinner.set_name ("MixerStripSpeedBase");
1289         }
1290
1291         if (speed_adjustment.get_value() != val) {
1292                 ignore_speed_adjustment = true;
1293                 speed_adjustment.set_value (val);
1294                 ignore_speed_adjustment = false;
1295         }
1296 }                       
1297
1298
1299 void
1300 MixerStrip::set_selected (bool yn)
1301 {
1302         AxisView::set_selected (yn);
1303         if (_selected) {
1304                 global_frame.set_shadow_type (Gtk::SHADOW_ETCHED_OUT);
1305                 global_frame.set_name ("MixerStripSelectedFrame");
1306         } else {
1307                 global_frame.set_shadow_type (Gtk::SHADOW_IN);
1308                 global_frame.set_name ("MixerStripFrame");
1309         }
1310         global_frame.queue_draw ();
1311 }
1312
1313 void
1314 MixerStrip::name_changed (void *src)
1315 {
1316         switch (_width) {
1317         case Wide:
1318                 RouteUI::name_changed (src);
1319                 break;
1320         case Narrow:
1321                 name_label.set_text (short_version (_route.name(), 5));
1322                 break;
1323         }
1324 }
1325
1326 void
1327 MixerStrip::width_clicked ()
1328 {
1329         switch (_width) {
1330         case Wide:
1331                 set_width (Narrow);
1332                 break;
1333         case Narrow:
1334                 set_width (Wide);
1335                 break;
1336         }
1337 }
1338
1339 void
1340 MixerStrip::hide_clicked ()
1341 {
1342         if (_embedded) {
1343                  Hiding(); /* EMIT_SIGNAL */
1344         } else {
1345                 _mixer.hide_strip (this);
1346         }
1347 }
1348
1349 void
1350 MixerStrip::set_embedded (bool yn)
1351 {
1352         _embedded = yn;
1353 }
1354
1355 void
1356 MixerStrip::map_frozen ()
1357 {
1358         ENSURE_GUI_THREAD (mem_fun(*this, &MixerStrip::map_frozen));
1359
1360         AudioTrack* at = dynamic_cast<AudioTrack*>(&_route);
1361
1362         if (at) {
1363                 switch (at->freeze_state()) {
1364                 case AudioTrack::Frozen:
1365                         pre_redirect_box.set_sensitive (false);
1366                         post_redirect_box.set_sensitive (false);
1367                         speed_spinner.set_sensitive (false);
1368                         break;
1369                 default:
1370                         pre_redirect_box.set_sensitive (true);
1371                         post_redirect_box.set_sensitive (true);
1372                         speed_spinner.set_sensitive (true);
1373                         break;
1374                 }
1375         }
1376         _route.foreach_redirect (this, &MixerStrip::hide_redirect_editor);
1377 }
1378
1379 void
1380 MixerStrip::hide_redirect_editor (Redirect* redirect)
1381 {
1382         void* gui = redirect->get_gui ();
1383         
1384         if (gui) {
1385                 static_cast<Gtk::Widget*>(gui)->hide ();
1386         }
1387 }
1388
1389 void
1390 MixerStrip::route_active_changed ()
1391 {
1392         RouteUI::route_active_changed ();
1393
1394         if (is_audio_track()) {
1395                 if (_route.active()) {
1396                         set_name ("AudioTrackStripBase");
1397                         gpm.set_meter_strip_name ("AudioTrackStripBase");
1398                 } else {
1399                         set_name ("AudioTrackStripBaseInactive");
1400                         gpm.set_meter_strip_name ("AudioTrackStripBaseInactive");
1401                 }
1402                 gpm.set_fader_name ("AudioTrackFader");
1403         } else {
1404                 if (_route.active()) {
1405                         set_name ("AudioBusStripBase");
1406                         gpm.set_meter_strip_name ("AudioBusStripBase");
1407                 } else {
1408                         set_name ("AudioBusStripBaseInactive");
1409                         gpm.set_meter_strip_name ("AudioBusStripBaseInactive");
1410                 }
1411                 gpm.set_fader_name ("AudioBusFader");
1412         }
1413 }
1414
1415 RouteGroup*
1416 MixerStrip::mix_group() const
1417 {
1418         return _route.mix_group();
1419 }