1 /***************************************************************************
2  *   Copyright (C) 2011 by Pere Ràfols Soler                               *
3  *   sapista2@gmail.com                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include <iostream>
22 #include <cmath>
23 #include <cstdio>
24 #include "knob2.h"
25 #include "colors.h"
26 #include <gdkmm.h>//For the function Gdk::Cairo::set_source_pixbuf()
27 //#include <gdkmm/general.h> //Switched back to gdkmm.h for cairo portability problems
28 
29 #define KNOB_RADIUS 0.4
30 #define SCROLL_EVENT_PERCENT 0.005
31 #define MOUSE_EVENT_PERCENT 0.008
32 #define KNOB_CENTER_X 0.5
33 #define KNOB_CENTER_Y 0.5
34 #define TEXT_SIZE 22
35 #define KNOB_R_CALIBRATION 0.93
36 #define SLOW_MOTION_MULTIPLIER 0.05
37 
KnobWidget2(float fMin,float fMax,std::string sLabel,std::string sUnits,const char * knobIconPath,int iType,bool snap2ZerodB)38 KnobWidget2::KnobWidget2(float fMin, float fMax, std::string sLabel, std::string sUnits, const char *knobIconPath, int iType, bool snap2ZerodB ):
39   m_fMin(fMin),
40   m_fMax(fMax),
41   bMotionIsConnected(false),
42   m_Value(fMin),
43   m_Label(sLabel),
44   m_Units(sUnits),
45   m_TypeKnob(iType),
46   mouse_move_ant(0),
47   m_snap2Zero(snap2ZerodB),
48   m_focus(false),
49   m_slowMultiplier(1.0),
50   m_knobIconPath(knobIconPath)
51 {
52    m_image_ptr =  Gdk::Pixbuf::create_from_file(m_knobIconPath);
53 
54   // Detect transparent colors for loaded image
55   Cairo::Format format = Cairo::FORMAT_RGB24;
56   if (m_image_ptr->get_has_alpha())
57   {
58       format = Cairo::FORMAT_ARGB32;
59   }
60 
61   // Create a new ImageSurface
62   m_image_surface_ptr = Cairo::ImageSurface::create  (format, m_image_ptr->get_width(), m_image_ptr->get_height());
63 
64   // Create the new Context for the ImageSurface
65   m_image_context_ptr = Cairo::Context::create (m_image_surface_ptr);
66 
67   // Draw the image on the new Context
68   Gdk::Cairo::set_source_pixbuf (m_image_context_ptr, m_image_ptr, 0.0, 0.0);
69   m_image_context_ptr->paint();
70 
71   //Size Request acording knob image
72   set_size_request((int)(1.5*(double)(m_image_ptr->get_width())), TEXT_SIZE + (int)(1.5*(double)(m_image_ptr->get_height())));
73 
74   add_events(Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::POINTER_MOTION_MASK | Gdk::SCROLL_MASK | Gdk::LEAVE_NOTIFY_MASK);
75   signal_button_press_event().connect(sigc::mem_fun(*this, &KnobWidget2::on_button_press_event),true);
76   signal_button_release_event().connect(sigc::mem_fun(*this, &KnobWidget2::on_button_release_event),true);
77   signal_scroll_event().connect(sigc::mem_fun(*this, &KnobWidget2::on_scrollwheel_event),true);
78   signal_motion_notify_event().connect(sigc::mem_fun(*this, &KnobWidget2::on_mouse_motion_event),true);
79   signal_leave_notify_event().connect(sigc::mem_fun(*this, &KnobWidget2::on_mouse_leave_widget),true);
80 }
81 
~KnobWidget2()82 KnobWidget2::~KnobWidget2()
83 {
84 
85 }
86 
signal_changed()87 KnobWidget2::signal_KnobChanged KnobWidget2::signal_changed()
88 {
89   return m_KnobChangedSignal;
90 }
91 
set_value(float fValue)92 void KnobWidget2::set_value(float fValue)
93 {
94   m_Value = fValue;
95   m_Value = m_Value < m_fMin ? m_fMin : m_Value;
96   m_Value = m_Value > m_fMax ? m_fMax : m_Value;
97   redraw();
98 }
99 
get_value()100 double KnobWidget2::get_value()
101 {
102   return m_Value;
103 }
104 
redraw()105 void KnobWidget2::redraw()
106 {
107   Glib::RefPtr<Gdk::Window> win = get_window();
108   if(win)
109   {
110     Gdk::Rectangle r(0, 0, get_allocation().get_width(), get_allocation().get_height());
111     win->invalidate_rect(r, false);
112   }
113 }
114 
on_mouse_leave_widget(GdkEventCrossing * event)115 bool KnobWidget2::on_mouse_leave_widget(GdkEventCrossing* event)
116 {
117   if(!bMotionIsConnected)
118   {
119     m_slowMultiplier = 1.0;
120     m_focus = false;
121     redraw();
122   }
123   return true;
124 }
125 
126 
127 //Mouse events
on_button_press_event(GdkEventButton * event)128 bool KnobWidget2::on_button_press_event(GdkEventButton* event)
129 {
130   int x,y;
131   get_pointer(x,y);
132   if( x > 0 &&
133       x < width  &&
134       y > 0 &&
135       y < width && //I use width for y becous knob must be square, this discards text area
136       event->type == GDK_BUTTON_PRESS) //Only grab single click
137   {
138     mouse_move_ant = y;
139     if(event->button == 1)
140     {
141       bMotionIsConnected = true;
142       m_slowMultiplier = 1.0;
143     }
144     else if(event->button == 3)
145     {
146       bMotionIsConnected = true;
147       m_slowMultiplier = SLOW_MOTION_MULTIPLIER;
148     }
149   }
150   return true;
151 }
152 
on_button_release_event(GdkEventButton * event)153 bool KnobWidget2::on_button_release_event(GdkEventButton* event)
154 {
155   bMotionIsConnected = false;
156   return true;
157 }
158 
on_mouse_motion_event(GdkEventMotion * event)159 bool KnobWidget2::on_mouse_motion_event(GdkEventMotion* event)
160 {
161 
162   if(bMotionIsConnected)
163   {
164     double  increment = 0.0;
165 
166     switch(m_TypeKnob)
167     {
168       case  KNOB_TYPE_FREQ:
169 	increment = m_slowMultiplier * MOUSE_EVENT_PERCENT*(m_fMax - m_fMin)*0.0002*m_Value;
170 	break;
171 
172       case KNOB_TYPE_LIN:
173 	increment = m_slowMultiplier * MOUSE_EVENT_PERCENT*(m_fMax - m_fMin);
174 	break;
175 
176       case KNOB_TYPE_TIME:
177 	increment = m_slowMultiplier * MOUSE_EVENT_PERCENT*5.0*(m_Value + 1.0);
178 	break;
179     }
180 
181     float val = 0.0f;
182     bool ismoving = false;
183     if(event->y - mouse_move_ant < 0)
184     {
185       //Move up
186       val = m_Value + increment*(abs(event->y - mouse_move_ant));
187       ismoving = true;
188     }
189 
190     if(event->y - mouse_move_ant > 0)
191     {
192       //Move down
193       val = m_Value - increment*(abs(event->y - mouse_move_ant));
194       ismoving = true;
195     }
196 
197     //Snap to 0 dB
198     if(m_snap2Zero && val < 0.5f && val > -0.5f)
199     {
200       val = 0.0f;
201     }
202 
203     if(ismoving)
204     {
205       set_value(val);
206     }
207     mouse_move_ant = event->y;
208     m_KnobChangedSignal.emit();
209   }
210   else
211   {
212     //Grab focus if mouse over knob
213     m_focus = event->x > 0 && event->x < width && event->y > 0 && event->y < width;
214     redraw();
215   }
216   return true;
217 }
218 
on_scrollwheel_event(GdkEventScroll * event)219 bool KnobWidget2::on_scrollwheel_event(GdkEventScroll* event)
220 {
221   double  increment = 0.0;
222   switch(m_TypeKnob)
223   {
224     case  KNOB_TYPE_FREQ:
225       increment =  SCROLL_EVENT_PERCENT*(m_fMax - m_fMin)*0.0001*m_Value;
226       break;
227 
228     case KNOB_TYPE_LIN:
229        increment =  SCROLL_EVENT_PERCENT*(m_fMax - m_fMin);
230       break;
231 
232     case KNOB_TYPE_TIME:
233       increment =  SCROLL_EVENT_PERCENT*5.0*(m_Value + 1.0);
234       break;
235   }
236 
237   if (event->direction == GDK_SCROLL_UP)
238   {
239     // up code
240     set_value(m_Value + increment);
241 
242   }
243   else if (event->direction == GDK_SCROLL_DOWN)
244   {
245     // down code
246     set_value(m_Value - increment);
247   }
248   m_KnobChangedSignal.emit();
249   return true;
250 }
251 
252 //Drawing Knob
on_expose_event(GdkEventExpose * event)253 bool KnobWidget2::on_expose_event(GdkEventExpose* event)
254 {
255   Glib::RefPtr<Gdk::Window> window = get_window();
256   if(window)
257   {
258 
259     Gtk::Allocation allocation = get_allocation();
260     width = allocation.get_width();
261     height = allocation.get_height();
262     Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
263 
264     //Clip inside acording the expose event
265     cr->rectangle(event->area.x, event->area.y, event->area.width, event->area.height);
266     cr->clip();
267     cr->set_source_rgb(BACKGROUND_R, BACKGROUND_G, BACKGROUND_B);
268     cr->paint(); //Fill all with background color
269 
270     //Set text
271     Glib::RefPtr<Pango::Layout> pangoLayout = Pango::Layout::create(cr);
272     Pango::FontDescription font_desc("sans 9px");
273     pangoLayout->set_font_description(font_desc);
274 
275     cr->move_to(0, height - TEXT_SIZE);
276     cr->set_source_rgba(0.9, 0.9, 0.9, 1.0);
277     pangoLayout->update_from_cairo_context(cr);  //gets cairo cursor position
278     pangoLayout->set_text(m_Label);
279     pangoLayout->set_width(Pango::SCALE * width);
280     pangoLayout->set_alignment(Pango::ALIGN_CENTER);
281     pangoLayout->show_in_cairo_context(cr);
282     cr->stroke();
283 
284     cr->move_to(0, height - TEXT_SIZE/2);
285     cr->set_source_rgba(0.9, 0.9, 0.9, 1.0);
286     pangoLayout->update_from_cairo_context(cr);  //gets cairo cursor position
287     std::stringstream ss;
288     ss.precision(1);
289 
290     if(m_TypeKnob == KNOB_TYPE_FREQ && m_Value >= 1000.0)
291     {
292       ss<<std::fixed<<m_Value/1000.0<<" k"<<m_Units;
293     }
294     else if(m_TypeKnob == KNOB_TYPE_TIME && m_Value >= 1000.0)
295     {
296       ss<<std::fixed<<m_Value/1000.0<<" s";
297     }
298     else if(m_TypeKnob == KNOB_TYPE_TIME && m_Value < 1.0)
299     {
300       ss<<std::fixed<<m_Value*1000.0<<" us";
301     }
302     else
303     {
304       ss<<std::fixed<<m_Value<<" "<<m_Units;
305     }
306     pangoLayout->set_text(ss.str());
307     pangoLayout->set_width(Pango::SCALE * width);
308     pangoLayout->set_alignment(Pango::ALIGN_CENTER);
309     pangoLayout->show_in_cairo_context(cr);
310     cr->stroke();
311     cr->save();
312 
313 
314     //Calc konb angle (pos)
315     double pos = 0.0, m, n;
316     switch(m_TypeKnob)
317     {
318       case KNOB_TYPE_FREQ:
319       case KNOB_TYPE_TIME:
320         m = (1.48*M_PI)/log10(m_fMax/m_fMin);
321         n = 0.76*M_PI;
322         pos = m*log10(m_Value/m_fMin) + n;
323         break;
324 
325       case KNOB_TYPE_LIN:
326         m = (1.48*M_PI)/(m_fMax-m_fMin);
327         n = 0.76*M_PI  - m*m_fMin;
328         pos = m*m_Value + n;
329         break;
330     }
331 
332     //Scale to 1
333     cr->rectangle(0, 0, width,height - TEXT_SIZE);
334     cr->clip();
335     cr->scale(width,height - TEXT_SIZE);
336 
337     //Draw glow, mouse is over
338     if(m_focus)
339     {
340       Cairo::RefPtr<Cairo::RadialGradient> glow_gradient_ptr = Cairo::RadialGradient::create(
341         KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS - 0.1, KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS + 0.1);
342 
343       glow_gradient_ptr->add_color_stop_rgba (0, 0.4, 0.6, 0.8, 0.6);
344       glow_gradient_ptr->add_color_stop_rgba (1, BACKGROUND_R, BACKGROUND_G, BACKGROUND_B, 0.1);
345 
346       cr->set_source(  glow_gradient_ptr);
347       cr->set_line_width(0.8);
348       cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, 0.2, 0.0, 2.0 * M_PI);
349       cr->stroke();
350     }
351 
352     //Draw Background gradient full circle
353     Cairo::RefPtr<Cairo::RadialGradient> bkg_gradient_ptr = Cairo::RadialGradient::create(
354         KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS - 0.08, KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS + 0.1);
355 
356     bkg_gradient_ptr->add_color_stop_rgba (0, 0.0, 0.8, 0.3, 0.2);
357     bkg_gradient_ptr->add_color_stop_rgba (1, BACKGROUND_R, BACKGROUND_G, BACKGROUND_B, 0.1);
358 
359     cr->set_source(  bkg_gradient_ptr);
360     cr->set_line_width(0.8);
361     cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, 0.2, 0.0, 2.0 * M_PI);
362     cr->stroke();
363 
364     //Draw colored circle
365     Cairo::RefPtr<Cairo::RadialGradient> rad_gradient_ptr = Cairo::RadialGradient::create(
366         KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS - 0.08, KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS + 0.1);
367 
368     rad_gradient_ptr->add_color_stop_rgba (0, 0.0, 1.0, 0.0, 0.8);
369     rad_gradient_ptr->add_color_stop_rgba (1, BACKGROUND_R, BACKGROUND_G, BACKGROUND_B, 0.1);
370 
371     cr->set_source(  rad_gradient_ptr);
372     cr->set_line_width(0.2);
373     cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS + 0.04, 0.76 * M_PI, pos);
374     cr->stroke();
375 
376     //Draw color circle frame
377     cr->set_source_rgba(BACKGROUND_R + 0.4, BACKGROUND_G + 0.4, BACKGROUND_B + 0.4, 1.0);
378     cr->set_line_width(1.0/width);
379     //cr->set_line_width(0.01);
380     cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS + 0.04, 0.76 * M_PI, 0.24 * M_PI);
381     cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS - 0.06, 0.24 * M_PI, 2.76*M_PI);
382     cr->close_path();
383     cr->stroke();
384 
385     cr->set_source_rgba(0.0, 0.6, 0.6, 0.1);
386     cr->set_line_width(0.1);
387     std::valarray< double > dashes(2);
388     dashes[0] = 0.01;//1.0/width;
389     dashes[1] = 0.02;//4.0/width;
390     cr->set_dash (dashes, 0.5);
391     cr->arc(KNOB_CENTER_X, KNOB_CENTER_Y, KNOB_RADIUS - 0.01, 0.76 * M_PI, 0.24 * M_PI);
392     cr->stroke();
393     cr->restore();
394 
395     //Draw knob and rotate
396     cr->save();
397     cr->translate(width/2, (height-TEXT_SIZE)/2);
398     cr->rotate(pos + KNOB_R_CALIBRATION);
399 
400     //Draw the knob icon
401     cr->set_source (m_image_surface_ptr, -m_image_surface_ptr->get_width()/2, -m_image_surface_ptr->get_height()/2);
402     cr->rectangle (-m_image_surface_ptr->get_width()/2, -m_image_surface_ptr->get_height()/2, m_image_surface_ptr->get_width(),  m_image_surface_ptr->get_height());
403     cr->clip();
404     cr->paint();
405     cr->restore();
406 
407   }
408   return true;
409 }