remove frame from barcontroller
[ardour.git] / libs / gtkmm2ext / barcontroller.cc
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.
7
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.
12
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.
16
17     $Id$
18 */
19
20 #include <string>
21 #include <sstream>
22 #include <climits>
23 #include <cstdio>
24 #include <cmath>
25 #include <algorithm>
26
27 #include <pbd/controllable.h>
28 #include <pbd/locale_guard.h>
29
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/utils.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/barcontroller.h"
34 #include "gtkmm2ext/cairo_widget.h"
35
36 #include "i18n.h"
37
38 using namespace std;
39 using namespace Gtk;
40 using namespace Gtkmm2ext;
41
42 BarController::BarController (Gtk::Adjustment& adj,
43                               boost::shared_ptr<PBD::Controllable> mc)
44
45         : adjustment (adj)
46         , binding_proxy (mc)
47         , spinner (adjustment)
48         , _hovering (false)
49 {                         
50         _style = LeftToRight;
51         grabbed = false;
52         switching = false;
53         switch_on_release = false;
54         use_parent = false;
55         logarithmic = false;
56
57         layout = darea.create_pango_layout("");
58
59         set (.5, .5, 1.0, 1.0);
60         set_border_width(2);
61
62         initial_value = adjustment.get_value ();
63
64         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
65         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
66
67         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
68                           Gdk::BUTTON_PRESS_MASK|
69                           Gdk::POINTER_MOTION_MASK|
70                           Gdk::ENTER_NOTIFY_MASK|
71                           Gdk::LEAVE_NOTIFY_MASK|
72                           Gdk::SCROLL_MASK);
73
74         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
75         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
76         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
77         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
78         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
79         darea.signal_enter_notify_event().connect (mem_fun (*this, &BarController::on_enter_notify_event));
80         darea.signal_leave_notify_event().connect (mem_fun (*this, &BarController::on_leave_notify_event));
81
82         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
83         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
84         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
85         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
86         spinner.set_digits (9);
87         spinner.set_numeric (true);
88         
89         add (darea);
90
91         show_all ();
92 }
93
94 BarController::~BarController ()
95 {
96 }
97
98 bool
99 BarController::on_enter_notify_event (GdkEventCrossing*)
100 {
101         _hovering = true;
102         Keyboard::magic_widget_grab_focus ();
103         queue_draw ();
104         return false;
105 }
106
107 bool
108 BarController::on_leave_notify_event (GdkEventCrossing*)
109 {
110         _hovering = false;
111         Keyboard::magic_widget_drop_focus();
112         queue_draw ();
113         return false;
114 }
115
116 void
117 BarController::drop_grab ()
118 {
119         if (grabbed) {
120                 grabbed = false;
121                 darea.remove_modal_grab();
122                 StopGesture ();
123         }
124 }
125
126 bool
127 BarController::button_press (GdkEventButton* ev)
128 {
129         double fract;
130
131         if (binding_proxy.button_press_handler (ev)) {
132                 return true;
133         }
134
135         switch (ev->button) {
136         case 1:
137                 if (ev->type == GDK_2BUTTON_PRESS) {
138                         switch_on_release = true;
139                         drop_grab ();
140                 } else {
141                         switch_on_release = false;
142                         darea.add_modal_grab();
143                         grabbed = true;
144                         grab_x = ev->x;
145                         grab_window = ev->window;
146                         StartGesture ();
147                 }
148                 return true;
149                 break;
150
151         case 2:
152                 fract = ev->x / (darea.get_width() - 2.0);
153                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
154
155         case 3:
156                 break;
157
158         case 4:
159         case 5:
160                 break;
161         }
162
163         return false;
164 }
165
166 bool
167 BarController::button_release (GdkEventButton* ev)
168 {
169         drop_grab ();
170         
171         switch (ev->button) {
172         case 1:
173                 if (switch_on_release) {
174                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
175                         return true;
176                 }
177
178                 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
179                         adjustment.set_value (initial_value);
180                 } else {
181                         double scale;
182
183                         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
184                                 scale = 0.01;
185                         } else if (ev->state & Keyboard::PrimaryModifier) {
186                                 scale = 0.1;
187                         } else {
188                                 scale = 1.0;
189                         }
190
191                         mouse_control (ev->x, ev->window, scale);
192                 }
193                 break;
194
195         case 2:
196                 break;
197                 
198         case 3:
199                 return false;
200                 
201         default:
202                 break;
203         }
204
205         return true;
206 }
207
208 bool
209 BarController::scroll (GdkEventScroll* ev)
210 {
211         double scale;
212
213         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
214                 scale = 0.01;
215         } else if (ev->state & Keyboard::PrimaryModifier) {
216                 scale = 0.1;
217         } else {
218                 scale = 1.0;
219         }
220
221         switch (ev->direction) {
222         case GDK_SCROLL_UP:
223         case GDK_SCROLL_RIGHT:
224                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
225                 break;
226
227         case GDK_SCROLL_DOWN:
228         case GDK_SCROLL_LEFT:
229                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
230                 break;
231         }
232
233         return true;
234 }
235
236 bool
237 BarController::motion (GdkEventMotion* ev)
238 {
239         double scale;
240         
241         if (!grabbed) {
242                 return true;
243         }
244
245         if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
246                 return TRUE;
247         }
248
249         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
250                 scale = 0.01;
251         } else if (ev->state & Keyboard::PrimaryModifier) {
252                 scale = 0.1;
253         } else {
254                 scale = 1.0;
255         }
256
257         return mouse_control (ev->x, ev->window, scale);
258 }
259
260 gint
261 BarController::mouse_control (double x, GdkWindow* window, double scaling)
262 {
263         double fract = 0.0;
264         double delta;
265
266         if (window != grab_window) {
267                 grab_x = x;
268                 grab_window = window;
269                 return TRUE;
270         }
271
272         delta = x - grab_x;
273         grab_x = x;
274         
275         switch (_style) {
276         case Line:
277         case Blob:
278         case LeftToRight:
279         case CenterOut:
280                 fract = scaling * (delta / (darea.get_width() - 2));
281                 fract = min (1.0, fract);
282                 fract = max (-1.0, fract);
283                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
284                 break;
285         default:
286                 fract = 0.0;
287         }
288         
289         
290         return TRUE;
291 }
292
293 Gdk::Color
294 BarController::get_parent_bg ()
295 {
296         Widget* parent;
297
298         parent = get_parent ();
299
300         while (parent) {
301                 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
302                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
303
304                 if (p) {
305                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
306                         return style->get_bg (get_state());
307                 }
308                 
309                 if (!parent->get_has_window()) {
310                         parent = parent->get_parent();
311                 } else {
312                         break;
313                 }
314         }
315
316         if (parent && parent->get_has_window()) {
317                 return parent->get_style ()->get_bg (parent->get_state());
318         } 
319
320         return get_style ()->get_bg (get_state());
321 }
322
323 bool
324 BarController::expose (GdkEventExpose* /*event*/)
325 {
326         Glib::RefPtr<Gdk::Window> win (darea.get_window());
327         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
328         cairo_t* cr = context->cobj();
329
330         Gdk::Color fg_col = get_style()->get_fg (get_state());
331
332         double fract = ((adjustment.get_value() - adjustment.get_lower()) /
333                  (adjustment.get_upper() - adjustment.get_lower()));
334         
335         gint w = darea.get_width() ;
336         gint h = darea.get_height();
337         gint bar_start, bar_width;
338         double radius = 4;
339
340         switch (_style) {
341         case Line:
342                 bar_start = (gint) floor ((w-1) * fract);
343                 bar_width = 1;
344                 break;
345
346         case Blob:
347                 // ????
348                 break;
349
350         case CenterOut:
351         bar_width = (w*fract);
352         bar_start = (w/2) - bar_width/2; // center, back up half the bar width
353                 break;
354
355         case LeftToRight:
356                 bar_start = 1;
357                 bar_width = floor((w-2)*fract);
358
359                 break;
360
361         case RightToLeft:
362                 break;
363         case TopToBottom:
364                 break;
365         case BottomToTop:
366                 break;
367         }
368
369         //fill in the bg rect ... 
370         Gdk::Color c = get_parent_bg(); //get_style()->get_bg (Gtk::STATE_PRELIGHT);  //why prelight?  Shouldn't we be using the parent's color?  maybe   get_parent_bg  ?
371         CairoWidget::set_source_rgb_a (cr, c);
372         cairo_rectangle (cr, 0, 0, w, h);
373         cairo_fill(cr);
374
375         //"slot"
376         cairo_set_source_rgba (cr, 0.17, 0.17, 0.17, 1.0);
377         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
378         cairo_fill(cr);
379
380         //mask off the corners
381         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
382         cairo_clip(cr);
383         
384                 //background gradient
385                 if ( !CairoWidget::flat_buttons() ) {
386                         cairo_pattern_t *bg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
387                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0, 0, 0, 0, 0.4);
388                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0.2, 0, 0, 0, 0.2);
389                         cairo_pattern_add_color_stop_rgba (bg_gradient, 1, 0, 0, 0, 0.0);
390                         cairo_set_source (cr, bg_gradient);
391                         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-1.5);
392                         cairo_fill (cr);
393                         cairo_pattern_destroy(bg_gradient);
394                 }
395                 
396                 //fg color
397                 CairoWidget::set_source_rgb_a (cr, fg_col, 1.0);
398                 Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
399                 cairo_fill(cr);
400
401                 //fg gradient
402                 if (!CairoWidget::flat_buttons() ) {
403                         cairo_pattern_t * fg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
404                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0, 0, 0, 0, 0.0);
405                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0.1, 0, 0, 0, 0.0);
406                         cairo_pattern_add_color_stop_rgba (fg_gradient, 1, 0, 0, 0, 0.3);
407                         cairo_set_source (cr, fg_gradient);
408                         Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
409                         cairo_fill (cr);
410                         cairo_pattern_destroy(fg_gradient);
411                 }
412                 
413         cairo_reset_clip(cr);
414
415         //black border
416         cairo_set_line_width (cr, 1.0);
417         cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
418         Gtkmm2ext::rounded_rectangle (cr, 0.5, 0.5, w-1, h-1, radius);
419         cairo_stroke(cr);
420
421         /* draw the unity-position line if it's not at either end*/
422 /*      if (unity_loc > 0) {
423                 context->set_line_width (1);
424                 cairo_set_source_rgba (cr, 1,1,1, 1.0);
425                 if ( _orien == VERT) {
426                         if (unity_loc < h ) {
427                                 context->move_to (2.5, unity_loc + radius + .5);
428                                 context->line_to (girth-2.5, unity_loc + radius + .5);
429                                 context->stroke ();
430                         }
431                 } else {
432                         if ( unity_loc < w ){
433                                 context->move_to (unity_loc - radius + .5, 3.5);
434                                 context->line_to (unity_loc - radius + .5, girth-3.5);
435                                 context->stroke ();
436                         }
437                 }
438         }*/
439         
440         if (!darea.get_sensitive()) {
441                 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height(), 3);
442                 context->set_source_rgba (0.505, 0.517, 0.525, 0.6);
443                 context->fill ();
444         } else if (_hovering) {
445                 Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius);
446                 cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
447                 cairo_fill (cr);
448         }
449
450         /* draw label */
451
452         double xpos = -1;
453         std::string const label = get_label (xpos);
454         if (!label.empty()) {
455                 
456                 int twidth, theight;
457                 layout->set_text (label);
458                 layout->get_pixel_size (twidth, theight);
459
460                 if (fract > 0.5) {
461                         cairo_set_source_rgba (cr, 0.17, 0.17, 0.17, 1.0);
462                         context->move_to ( 5, (darea.get_height()/2) - (theight/2));
463                 } else {
464                         c = get_style()->get_text (get_state());
465                         CairoWidget::set_source_rgb_a (cr, c, 0.7);
466                         context->move_to ( w-twidth-5, (darea.get_height()/2) - (theight/2));
467                 }
468                 layout->show_in_cairo_context (context);
469         }
470         
471         return true;
472 }
473
474 void
475 BarController::set_style (barStyle s)
476 {
477         _style = s;
478         darea.queue_draw ();
479 }
480
481 gint
482 BarController::switch_to_bar ()
483 {
484         if (switching) {
485                 return FALSE;
486         }
487
488         switching = true;
489
490         if (get_child() == &darea) {
491                 return FALSE;
492         }
493
494         remove ();
495         add (darea);
496         darea.show ();
497
498         switching = false;
499
500         SpinnerActive (false); /* EMIT SIGNAL */
501         
502         return FALSE;
503 }
504
505 gint
506 BarController::switch_to_spinner ()
507 {
508         if (switching) {
509                 return FALSE;
510         }
511
512         switching = true;
513
514         if (get_child() == &spinner) {
515                 return FALSE;
516         }
517
518         remove ();
519         add (spinner);
520         spinner.show ();
521         spinner.select_region (0, spinner.get_text_length());
522         spinner.grab_focus ();
523
524         switching = false;
525
526         SpinnerActive (true); /* EMIT SIGNAL */
527
528         return FALSE;
529 }
530
531 void
532 BarController::entry_activated ()
533 {
534         switch_to_bar ();
535 }
536
537 bool
538 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
539 {
540         entry_activated ();
541         return true;
542 }
543
544 void
545 BarController::set_use_parent (bool yn)
546 {
547         use_parent = yn;
548         queue_draw ();
549 }
550
551 void
552 BarController::set_sensitive (bool yn)
553 {
554         Alignment::set_sensitive (yn);
555         darea.set_sensitive (yn);
556 }
557
558 /* 
559     This is called when we need to update the adjustment with the value
560     from the spinner's text entry.
561     
562     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
563     
564     If we're not in logarithmic mode we can return false to use the 
565     default conversion.
566     
567     In theory we should check for conversion errors but set numeric
568     mode to true on the spinner prevents invalid input.
569 */
570 int
571 BarController::entry_input (double* new_value)
572 {
573         if (!logarithmic) {
574                 return false;
575         }
576
577         // extract a double from the string and take its log
578         Entry *entry = dynamic_cast<Entry *>(&spinner);
579         double value;
580
581         {
582                 // Switch to user's preferred locale so that
583                 // if they use different LC_NUMERIC conventions,
584                 // we will honor them.
585
586                 PBD::LocaleGuard lg ("");
587                 sscanf (entry->get_text().c_str(), "%lf", &value);
588         }
589
590         *new_value = log(value);
591
592         return true;
593 }
594
595 /* 
596     This is called when we need to update the spinner's text entry 
597     with the value of the adjustment.
598     
599     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
600     
601     If we're not in logarithmic mode we can return false to use the 
602     default conversion.
603 */
604 bool
605 BarController::entry_output ()
606 {
607         if (!logarithmic) {
608                 return false;
609         }
610
611         // generate the exponential and turn it into a string
612         // convert to correct locale. 
613         
614         stringstream stream;
615         string str;
616
617         char buf[128];
618
619         {
620                 // Switch to user's preferred locale so that
621                 // if they use different LC_NUMERIC conventions,
622                 // we will honor them.
623                 
624                 PBD::LocaleGuard lg ("");
625                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
626         }
627
628         Entry *entry = dynamic_cast<Entry *>(&spinner);
629         entry->set_text(buf);
630         
631         return true;
632 }
633
634
635