ardour
mono_panner.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2000-2007 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 <iostream>
21 #include <iomanip>
22 #include <cstring>
23 #include <cmath>
24 
25 #include <gtkmm/window.h>
26 #include <pangomm/layout.h>
27 
28 #include "pbd/controllable.h"
29 #include "pbd/compose.h"
30 
31 #include "gtkmm2ext/gui_thread.h"
32 #include "gtkmm2ext/gtk_ui.h"
33 #include "gtkmm2ext/keyboard.h"
34 #include "gtkmm2ext/utils.h"
36 
37 #include "ardour/pannable.h"
38 #include "ardour/panner.h"
39 #include "ardour/panner_shell.h"
40 
41 #include "ardour_ui.h"
42 #include "global_signals.h"
43 #include "mono_panner.h"
44 #include "mono_panner_editor.h"
45 #include "rgb_macros.h"
46 #include "utils.h"
47 
48 #include "i18n.h"
49 
50 using namespace std;
51 using namespace Gtk;
52 using namespace Gtkmm2ext;
53 using namespace ARDOUR_UI_UTILS;
54 
56 bool MonoPanner::have_colors = false;
57 
59 bool MonoPanner::have_font = false;
60 
62  : PannerInterface (p->panner())
63  , _panner_shell (p)
64  , position_control (_panner->pannable()->pan_azimuth_control)
65  , drag_start_x (0)
66  , last_drag_x (0)
67  , accumulated_delta (0)
68  , detented (false)
69  , position_binder (position_control)
70  , _dragging (false)
71 {
72  if (!have_colors) {
73  set_colors ();
74  have_colors = true;
75  }
76  if (!have_font) {
77  Pango::FontDescription font;
78  Pango::AttrFontDesc* font_attr;
79  font = Pango::FontDescription (ARDOUR_UI::config()->get_SmallBoldMonospaceFont());
80  font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
81  panner_font_attributes.change(*font_attr);
82  delete font_attr;
83  have_font = true;
84  }
85 
87 
90  ColorsChanged.connect (sigc::mem_fun (*this, &MonoPanner::color_handler));
91 
92  set_tooltip ();
93 }
94 
96 {
97 
98 }
99 
100 void
102 {
103  if (_panner_shell->bypassed()) {
104  _tooltip.set_tip (_("bypassed"));
105  return;
106  }
107  double pos = position_control->get_value(); // 0..1
108 
109  /* We show the position of the center of the image relative to the left & right.
110  This is expressed as a pair of percentage values that ranges from (100,0)
111  (hard left) through (50,50) (hard center) to (0,100) (hard right).
112 
113  This is pretty wierd, but its the way audio engineers expect it. Just remember that
114  the center of the USA isn't Kansas, its (50LA, 50NY) and it will all make sense.
115  */
116 
117  char buf[64];
118  snprintf (buf, sizeof (buf), _("L:%3d R:%3d"),
119  (int) rint (100.0 * (1.0 - pos)),
120  (int) rint (100.0 * pos));
121  _tooltip.set_tip (buf);
122 }
123 
124 bool
126 {
127  Glib::RefPtr<Gdk::Window> win (get_window());
128  Glib::RefPtr<Gdk::GC> gc (get_style()->get_base_gc (get_state()));
129  Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
130 
131  int width, height;
132  double pos = position_control->get_value (); /* 0..1 */
133  uint32_t o, f, t, b, pf, po;
134 
135  width = get_width();
136  height = get_height ();
137 
138  const int step_down = rint(height / 3.5);
139  const int lr_box_size = height - 2 * step_down;
140  const int pos_box_size = (int)(rint(step_down * .8)) | 1;
141  const int top_step = step_down - pos_box_size;
142  const double corner_radius = 5 * ARDOUR_UI::ui_scale;
143 
144  o = colors.outline;
145  f = colors.fill;
146  t = colors.text;
147  b = colors.background;
148  pf = colors.pos_fill;
149  po = colors.pos_outline;
150 
151  if (_panner_shell->bypassed()) {
152  b = 0x20202040;
153  f = 0x404040ff;
154  o = 0x606060ff;
155  po = 0x606060ff;
156  pf = 0x404040ff;
157  t = 0x606060ff;
158  }
159 
160  if (_send_mode) {
161  b = ARDOUR_UI::config()->color ("send bg");
162  }
163  /* background */
164  context->set_source_rgba (UINT_RGBA_R_FLT(b), UINT_RGBA_G_FLT(b), UINT_RGBA_B_FLT(b), UINT_RGBA_A_FLT(b));
165  context->rectangle (0, 0, width, height);
166  context->fill ();
167 
168  double usable_width = width - pos_box_size;
169 
170  /* compute the centers of the L/R boxes based on the current stereo width */
171  if (fmod (usable_width,2.0) == 0) {
172  usable_width -= 1.0;
173  }
174  const double half_lr_box = lr_box_size/2.0;
175  const double left = pos_box_size * .5 + half_lr_box; // center of left box
176  const double right = width - pos_box_size * .5 - half_lr_box; // center of right box
177 
178  /* center line */
179  context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
180  context->set_line_width (1.0);
181  context->move_to ((pos_box_size/2.0) + (usable_width/2.0), 0);
182  context->line_to ((pos_box_size/2.0) + (usable_width/2.0), height);
183  context->stroke ();
184 
185  context->set_line_width (1.0);
186  /* left box */
187 
189  left - half_lr_box + .5,
190  half_lr_box + step_down,
191  lr_box_size, lr_box_size, corner_radius);
192  context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
193  context->fill_preserve ();
194  context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
195  context->stroke();
196 
197  /* add text */
198  int tw, th;
199  Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(get_pango_context());
200  layout->set_attributes (panner_font_attributes);
201 
202  layout->set_text (S_("Panner|L"));
203  layout->get_pixel_size(tw, th);
204  context->move_to (rint(left - tw/2), rint(lr_box_size + step_down - th/2));
205  context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
206  pango_cairo_show_layout (context->cobj(), layout->gobj());
207 
208  /* right box */
210  right - half_lr_box - .5,
211  half_lr_box + step_down,
212  lr_box_size, lr_box_size, corner_radius);
213  context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
214  context->fill_preserve ();
215  context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
216  context->stroke();
217 
218  /* add text */
219  layout->set_text (S_("Panner|R"));
220  layout->get_pixel_size(tw, th);
221  context->move_to (rint(right - tw/2), rint(lr_box_size + step_down - th/2));
222  context->set_source_rgba (UINT_RGBA_R_FLT(t), UINT_RGBA_G_FLT(t), UINT_RGBA_B_FLT(t), UINT_RGBA_A_FLT(t));
223  pango_cairo_show_layout (context->cobj(), layout->gobj());
224 
225  /* 2 lines that connect them both */
226  context->set_line_width (1.0);
227 
228  if (_panner_shell->panner_gui_uri() != "http://ardour.org/plugin/panner_balance#ui") {
229  context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
230  context->move_to (left + half_lr_box, half_lr_box + step_down);
231  context->line_to (right - half_lr_box, half_lr_box + step_down);
232  context->stroke ();
233 
234  context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
235  context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
236  context->stroke ();
237  } else {
238  context->move_to (left + half_lr_box, half_lr_box+step_down+lr_box_size);
239  context->line_to (left + half_lr_box, half_lr_box + step_down);
240  context->line_to ((pos_box_size/2.0) + (usable_width/2.0), half_lr_box+step_down+lr_box_size);
241  context->line_to (right - half_lr_box, half_lr_box + step_down);
242  context->line_to (right - half_lr_box, half_lr_box+step_down+lr_box_size);
243  context->close_path();
244 
245  context->set_source_rgba (UINT_RGBA_R_FLT(f), UINT_RGBA_G_FLT(f), UINT_RGBA_B_FLT(f), UINT_RGBA_A_FLT(f));
246  context->fill_preserve ();
247  context->set_source_rgba (UINT_RGBA_R_FLT(o), UINT_RGBA_G_FLT(o), UINT_RGBA_B_FLT(o), UINT_RGBA_A_FLT(o));
248  context->stroke ();
249  }
250 
251  /* draw the position indicator */
252  double spos = (pos_box_size/2.0) + (usable_width * pos);
253 
254  context->set_line_width (2.0);
255  context->move_to (spos + (pos_box_size/2.0), top_step); /* top right */
256  context->rel_line_to (0.0, pos_box_size); /* lower right */
257  context->rel_line_to (-pos_box_size/2.0, 4.0 * ARDOUR_UI::ui_scale); /* bottom point */
258  context->rel_line_to (-pos_box_size/2.0, -4.0 * ARDOUR_UI::ui_scale); /* lower left */
259  context->rel_line_to (0.0, -pos_box_size); /* upper left */
260  context->close_path ();
261 
262 
263  context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
264  context->stroke_preserve ();
265  context->set_source_rgba (UINT_RGBA_R_FLT(pf), UINT_RGBA_G_FLT(pf), UINT_RGBA_B_FLT(pf), UINT_RGBA_A_FLT(pf));
266  context->fill ();
267 
268  /* marker line */
269  context->set_line_width (1.0);
270  context->move_to (spos, 1 + top_step + pos_box_size + 4.0 * ARDOUR_UI::ui_scale);
271  context->line_to (spos, half_lr_box + step_down + lr_box_size - 1);
272  context->set_source_rgba (UINT_RGBA_R_FLT(po), UINT_RGBA_G_FLT(po), UINT_RGBA_B_FLT(po), UINT_RGBA_A_FLT(po));
273  context->stroke ();
274 
275  /* done */
276 
277  return true;
278 }
279 
280 bool
282 {
284  return true;
285  }
286  if (_panner_shell->bypassed()) {
287  return false;
288  }
289 
290  drag_start_x = ev->x;
291  last_drag_x = ev->x;
292 
293  _dragging = false;
295  accumulated_delta = 0;
296  detented = false;
297 
298  /* Let the binding proxies get first crack at the press event
299  */
300 
301  if (ev->y < 20) {
303  return true;
304  }
305  }
306 
307  if (ev->button != 1) {
308  return false;
309  }
310 
311  if (ev->type == GDK_2BUTTON_PRESS) {
312  int width = get_width();
313 
314  if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
315  /* handled by button release */
316  return true;
317  }
318 
319 
320  if (ev->x <= width/3) {
321  /* left side dbl click */
323  } else if (ev->x > 2*width/3) {
325  } else {
327  }
328 
329  _dragging = false;
331 
332  } else if (ev->type == GDK_BUTTON_PRESS) {
333 
334  if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
335  /* handled by button release */
336  return true;
337  }
338 
339  _dragging = true;
341  StartGesture ();
342  }
343 
344  return true;
345 }
346 
347 bool
349 {
351  return true;
352  }
353 
354  if (ev->button != 1) {
355  return false;
356  }
357 
358  if (_panner_shell->bypassed()) {
359  return false;
360  }
361 
362  _dragging = false;
364  accumulated_delta = 0;
365  detented = false;
366 
367  if (Keyboard::modifier_state_contains (ev->state, Keyboard::TertiaryModifier)) {
368  _panner->reset ();
369  } else {
370  StopGesture ();
371  }
372 
373  return true;
374 }
375 
376 bool
377 MonoPanner::on_scroll_event (GdkEventScroll* ev)
378 {
379  double one_degree = 1.0/180.0; // one degree as a number from 0..1, since 180 degrees is the full L/R axis
380  double pv = position_control->get_value(); // 0..1.0 ; 0 = left
381  double step;
382 
383  if (_panner_shell->bypassed()) {
384  return false;
385  }
386 
387  if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
388  step = one_degree;
389  } else {
390  step = one_degree * 5.0;
391  }
392 
393  switch (ev->direction) {
394  case GDK_SCROLL_UP:
395  case GDK_SCROLL_LEFT:
396  pv -= step;
398  break;
399  case GDK_SCROLL_DOWN:
400  case GDK_SCROLL_RIGHT:
401  pv += step;
403  break;
404  }
405 
406  return true;
407 }
408 
409 bool
411 {
412  if (_panner_shell->bypassed()) {
413  _dragging = false;
414  }
415  if (!_dragging) {
416  return false;
417  }
418 
419  int w = get_width();
420  double delta = (ev->x - last_drag_x) / (double) w;
421 
422  /* create a detent close to the center */
423 
425  detented = true;
426  /* snap to center */
428  }
429 
430  if (detented) {
431  accumulated_delta += delta;
432 
433  /* have we pulled far enough to escape ? */
434 
435  if (fabs (accumulated_delta) >= 0.025) {
437  detented = false;
438  accumulated_delta = false;
439  }
440  } else {
441  double pv = position_control->get_value(); // 0..1.0 ; 0 = left
442  position_control->set_value (pv + delta);
443  }
444 
445  last_drag_x = ev->x;
446  return true;
447 }
448 
449 bool
451 {
452  double one_degree = 1.0/180.0;
453  double pv = position_control->get_value(); // 0..1.0 ; 0 = left
454  double step;
455 
456  if (_panner_shell->bypassed()) {
457  return false;
458  }
459 
460  if (Keyboard::modifier_state_contains (ev->state, Keyboard::PrimaryModifier)) {
461  step = one_degree;
462  } else {
463  step = one_degree * 5.0;
464  }
465 
466  switch (ev->keyval) {
467  case GDK_Left:
468  pv -= step;
470  break;
471  case GDK_Right:
472  pv += step;
474  break;
475  case GDK_0:
476  case GDK_KP_0:
478  break;
479  default:
480  return false;
481  }
482 
483  return true;
484 }
485 
486 void
488 {
489  colors.fill = ARDOUR_UI::config()->color_mod ("mono panner fill", "panner fill");
490  colors.outline = ARDOUR_UI::config()->color ("mono panner outline");
491  colors.text = ARDOUR_UI::config()->color ("mono panner text");
492  colors.background = ARDOUR_UI::config()->color ("mono panner bg");
493  colors.pos_outline = ARDOUR_UI::config()->color ("mono panner position outline");
494  colors.pos_fill = ARDOUR_UI::config()->color_mod ("mono panner position fill", "mono panner position fill");
495 }
496 
497 void
499 {
500  set_colors ();
501  queue_draw ();
502 }
503 
504 void
506 {
507  queue_draw ();
508 }
509 
510 void
512 {
514  position_control = _panner->pannable()->pan_azimuth_control;
517  queue_draw ();
518 }
519 
522 {
523  return new MonoPannerEditor (this);
524 }
boost::shared_ptr< Pannable > pannable() const
Definition: panner.h:163
ArdourCanvas::Color color(const std::string &, bool *failed=0) const
Definition: ui_config.cc:567
static bool equivalent(pan_t a, pan_t b)
Definition: panner.h:165
bool on_button_release_event(GdkEventButton *)
bool bypassed() const
static Pango::AttrList panner_font_attributes
Definition: mono_panner.h:85
sigc::signal< void > StopGesture
Definition: mono_panner.h:48
boost::shared_ptr< ARDOUR::PannerShell > _panner_shell
Definition: mono_panner.h:60
PBD::Signal0< void > PannableChanged
Definition: panner_shell.h:67
Definition: ardour_ui.h:130
#define UINT_RGBA_A_FLT(x)
Definition: rgb_macros.h:60
LIBGTKMM2EXT_API void rounded_left_half_rectangle(Cairo::RefPtr< Cairo::Context >, double x, double y, double w, double h, double r=10)
Definition: utils.cc:551
std::string panner_gui_uri() const
Definition: panner_shell.h:83
#define UINT_RGBA_G_FLT(x)
Definition: rgb_macros.h:58
void set_controllable(boost::shared_ptr< PBD::Controllable >)
tuple f
Definition: signals.py:35
static void set_colors()
Definition: mono_panner.cc:487
Definition: Beats.hpp:239
#define UINT_RGBA_B_FLT(x)
Definition: rgb_macros.h:59
int last_drag_x
Definition: mono_panner.h:66
void color_handler()
Definition: mono_panner.cc:498
#define invalidator(x)
Definition: gui_thread.h:40
void pannable_handler()
Definition: mono_panner.cc:511
BindingProxy position_binder
Definition: mono_panner.h:70
double accumulated_delta
Definition: mono_panner.h:67
bool on_motion_notify_event(GdkEventMotion *)
Definition: mono_panner.cc:410
#define _(Text)
Definition: i18n.h:11
static bool have_font
Definition: mono_panner.h:86
bool _dragging
Definition: mono_panner.h:83
PBD::ScopedConnectionList panshell_connections
Definition: mono_panner.h:64
#define gui_context()
Definition: gui_thread.h:36
boost::shared_ptr< PBD::Controllable > position_control
Definition: mono_panner.h:62
virtual void reset()=0
bool on_button_press_event(GdkEventButton *)
virtual double get_value(void) const =0
bool on_scroll_event(GdkEventScroll *)
Definition: mono_panner.cc:377
bool button_press_handler(GdkEventButton *)
LIBGTKMM2EXT_API void rounded_right_half_rectangle(Cairo::RefPtr< Cairo::Context >, double x, double y, double w, double h, double r=10)
Definition: utils.cc:557
static bool have_colors
Definition: mono_panner.h:90
sigc::signal< void > StartGesture
Definition: mono_panner.h:47
bool on_expose_event(GdkEventExpose *)
Definition: mono_panner.cc:125
static ColorScheme colors
Definition: mono_panner.h:88
bool detented
Definition: mono_panner.h:68
PBD::ScopedConnectionList panvalue_connections
Definition: mono_panner.h:63
virtual void set_value(double)=0
PBD::Signal0< void > Changed
Definition: panner_shell.h:68
ArdourCanvas::Color color_mod(std::string const &color, std::string const &modifier) const
Definition: ui_config.cc:555
static UIConfiguration * config()
Definition: ardour_ui.h:188
PBD::Signal0< void > Changed
Definition: controllable.h:94
PannerEditor * editor()
Definition: mono_panner.cc:521
sigc::signal< void > ColorsChanged
MonoPanner(boost::shared_ptr< ARDOUR::PannerShell >)
Definition: mono_panner.cc:61
#define S_(Text)
Definition: i18n.h:18
bool on_button_release_event(GdkEventButton *)
Definition: mono_panner.cc:348
int drag_start_x
Definition: mono_panner.h:65
static float ui_scale
Definition: ardour_ui.h:189
boost::shared_ptr< ARDOUR::Panner > _panner
PannerPersistentTooltip _tooltip
bool on_button_press_event(GdkEventButton *)
Definition: mono_panner.cc:281
void set_tooltip()
Definition: mono_panner.cc:101
void bypass_handler()
Definition: mono_panner.cc:505
bool on_key_press_event(GdkEventKey *)
Definition: mono_panner.cc:450
#define UINT_RGBA_R_FLT(x)
Definition: rgb_macros.h:57