Zoom session when the mouse pointer is moved up and down during a playhead drag.
[ardour2.git] / libs / gtkmm2ext / barcontroller.cc
blob59c313621ea66b24df847b2585d81d731e79cfd8
1 /*
2 Copyright (C) 2004 Paul Davis
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 $Id$
20 #include <string>
21 #include <sstream>
22 #include <climits>
23 #include <cstdio>
24 #include <cmath>
25 #include <algorithm>
27 #include <pbd/controllable.h>
28 #include <pbd/locale_guard.h>
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/utils.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/barcontroller.h"
35 #include "i18n.h"
37 using namespace std;
38 using namespace Gtk;
39 using namespace Gtkmm2ext;
41 BarController::BarController (Gtk::Adjustment& adj,
42 boost::shared_ptr<PBD::Controllable> mc)
44 : adjustment (adj),
45 binding_proxy (mc),
46 spinner (adjustment)
49 _style = LeftToRight;
50 grabbed = false;
51 switching = false;
52 switch_on_release = false;
53 use_parent = false;
54 logarithmic = false;
56 layout = darea.create_pango_layout("");
58 set_shadow_type (SHADOW_NONE);
60 initial_value = adjustment.get_value ();
62 adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
63 adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
65 darea.add_events (Gdk::BUTTON_RELEASE_MASK|
66 Gdk::BUTTON_PRESS_MASK|
67 Gdk::POINTER_MOTION_MASK|
68 Gdk::ENTER_NOTIFY_MASK|
69 Gdk::LEAVE_NOTIFY_MASK|
70 Gdk::SCROLL_MASK);
72 darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
73 darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
74 darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
75 darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
76 darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
78 spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
79 spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
80 spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
81 spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
82 spinner.set_digits (9);
83 spinner.set_numeric (true);
85 add (darea);
86 show_all ();
89 void
90 BarController::drop_grab ()
92 if (grabbed) {
93 grabbed = false;
94 darea.remove_modal_grab();
95 StopGesture ();
99 bool
100 BarController::button_press (GdkEventButton* ev)
102 double fract;
104 if (binding_proxy.button_press_handler (ev)) {
105 return true;
108 switch (ev->button) {
109 case 1:
110 if (ev->type == GDK_2BUTTON_PRESS) {
111 switch_on_release = true;
112 drop_grab ();
113 } else {
114 switch_on_release = false;
115 darea.add_modal_grab();
116 grabbed = true;
117 grab_x = ev->x;
118 grab_window = ev->window;
119 StartGesture ();
121 return true;
122 break;
124 case 2:
125 fract = ev->x / (darea.get_width() - 2.0);
126 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
128 case 3:
129 break;
131 case 4:
132 case 5:
133 break;
136 return false;
139 bool
140 BarController::button_release (GdkEventButton* ev)
142 drop_grab ();
144 switch (ev->button) {
145 case 1:
146 if (switch_on_release) {
147 Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
148 return true;
151 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
152 adjustment.set_value (initial_value);
153 } else {
154 double scale;
156 if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
157 scale = 0.01;
158 } else if (ev->state & Keyboard::PrimaryModifier) {
159 scale = 0.1;
160 } else {
161 scale = 1.0;
164 mouse_control (ev->x, ev->window, scale);
166 break;
168 case 2:
169 break;
171 case 3:
172 return false;
174 default:
175 break;
178 return true;
181 bool
182 BarController::scroll (GdkEventScroll* ev)
184 double scale;
186 if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
187 scale = 0.01;
188 } else if (ev->state & Keyboard::PrimaryModifier) {
189 scale = 0.1;
190 } else {
191 scale = 1.0;
194 switch (ev->direction) {
195 case GDK_SCROLL_UP:
196 case GDK_SCROLL_RIGHT:
197 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
198 break;
200 case GDK_SCROLL_DOWN:
201 case GDK_SCROLL_LEFT:
202 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
203 break;
206 return true;
209 bool
210 BarController::motion (GdkEventMotion* ev)
212 double scale;
214 if (!grabbed) {
215 return true;
218 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
219 return TRUE;
222 if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
223 scale = 0.01;
224 } else if (ev->state & Keyboard::PrimaryModifier) {
225 scale = 0.1;
226 } else {
227 scale = 1.0;
230 return mouse_control (ev->x, ev->window, scale);
233 gint
234 BarController::mouse_control (double x, GdkWindow* window, double scaling)
236 double fract = 0.0;
237 double delta;
239 if (window != grab_window) {
240 grab_x = x;
241 grab_window = window;
242 return TRUE;
245 delta = x - grab_x;
246 grab_x = x;
248 switch (_style) {
249 case Line:
250 case Blob:
251 case LeftToRight:
252 case CenterOut:
253 fract = scaling * (delta / (darea.get_width() - 2));
254 fract = min (1.0, fract);
255 fract = max (-1.0, fract);
256 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
257 break;
258 default:
259 fract = 0.0;
263 return TRUE;
266 bool
267 BarController::expose (GdkEventExpose* /*event*/)
269 Glib::RefPtr<Gdk::Window> win (darea.get_window());
270 Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
271 Gdk::Color c;
272 Widget* parent;
273 gint x1=0, x2=0, y1=0, y2=0;
274 gint w, h;
275 double fract;
276 float r, g, b;
278 fract = ((adjustment.get_value() - adjustment.get_lower()) /
279 (adjustment.get_upper() - adjustment.get_lower()));
281 switch (_style) {
282 case Line:
283 w = darea.get_width() - 1;
284 h = darea.get_height();
285 x1 = (gint) floor (w * fract);
286 x2 = x1;
287 y1 = 0;
288 y2 = h - 1;
290 if (use_parent) {
291 parent = get_parent();
293 if (parent) {
294 c = parent->get_style()->get_fg (parent->get_state());
295 r = c.get_red_p ();
296 g = c.get_green_p ();
297 b = c.get_blue_p ();
298 context->set_source_rgb (r, g, b);
299 context->rectangle (0, 0, darea.get_width(), darea.get_height());
300 context->fill ();
303 } else {
305 c = get_style()->get_bg (get_state());
306 r = c.get_red_p ();
307 g = c.get_green_p ();
308 b = c.get_blue_p ();
309 context->set_source_rgb (r, g, b);
310 context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
311 context->fill ();
314 c = get_style()->get_fg (get_state());
315 r = c.get_red_p ();
316 g = c.get_green_p ();
317 b = c.get_blue_p ();
318 context->set_source_rgb (r, g, b);
319 context->move_to (x1, 0);
320 context->line_to (x1, h);
321 context->stroke ();
322 break;
324 case Blob:
325 w = darea.get_width() - 1;
326 h = darea.get_height();
327 x1 = (gint) floor (w * fract);
328 x2 = min (w-2,h-2);
330 if (use_parent) {
331 parent = get_parent();
333 if (parent) {
334 c = parent->get_style()->get_fg (parent->get_state());
335 r = c.get_red_p ();
336 g = c.get_green_p ();
337 b = c.get_blue_p ();
338 context->set_source_rgb (r, g, b);
339 context->rectangle (0, 0, darea.get_width(), darea.get_height());
340 context->fill ();
343 } else {
345 c = get_style()->get_bg (get_state());
346 r = c.get_red_p ();
347 g = c.get_green_p ();
348 b = c.get_blue_p ();
349 context->set_source_rgb (r, g, b);
350 context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
351 context->fill ();
354 c = get_style()->get_fg (get_state());
355 r = c.get_red_p ();
356 g = c.get_green_p ();
357 b = c.get_blue_p ();
358 context->arc (x1, ((h-2)/2)-1, x2, 0, 2*M_PI);
359 break;
361 case CenterOut:
362 w = darea.get_width();
363 h = darea.get_height()-2;
364 if (use_parent) {
365 parent = get_parent();
366 if (parent) {
367 c = parent->get_style()->get_fg (parent->get_state());
368 r = c.get_red_p ();
369 g = c.get_green_p ();
370 b = c.get_blue_p ();
371 context->set_source_rgb (r, g, b);
372 context->rectangle (0, 0, darea.get_width(), darea.get_height());
373 context->fill ();
375 } else {
376 c = get_style()->get_bg (get_state());
377 r = c.get_red_p ();
378 g = c.get_green_p ();
379 b = c.get_blue_p ();
380 context->set_source_rgb (r, g, b);
381 context->rectangle (0, 0, darea.get_width(), darea.get_height());
382 context->fill ();
384 c = get_style()->get_fg (get_state());
385 r = c.get_red_p ();
386 g = c.get_green_p ();
387 b = c.get_blue_p ();
388 x1 = (w/2) - ((w*fract)/2); // center, back up half the bar width
389 context->set_source_rgb (r, g, b);
390 context->rectangle (x1, 1, w*fract, h);
391 context->fill ();
392 break;
394 case LeftToRight:
396 w = darea.get_width() - 2;
397 h = darea.get_height() - 2;
399 x1 = 0;
400 x2 = (gint) floor (w * fract);
401 y1 = 0;
402 y2 = h;
404 /* bounding box */
406 c = get_style()->get_bg (get_state());
407 r = c.get_red_p ();
408 g = c.get_green_p ();
409 b = c.get_blue_p ();
410 context->set_source_rgb (r, g, b);
411 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height());
412 context->stroke ();
414 /* draw active box */
416 c = get_style()->get_fg (get_state());
417 r = c.get_red_p ();
418 g = c.get_green_p ();
419 b = c.get_blue_p ();
420 context->set_source_rgb (r, g, b);
421 rounded_rectangle (context, 1 + x1, 1 + y1, x2, y2);
422 context->fill ();
424 /* draw inactive box */
426 c = get_style()->get_fg (STATE_INSENSITIVE);
427 r = c.get_red_p ();
428 g = c.get_green_p ();
429 b = c.get_blue_p ();
430 context->set_source_rgb (r, g, b);
431 rounded_rectangle (context, 1 + x2, 1 + y1, w - x2, y2);
432 context->fill ();
434 break;
436 case RightToLeft:
437 break;
438 case TopToBottom:
439 break;
440 case BottomToTop:
441 break;
444 /* draw label */
446 double xpos = -1;
447 std::string const label = get_label (xpos);
449 if (!label.empty()) {
451 layout->set_text (label);
453 int width, height, x;
454 layout->get_pixel_size (width, height);
456 if (xpos == -1) {
457 x = max (3, 1 + (x2 - (width/2)));
458 x = min (darea.get_width() - width - 3, (int) lrint (xpos));
459 } else {
460 x = lrint (darea.get_width() * xpos);
463 c = get_style()->get_text (get_state());
464 r = c.get_red_p ();
465 g = c.get_green_p ();
466 b = c.get_blue_p ();
467 context->set_source_rgb (r, g, b);
468 context->move_to (x, (darea.get_height()/2) - (height/2));
469 layout->show_in_cairo_context (context);
472 return true;
475 void
476 BarController::set_style (barStyle s)
478 _style = s;
479 darea.queue_draw ();
482 gint
483 BarController::switch_to_bar ()
485 if (switching) {
486 return FALSE;
489 switching = true;
491 if (get_child() == &darea) {
492 return FALSE;
495 remove ();
496 add (darea);
497 darea.show ();
499 switching = false;
501 SpinnerActive (false); /* EMIT SIGNAL */
503 return FALSE;
506 gint
507 BarController::switch_to_spinner ()
509 if (switching) {
510 return FALSE;
513 switching = true;
515 if (get_child() == &spinner) {
516 return FALSE;
519 remove ();
520 add (spinner);
521 spinner.show ();
522 spinner.select_region (0, spinner.get_text_length());
523 spinner.grab_focus ();
525 switching = false;
527 SpinnerActive (true); /* EMIT SIGNAL */
529 return FALSE;
532 void
533 BarController::entry_activated ()
535 switch_to_bar ();
538 bool
539 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
541 entry_activated ();
542 return true;
545 void
546 BarController::set_use_parent (bool yn)
548 use_parent = yn;
549 queue_draw ();
552 void
553 BarController::set_sensitive (bool yn)
555 Frame::set_sensitive (yn);
556 darea.set_sensitive (yn);
560 This is called when we need to update the adjustment with the value
561 from the spinner's text entry.
563 We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
565 If we're not in logarithmic mode we can return false to use the
566 default conversion.
568 In theory we should check for conversion errors but set numeric
569 mode to true on the spinner prevents invalid input.
572 BarController::entry_input (double* new_value)
574 if (!logarithmic) {
575 return false;
578 // extract a double from the string and take its log
579 Entry *entry = dynamic_cast<Entry *>(&spinner);
580 double value;
583 // Switch to user's preferred locale so that
584 // if they use different LC_NUMERIC conventions,
585 // we will honor them.
587 PBD::LocaleGuard lg ("");
588 sscanf (entry->get_text().c_str(), "%lf", &value);
591 *new_value = log(value);
593 return true;
597 This is called when we need to update the spinner's text entry
598 with the value of the adjustment.
600 We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
602 If we're not in logarithmic mode we can return false to use the
603 default conversion.
605 bool
606 BarController::entry_output ()
608 if (!logarithmic) {
609 return false;
612 // generate the exponential and turn it into a string
613 // convert to correct locale.
615 stringstream stream;
616 string str;
618 char buf[128];
621 // Switch to user's preferred locale so that
622 // if they use different LC_NUMERIC conventions,
623 // we will honor them.
625 PBD::LocaleGuard lg ("");
626 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
629 Entry *entry = dynamic_cast<Entry *>(&spinner);
630 entry->set_text(buf);
632 return true;