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 }