54519546d0265144ca3a73eaf01445ff638347c4
[ardour.git] / gtk2_ardour / gain_meter.cc
1 /*
2   Copyright (C) 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 */
19
20 #include <limits.h>
21
22 #include "ardour/amp.h"
23 #include "ardour/route_group.h"
24 #include "ardour/session_route.h"
25 #include "ardour/dB.h"
26 #include "ardour/utils.h"
27
28 #include <gtkmm/style.h>
29 #include <gdkmm/color.h>
30 #include <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/fastmeter.h>
32 #include <gtkmm2ext/barcontroller.h>
33 #include <gtkmm2ext/gtk_ui.h>
34 #include "midi++/manager.h"
35 #include "pbd/fastlog.h"
36 #include "pbd/stacktrace.h"
37
38 #include "ardour_ui.h"
39 #include "gain_meter.h"
40 #include "global_signals.h"
41 #include "logmeter.h"
42 #include "gui_thread.h"
43 #include "keyboard.h"
44 #include "public_editor.h"
45 #include "utils.h"
46
47 #include "ardour/session.h"
48 #include "ardour/route.h"
49 #include "ardour/meter.h"
50
51 #include "i18n.h"
52
53 using namespace ARDOUR;
54 using namespace PBD;
55 using namespace Gtkmm2ext;
56 using namespace Gtk;
57 using namespace std;
58 using Gtkmm2ext::Keyboard;
59
60 sigc::signal<void> GainMeterBase::ResetAllPeakDisplays;
61 sigc::signal<void,RouteGroup*> GainMeterBase::ResetGroupPeakDisplays;
62
63 GainMeter::MetricPatterns GainMeter::metric_patterns;
64 Glib::RefPtr<Gdk::Pixbuf> GainMeter::slider;
65 Glib::RefPtr<Gdk::Pixbuf> GainMeter::slider_desensitised;
66
67
68 void
69 GainMeter::setup_slider_pix ()
70 {
71         if ((slider = ::get_icon ("fader_belt")) == 0) {
72                 throw failed_constructor();
73         }
74
75         if ((slider_desensitised = ::get_icon ("fader_belt_desensitised")) == 0) {
76                 throw failed_constructor();
77         }
78 }
79
80 GainMeterBase::GainMeterBase (Session* s,
81                               const Glib::RefPtr<Gdk::Pixbuf>& pix,
82                               const Glib::RefPtr<Gdk::Pixbuf>& pix_desensitised,
83                               bool horizontal,
84                               int fader_length)
85         : gain_adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain()), 0.0, 1.0, 0.01, 0.1)
86         , gain_automation_style_button ("")
87         , gain_automation_state_button ("")
88         , style_changed (false)
89         , dpi_changed (false)
90         , _data_type (DataType::AUDIO)
91
92 {
93         using namespace Menu_Helpers;
94
95         set_session (s);
96
97         ignore_toggle = false;
98         meter_menu = 0;
99         next_release_selects = false;
100         _width = Wide;
101
102         if (horizontal) {
103                 gain_slider = manage (new HSliderController (pix,
104                                                              pix_desensitised,
105                                                              &gain_adjustment,
106                                                              fader_length,
107                                                              false));
108         } else {
109                 gain_slider = manage (new VSliderController (pix,
110                                                              pix_desensitised,
111                                                              &gain_adjustment,
112                                                              fader_length,
113                                                              false));
114         }
115
116         level_meter = new LevelMeter(_session);
117
118         level_meter->ButtonPress.connect_same_thread (_level_meter_connection, boost::bind (&GainMeterBase::level_meter_button_press, this, _1));
119         meter_metric_area.signal_button_press_event().connect (sigc::mem_fun (*this, &GainMeterBase::level_meter_button_press));
120         meter_metric_area.add_events (Gdk::BUTTON_PRESS_MASK);
121
122         gain_slider->signal_button_press_event().connect (sigc::mem_fun(*this, &GainMeter::gain_slider_button_press), false);
123         gain_slider->signal_button_release_event().connect (sigc::mem_fun(*this, &GainMeter::gain_slider_button_release), false);
124         gain_slider->set_name ("GainFader");
125
126         gain_display.set_name ("MixerStripGainDisplay");
127         set_size_request_to_display_given_text (gain_display, "-80.g", 2, 6); /* note the descender */
128         gain_display.signal_activate().connect (sigc::mem_fun (*this, &GainMeter::gain_activated));
129         gain_display.signal_focus_in_event().connect (sigc::mem_fun (*this, &GainMeter::gain_focused), false);
130         gain_display.signal_focus_out_event().connect (sigc::mem_fun (*this, &GainMeter::gain_focused), false);
131
132         peak_display.set_name ("MixerStripPeakDisplay");
133         set_size_request_to_display_given_text (peak_display, "-80.g", 2, 6); /* note the descender */
134         max_peak = minus_infinity();
135         peak_display.set_label (_("-inf"));
136         peak_display.unset_flags (Gtk::CAN_FOCUS);
137
138         gain_automation_style_button.set_name ("mixer strip button");
139         gain_automation_state_button.set_name ("mixer strip button");
140
141         ARDOUR_UI::instance()->set_tip (gain_automation_state_button, _("Fader automation mode"));
142         ARDOUR_UI::instance()->set_tip (gain_automation_style_button, _("Fader automation type"));
143
144         gain_automation_style_button.unset_flags (Gtk::CAN_FOCUS);
145         gain_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
146
147         gain_automation_state_button.set_size_request(15, 15);
148         gain_automation_style_button.set_size_request(15, 15);
149
150         gain_astyle_menu.items().push_back (MenuElem (_("Trim")));
151         gain_astyle_menu.items().push_back (MenuElem (_("Abs")));
152
153         gain_astate_menu.set_name ("ArdourContextMenu");
154         gain_astyle_menu.set_name ("ArdourContextMenu");
155
156         gain_adjustment.signal_value_changed().connect (sigc::mem_fun(*this, &GainMeterBase::gain_adjusted));
157         peak_display.signal_button_release_event().connect (sigc::mem_fun(*this, &GainMeterBase::peak_button_release), false);
158         gain_display.signal_key_press_event().connect (sigc::mem_fun(*this, &GainMeterBase::gain_key_press), false);
159
160         ResetAllPeakDisplays.connect (sigc::mem_fun(*this, &GainMeterBase::reset_peak_display));
161         ResetGroupPeakDisplays.connect (sigc::mem_fun(*this, &GainMeterBase::reset_group_peak_display));
162
163         UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &GainMeterBase::on_theme_changed));
164         ColorsChanged.connect (sigc::bind(sigc::mem_fun (*this, &GainMeterBase::color_handler), false));
165         DPIReset.connect (sigc::bind(sigc::mem_fun (*this, &GainMeterBase::color_handler), true));
166         
167 //      PBD::ScopedConnection _config_connection;
168 //      Config->ParameterChanged.connect ( _config_connection, MISSING_INVALIDATOR, boost::bind(&GainMeterBase::set_flat_buttons, this, _1), gui_context() );
169 }
170
171 void
172 GainMeterBase::set_flat_buttons ()
173 {
174 printf("set_flat_butt\n");
175 //      gain_slider->set_flat_buttons( ARDOUR_UI::config()->flat_buttons.get() );
176 }
177
178 GainMeterBase::~GainMeterBase ()
179 {
180         delete meter_menu;
181         delete level_meter;
182 }
183
184 void
185 GainMeterBase::set_controls (boost::shared_ptr<Route> r,
186                              boost::shared_ptr<PeakMeter> pm,
187                              boost::shared_ptr<Amp> amp)
188 {
189         connections.clear ();
190         model_connections.drop_connections ();
191
192         if (!pm && !amp) {
193                 level_meter->set_meter (0);
194                 gain_slider->set_controllable (boost::shared_ptr<PBD::Controllable>());
195                 _meter.reset ();
196                 _amp.reset ();
197                 _route.reset ();
198                 return;
199         }
200
201         _meter = pm;
202         _amp = amp;
203         _route = r;
204
205         level_meter->set_meter (pm.get());
206         gain_slider->set_controllable (amp->gain_control());
207
208         if (amp) {
209                 amp->ConfigurationChanged.connect (
210                         model_connections, invalidator (*this), boost::bind (&GainMeterBase::setup_gain_adjustment, this), gui_context ()
211                         );
212         }
213
214         setup_gain_adjustment ();
215
216         if (!_route || !_route->is_hidden()) {
217
218                 using namespace Menu_Helpers;
219
220                 gain_astate_menu.items().clear ();
221
222                 gain_astate_menu.items().push_back (MenuElem (S_("Automation|Manual"),
223                                                               sigc::bind (sigc::mem_fun (*(amp.get()), &Automatable::set_parameter_automation_state),
224                                                                           Evoral::Parameter(GainAutomation), (AutoState) ARDOUR::Off)));
225                 gain_astate_menu.items().push_back (MenuElem (_("Play"),
226                                                               sigc::bind (sigc::mem_fun (*(amp.get()), &Automatable::set_parameter_automation_state),
227                                                                     Evoral::Parameter(GainAutomation), (AutoState) Play)));
228                 gain_astate_menu.items().push_back (MenuElem (_("Write"),
229                                                               sigc::bind (sigc::mem_fun (*(amp.get()), &Automatable::set_parameter_automation_state),
230                                                                     Evoral::Parameter(GainAutomation), (AutoState) Write)));
231                 gain_astate_menu.items().push_back (MenuElem (_("Touch"),
232                                                               sigc::bind (sigc::mem_fun (*(amp.get()), &Automatable::set_parameter_automation_state),
233                                                                     Evoral::Parameter(GainAutomation), (AutoState) Touch)));
234
235                 connections.push_back (gain_automation_style_button.signal_button_press_event().connect (sigc::mem_fun(*this, &GainMeterBase::gain_automation_style_button_event), false));
236                 connections.push_back (gain_automation_state_button.signal_button_press_event().connect (sigc::mem_fun(*this, &GainMeterBase::gain_automation_state_button_event), false));
237
238                 boost::shared_ptr<AutomationControl> gc = amp->gain_control();
239
240                 gc->alist()->automation_state_changed.connect (model_connections, invalidator (*this), boost::bind (&GainMeter::gain_automation_state_changed, this), gui_context());
241                 gc->alist()->automation_style_changed.connect (model_connections, invalidator (*this), boost::bind (&GainMeter::gain_automation_style_changed, this), gui_context());
242
243                 gain_automation_state_changed ();
244         }
245
246         amp->gain_control()->Changed.connect (model_connections, invalidator (*this), boost::bind (&GainMeterBase::gain_changed, this), gui_context());
247
248         gain_changed ();
249         show_gain ();
250         update_gain_sensitive ();
251 }
252
253 void
254 GainMeterBase::setup_gain_adjustment ()
255 {
256         if (!_amp) {
257                 return;
258         }
259
260         if (_previous_amp_output_streams == _amp->output_streams ()) {
261                 return;
262         }
263
264         ignore_toggle = true;
265
266         if (_amp->output_streams().n_midi() == 0) {
267                 _data_type = DataType::AUDIO;
268                 gain_adjustment.set_lower (0.0);
269                 gain_adjustment.set_upper (1.0);
270                 gain_adjustment.set_step_increment (0.01);
271                 gain_adjustment.set_page_increment (0.1);
272                 gain_slider->set_default_value (gain_to_slider_position (1));
273         } else {
274                 _data_type = DataType::MIDI;
275                 gain_adjustment.set_lower (0.0);
276                 gain_adjustment.set_upper (2.0);
277                 gain_adjustment.set_step_increment (1.0/128.0);
278                 gain_adjustment.set_page_increment (10.0/128.0);
279                 gain_slider->set_default_value (1.0);
280         }
281
282         ignore_toggle = false;
283
284         effective_gain_display ();
285         
286         _previous_amp_output_streams = _amp->output_streams ();
287 }
288
289 void
290 GainMeterBase::hide_all_meters ()
291 {
292         level_meter->hide_meters();
293 }
294
295 void
296 GainMeter::hide_all_meters ()
297 {
298         bool remove_metric_area = false;
299
300         GainMeterBase::hide_all_meters ();
301
302         if (remove_metric_area) {
303                 if (meter_metric_area.get_parent()) {
304                         level_meter->remove (meter_metric_area);
305                 }
306         }
307 }
308
309 void
310 GainMeterBase::setup_meters (int len)
311 {
312         level_meter->setup_meters(len, 5);
313 }
314
315 void
316 GainMeter::setup_meters (int len)
317 {
318         if (!meter_metric_area.get_parent()) {
319                 level_meter->pack_end (meter_metric_area, false, false);
320                 meter_metric_area.show_all ();
321         }
322         GainMeterBase::setup_meters (len);
323 }
324
325 bool
326 GainMeterBase::gain_key_press (GdkEventKey* ev)
327 {
328         if (key_is_legal_for_numeric_entry (ev->keyval)) {
329                 /* drop through to normal handling */
330                 return false;
331         }
332         /* illegal key for gain entry */
333         return true;
334 }
335
336 bool
337 GainMeterBase::peak_button_release (GdkEventButton* ev)
338 {
339         /* reset peak label */
340
341         if (ev->button == 1 && Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
342                 ResetAllPeakDisplays ();
343         } else if (ev->button == 1 && Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
344                 if (_route) {
345                         ResetGroupPeakDisplays (_route->route_group());
346                 }
347         } else {
348                 reset_peak_display ();
349         }
350
351         return true;
352 }
353
354 void
355 GainMeterBase::reset_peak_display ()
356 {
357         _meter->reset_max();
358         level_meter->clear_meters();
359         max_peak = -INFINITY;
360         peak_display.set_label (_("-Inf"));
361         peak_display.set_name ("MixerStripPeakDisplay");
362 }
363
364 void
365 GainMeterBase::reset_group_peak_display (RouteGroup* group)
366 {
367         if (_route && group == _route->route_group()) {
368                 reset_peak_display ();
369         }
370 }
371
372 void
373 GainMeterBase::popup_meter_menu (GdkEventButton *ev)
374 {
375         using namespace Menu_Helpers;
376
377         if (meter_menu == 0) {
378                 meter_menu = new Gtk::Menu;
379                 MenuList& items = meter_menu->items();
380
381                 items.push_back (MenuElem ("-inf .. +0dBFS"));
382                 items.push_back (MenuElem ("-10dB .. +0dBFS"));
383                 items.push_back (MenuElem ("-4 .. +0dBFS"));
384                 items.push_back (SeparatorElem());
385                 items.push_back (MenuElem ("-inf .. -2dBFS"));
386                 items.push_back (MenuElem ("-10dB .. -2dBFS"));
387                 items.push_back (MenuElem ("-4 .. -2dBFS"));
388         }
389
390         meter_menu->popup (1, ev->time);
391 }
392
393 bool
394 GainMeterBase::gain_focused (GdkEventFocus* ev)
395 {
396         if (ev->in) {
397                 gain_display.select_region (0, -1);
398         } else {
399                 gain_display.select_region (0, 0);
400         }
401         return false;
402 }
403
404 void
405 GainMeterBase::gain_activated ()
406 {
407         float f;
408
409         {
410                 // Switch to user's preferred locale so that
411                 // if they use different LC_NUMERIC conventions,
412                 // we will honor them.
413
414                 PBD::LocaleGuard lg ("");
415                 if (sscanf (gain_display.get_text().c_str(), "%f", &f) != 1) {
416                         return;
417                 }
418         }
419
420         /* clamp to displayable values */
421         if (_data_type == DataType::AUDIO) {
422                 f = min (f, 6.0f);
423                 _amp->set_gain (dB_to_coefficient(f), this);
424         } else {
425                 f = min (fabs (f), 2.0f);
426                 _amp->set_gain (f, this);
427         }
428
429         if (gain_display.has_focus()) {
430                 PublicEditor::instance().reset_focus();
431         }
432 }
433
434 void
435 GainMeterBase::show_gain ()
436 {
437         char buf[32];
438
439         float v = gain_adjustment.get_value();
440
441         switch (_data_type) {
442         case DataType::AUDIO:
443                 if (v == 0.0) {
444                         strcpy (buf, _("-inf"));
445                 } else {
446                         snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (v, Config->get_max_gain())));
447                 }
448                 break;
449         case DataType::MIDI:
450                 snprintf (buf, sizeof (buf), "%.1f", v);
451                 break;
452         }
453
454         gain_display.set_text (buf);
455 }
456
457 void
458 GainMeterBase::gain_adjusted ()
459 {
460         gain_t value;
461
462         /* convert from adjustment range (0..1) to gain coefficient */
463
464         if (_data_type == DataType::AUDIO) {
465                 value = slider_position_to_gain_with_max (gain_adjustment.get_value(), Config->get_max_gain());
466         } else {
467                 value = gain_adjustment.get_value();
468         }
469         
470         if (!ignore_toggle) {
471                 if (_route && _route->amp() == _amp) {
472                         _route->set_gain (value, this);
473                 } else {
474                         _amp->set_gain (value, this);
475                 }
476         }
477
478         show_gain ();
479 }
480
481 void
482 GainMeterBase::effective_gain_display ()
483 {
484         float value = 0.0;
485
486         switch (_data_type) {
487         case DataType::AUDIO:
488                 value = gain_to_slider_position_with_max (_amp->gain(), Config->get_max_gain());
489                 break;
490         case DataType::MIDI:
491                 value = _amp->gain ();
492                 break;
493         }
494
495         if (gain_adjustment.get_value() != value) {
496                 ignore_toggle = true;
497                 gain_adjustment.set_value (value);
498                 ignore_toggle = false;
499         }
500 }
501
502 void
503 GainMeterBase::gain_changed ()
504 {
505         Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&GainMeterBase::effective_gain_display, this));
506 }
507
508 void
509 GainMeterBase::set_meter_strip_name (const char * name)
510 {
511         meter_metric_area.set_name (name);
512 }
513
514 void
515 GainMeterBase::set_fader_name (const char * name)
516 {
517         uint32_t rgb_active = rgba_from_style (name, 0xff, 0, 0xff, 0, "bg", STATE_ACTIVE, false);
518         uint32_t rgb_normal = rgba_from_style (name, 0xff, 0xff, 0, 0, "bg", STATE_NORMAL, false);
519
520         gain_slider->set_border_colors (rgb_normal, rgb_active);
521 }
522
523 void
524 GainMeterBase::update_gain_sensitive ()
525 {
526         bool x = !(_amp->gain_control()->alist()->automation_state() & Play);
527         static_cast<Gtkmm2ext::SliderController*>(gain_slider)->set_sensitive (x);
528 }
529
530 static MeterPoint
531 next_meter_point (MeterPoint mp)
532 {
533         switch (mp) {
534         case MeterInput:
535                 return MeterPreFader;
536                 break;
537
538         case MeterPreFader:
539                 return MeterPostFader;
540                 break;
541
542         case MeterPostFader:
543                 return MeterOutput;
544                 break;
545
546         case MeterOutput:
547                 return MeterCustom;
548                 break;
549
550         case MeterCustom:
551                 return MeterInput;
552                 break;
553         }
554
555         /*NOTREACHED*/
556         return MeterInput;
557 }
558
559 gint
560 GainMeterBase::meter_press(GdkEventButton* ev)
561 {
562         wait_for_release = false;
563
564         if (!_route) {
565                 return FALSE;
566         }
567
568         if (!ignore_toggle) {
569
570                 if (Keyboard::is_context_menu_event (ev)) {
571
572                         // no menu at this time.
573
574                 } else {
575
576                         if (Keyboard::is_button2_event(ev)) {
577
578                                 // Primary-button2 click is the midi binding click
579                                 // button2-click is "momentary"
580
581                                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier))) {
582                                         wait_for_release = true;
583                                         old_meter_point = _route->meter_point ();
584                                 }
585                         }
586
587                         if (_route && (ev->button == 1 || Keyboard::is_button2_event (ev))) {
588
589                                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier))) {
590
591                                         /* Primary+Tertiary-click applies change to all routes */
592
593                                         _session->foreach_route (this, &GainMeterBase::set_meter_point, next_meter_point (_route->meter_point()));
594
595
596                                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::PrimaryModifier)) {
597
598                                         /* Primary-click: solo mix group.
599                                            NOTE: Primary-button2 is MIDI learn.
600                                         */
601
602                                         if (ev->button == 1) {
603                                                 set_route_group_meter_point (*_route, next_meter_point (_route->meter_point()));
604                                         }
605
606                                 } else {
607
608                                         /* click: change just this route */
609
610                                         // XXX no undo yet
611
612                                         _route->set_meter_point (next_meter_point (_route->meter_point()));
613                                 }
614                         }
615                 }
616         }
617
618         return true;
619
620 }
621
622 gint
623 GainMeterBase::meter_release(GdkEventButton*)
624 {
625         if (!ignore_toggle) {
626                 if (wait_for_release) {
627                         wait_for_release = false;
628
629                         if (_route) {
630                                 set_meter_point (*_route, old_meter_point);
631                         }
632                 }
633         }
634
635         return true;
636 }
637
638 void
639 GainMeterBase::set_meter_point (Route& route, MeterPoint mp)
640 {
641         route.set_meter_point (mp);
642 }
643
644 void
645 GainMeterBase::set_route_group_meter_point (Route& route, MeterPoint mp)
646 {
647         RouteGroup* route_group;
648
649         if ((route_group = route.route_group ()) != 0) {
650                 route_group->foreach_route (boost::bind (&Route::set_meter_point, _1, mp, false));
651         } else {
652                 route.set_meter_point (mp);
653         }
654 }
655
656 void
657 GainMeterBase::meter_point_clicked ()
658 {
659         if (_route) {
660                 /* WHAT? */
661         }
662 }
663
664 bool
665 GainMeterBase::gain_slider_button_press (GdkEventButton* ev)
666 {
667         switch (ev->type) {
668         case GDK_BUTTON_PRESS:
669                 _amp->gain_control()->start_touch (_amp->session().transport_frame());
670                 break;
671         default:
672                 return false;
673         }
674
675         return false;
676 }
677
678 bool
679 GainMeterBase::gain_slider_button_release (GdkEventButton*)
680 {
681         _amp->gain_control()->stop_touch (false, _amp->session().transport_frame());
682         return false;
683 }
684
685 gint
686 GainMeterBase::gain_automation_state_button_event (GdkEventButton *ev)
687 {
688         if (ev->type == GDK_BUTTON_RELEASE) {
689                 return TRUE;
690         }
691
692         switch (ev->button) {
693                 case 1:
694                         gain_astate_menu.popup (1, ev->time);
695                         break;
696                 default:
697                         break;
698         }
699
700         return TRUE;
701 }
702
703 gint
704 GainMeterBase::gain_automation_style_button_event (GdkEventButton *ev)
705 {
706         if (ev->type == GDK_BUTTON_RELEASE) {
707                 return TRUE;
708         }
709
710         switch (ev->button) {
711         case 1:
712                 gain_astyle_menu.popup (1, ev->time);
713                 break;
714         default:
715                 break;
716         }
717         return TRUE;
718 }
719
720 string
721 GainMeterBase::astate_string (AutoState state)
722 {
723         return _astate_string (state, false);
724 }
725
726 string
727 GainMeterBase::short_astate_string (AutoState state)
728 {
729         return _astate_string (state, true);
730 }
731
732 string
733 GainMeterBase::_astate_string (AutoState state, bool shrt)
734 {
735         string sstr;
736
737         switch (state) {
738         case ARDOUR::Off:
739                 sstr = (shrt ? "M" : _("M"));
740                 break;
741         case Play:
742                 sstr = (shrt ? "P" : _("P"));
743                 break;
744         case Touch:
745                 sstr = (shrt ? "T" : _("T"));
746                 break;
747         case Write:
748                 sstr = (shrt ? "W" : _("W"));
749                 break;
750         }
751
752         return sstr;
753 }
754
755 string
756 GainMeterBase::astyle_string (AutoStyle style)
757 {
758         return _astyle_string (style, false);
759 }
760
761 string
762 GainMeterBase::short_astyle_string (AutoStyle style)
763 {
764         return _astyle_string (style, true);
765 }
766
767 string
768 GainMeterBase::_astyle_string (AutoStyle style, bool shrt)
769 {
770         if (style & Trim) {
771                 return _("Trim");
772         } else {
773                 /* XXX it might different in different languages */
774
775                 return (shrt ? _("Abs") : _("Abs"));
776         }
777 }
778
779 void
780 GainMeterBase::gain_automation_style_changed ()
781 {
782         switch (_width) {
783         case Wide:
784                 gain_automation_style_button.set_text (astyle_string(_amp->gain_control()->alist()->automation_style()));
785                 break;
786         case Narrow:
787                 gain_automation_style_button.set_text  (short_astyle_string(_amp->gain_control()->alist()->automation_style()));
788                 break;
789         }
790 }
791
792 void
793 GainMeterBase::gain_automation_state_changed ()
794 {
795         ENSURE_GUI_THREAD (*this, &GainMeterBase::gain_automation_state_changed)
796
797         bool x;
798
799         switch (_width) {
800         case Wide:
801                 gain_automation_state_button.set_text (astate_string(_amp->gain_control()->alist()->automation_state()));
802                 break;
803         case Narrow:
804                 gain_automation_state_button.set_text (short_astate_string(_amp->gain_control()->alist()->automation_state()));
805                 break;
806         }
807
808         x = (_amp->gain_control()->alist()->automation_state() != ARDOUR::Off);
809
810         if (gain_automation_state_button.get_active() != x) {
811                 ignore_toggle = true;
812                 gain_automation_state_button.set_active (x);
813                 ignore_toggle = false;
814         }
815
816         update_gain_sensitive ();
817
818         /* start watching automation so that things move */
819
820         gain_watching.disconnect();
821
822         if (x) {
823                 gain_watching = ARDOUR_UI::RapidScreenUpdate.connect (sigc::mem_fun (*this, &GainMeterBase::effective_gain_display));
824         }
825 }
826
827 void
828 GainMeterBase::update_meters()
829 {
830         char buf[32];
831         float mpeak = level_meter->update_meters();
832
833         if (mpeak > max_peak) {
834                 max_peak = mpeak;
835                 if (mpeak <= -200.0f) {
836                         peak_display.set_label (_("-inf"));
837                 } else {
838                         snprintf (buf, sizeof(buf), "%.1f", mpeak);
839                         peak_display.set_label (buf);
840                 }
841
842                 if (mpeak >= 0.0f) {
843                         peak_display.set_name ("MixerStripPeakDisplayPeak");
844                 }
845         }
846 }
847
848 void GainMeterBase::color_handler(bool dpi)
849 {
850         color_changed = true;
851         dpi_changed = (dpi) ? true : false;
852         setup_meters();
853 }
854
855 void
856 GainMeterBase::set_width (Width w, int len)
857 {
858         _width = w;
859         level_meter->setup_meters (len);
860 }
861
862
863 void
864 GainMeterBase::on_theme_changed()
865 {
866         style_changed = true;
867 }
868
869 GainMeter::GainMeter (Session* s, int fader_length)
870         : GainMeterBase (s, slider, slider_desensitised, false, fader_length)
871         , gain_display_box(true, 0)
872         , hbox(true, 2)
873 {
874 //      gain_display_box.pack_start (gain_display, true, true);
875
876         meter_metric_area.set_name ("AudioTrackMetrics");
877         set_size_request_to_display_given_text (meter_metric_area, "-127", 0, 0);
878
879         gain_automation_style_button.set_name ("mixer strip button");
880         gain_automation_state_button.set_name ("mixer strip button");
881
882         ARDOUR_UI::instance()->set_tip (gain_automation_state_button, _("Fader automation mode"));
883         ARDOUR_UI::instance()->set_tip (gain_automation_style_button, _("Fader automation type"));
884
885         gain_automation_style_button.unset_flags (Gtk::CAN_FOCUS);
886         gain_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
887
888         gain_automation_state_button.set_size_request(15, 15);
889         gain_automation_style_button.set_size_request(15, 15);
890
891         fader_vbox = manage (new Gtk::VBox());
892         fader_vbox->set_spacing (0);
893         fader_vbox->pack_start (*gain_slider, true, true);
894
895         fader_alignment.set (0.5, 0.5, 0.0, 1.0);
896         fader_alignment.add (*fader_vbox);
897
898         hbox.pack_start (fader_alignment, true, true);
899
900         set_spacing (2);
901
902         pack_start (gain_display_box, Gtk::PACK_SHRINK);
903         pack_start (hbox, Gtk::PACK_SHRINK);
904
905         meter_alignment.set (0.5, 0.5, 0.0, 1.0);
906         meter_alignment.add (*level_meter);
907
908         meter_metric_area.signal_expose_event().connect (
909                 sigc::mem_fun(*this, &GainMeter::meter_metrics_expose));
910 }
911
912 void
913 GainMeter::set_controls (boost::shared_ptr<Route> r,
914                          boost::shared_ptr<PeakMeter> meter,
915                          boost::shared_ptr<Amp> amp)
916 {
917         if (meter_alignment.get_parent()) {
918                 hbox.remove (meter_alignment);
919         }
920
921 //      if (peak_display.get_parent()) {
922 //              gain_display_box.remove (peak_display);
923 //      }
924
925 //      if (gain_automation_state_button.get_parent()) {
926 //              fader_vbox->remove (gain_automation_state_button);
927 //      }
928
929         GainMeterBase::set_controls (r, meter, amp);
930
931         if (_meter) {
932                 _meter->ConfigurationChanged.connect (
933                         model_connections, invalidator (*this), boost::bind (&GainMeter::meter_configuration_changed, this, _1), gui_context()
934                         );
935
936                 meter_configuration_changed (_meter->input_streams ());
937         }
938
939
940         /*
941            if we have a non-hidden route (ie. we're not the click or the auditioner),
942            pack some route-dependent stuff.
943         */
944
945 //      gain_display_box.pack_end (peak_display, true, true);
946         hbox.pack_start (meter_alignment, true, true);
947
948 //      if (r && !r->is_hidden()) {
949 //              fader_vbox->pack_start (gain_automation_state_button, false, false, 0);
950 //      }
951
952         setup_meters ();
953         hbox.show_all ();
954 }
955
956 int
957 GainMeter::get_gm_width ()
958 {
959         Gtk::Requisition sz;
960         hbox.size_request (sz);
961         return sz.width;
962 }
963
964 cairo_pattern_t*
965 GainMeter::render_metrics (Gtk::Widget& w, vector<DataType> types)
966 {
967         Glib::RefPtr<Gdk::Window> win (w.get_window());
968
969         gint width, height;
970         win->get_size (width, height);
971
972         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
973         cairo_t* cr = cairo_create (surface);
974         PangoLayout* layout = gtk_widget_create_pango_layout (w.gobj(), "");
975
976         cairo_move_to (cr, 0, 0);
977         cairo_rectangle (cr, 0, 0, width, height);
978         {
979                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
980                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
981         }
982         cairo_fill (cr);
983
984         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
985
986                 Gdk::Color c;
987
988                 if (types.size() > 1) {
989                         /* we're overlaying more than 1 set of marks, so use different colours */
990                         Gdk::Color c;
991                         switch (*i) {
992                         case DataType::AUDIO:
993                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
994                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
995                                 break;
996                         case DataType::MIDI:
997                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
998                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
999                                 break;
1000                         }
1001                 } else {
1002                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
1003                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
1004                 }
1005
1006                 vector<int> points;
1007
1008                 switch (*i) {
1009                 case DataType::AUDIO:
1010                         points.push_back (-50);
1011                         points.push_back (-40);
1012                         points.push_back (-30);
1013                         points.push_back (-20);
1014                         points.push_back (-10);
1015                         points.push_back (-3);
1016                         points.push_back (0);
1017                         points.push_back (4);
1018                         break;
1019
1020                 case DataType::MIDI:
1021                         points.push_back (0);
1022                         if (types.size() == 1) {
1023                                 points.push_back (32);
1024                         } else {
1025                                 /* tweak so as not to overlay the -30dB mark */
1026                                 points.push_back (48);
1027                         }
1028                         points.push_back (64);
1029                         points.push_back (96);
1030                         points.push_back (127);
1031                         break;
1032                 }
1033
1034                 char buf[32];
1035
1036                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
1037
1038                         float fraction = 0;
1039                         switch (*i) {
1040                         case DataType::AUDIO:
1041                                 fraction = log_meter (*j);
1042                                 break;
1043                         case DataType::MIDI:
1044                                 fraction = *j / 127.0;
1045                                 break;
1046                         }
1047
1048                         gint const pos = height - (gint) floor (height * fraction);
1049
1050                         cairo_set_line_width (cr, 1.0);
1051                         cairo_move_to (cr, 0, pos);
1052                         cairo_line_to (cr, 4, pos);
1053                         cairo_stroke (cr);
1054                         
1055                         snprintf (buf, sizeof (buf), "%d", abs (*j));
1056                         pango_layout_set_text (layout, buf, strlen (buf));
1057
1058                         /* we want logical extents, not ink extents here */
1059
1060                         int tw, th;
1061                         pango_layout_get_pixel_size (layout, &tw, &th);
1062
1063                         int p = pos - (th / 2);
1064                         p = min (p, height - th);
1065                         p = max (p, 0);
1066
1067                         cairo_move_to (cr, 6, p);
1068                         pango_cairo_show_layout (cr, layout);
1069                 }
1070         }
1071
1072         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
1073         MetricPatterns::iterator p;
1074
1075         if ((p = metric_patterns.find (w.get_name())) != metric_patterns.end()) {
1076                 cairo_pattern_destroy (p->second);
1077         }
1078
1079         metric_patterns[w.get_name()] = pattern;
1080         
1081         cairo_destroy (cr);
1082         cairo_surface_destroy (surface);
1083         g_object_unref (layout);
1084
1085         return pattern;
1086 }
1087
1088 gint
1089 GainMeter::meter_metrics_expose (GdkEventExpose *ev)
1090 {
1091         Glib::RefPtr<Gdk::Window> win (meter_metric_area.get_window());
1092         cairo_t* cr;
1093
1094         cr = gdk_cairo_create (win->gobj());
1095         
1096         /* clip to expose area */
1097
1098         gdk_cairo_rectangle (cr, &ev->area);
1099         cairo_clip (cr);
1100
1101         cairo_pattern_t* pattern;
1102         MetricPatterns::iterator i = metric_patterns.find (meter_metric_area.get_name());
1103
1104         if (i == metric_patterns.end() || style_changed || dpi_changed) {
1105                 pattern = render_metrics (meter_metric_area, _types);
1106         } else {
1107                 pattern = i->second;
1108         }
1109
1110         cairo_move_to (cr, 0, 0);
1111         cairo_set_source (cr, pattern);
1112
1113         gint width, height;
1114         win->get_size (width, height);
1115
1116         cairo_rectangle (cr, 0, 0, width, height);
1117         cairo_fill (cr);
1118
1119         style_changed = false;
1120         dpi_changed = false;
1121
1122         cairo_destroy (cr);
1123
1124         return true;
1125 }
1126
1127 boost::shared_ptr<PBD::Controllable>
1128 GainMeterBase::get_controllable()
1129 {
1130         if (_amp) {
1131                 return _amp->gain_control();
1132         } else {
1133                 return boost::shared_ptr<PBD::Controllable>();
1134         }
1135 }
1136
1137 bool
1138 GainMeterBase::level_meter_button_press (GdkEventButton* ev)
1139 {
1140         return LevelMeterButtonPress (ev); /* EMIT SIGNAL */
1141 }
1142
1143 void
1144 GainMeter::meter_configuration_changed (ChanCount c)
1145 {
1146         _types.clear ();
1147
1148         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
1149                 if (c.get (*i) > 0) {
1150                         _types.push_back (*i);
1151                 }
1152         }
1153
1154         style_changed = true;
1155         meter_metric_area.queue_draw ();
1156 }
1157