2 Copyright 2009 by Hans Baier, Krzysztof Foltman
10 static const uint16 GDK_Home
= 0xff50;
11 static const uint16 GDK_End
= 0xff57;
12 static const uint16 GDK_Up
= 0xff52;
13 static const uint16 GDK_Down
= 0xff54;
14 static const uint16 GDK_Shift_L
= 0xffe1;
15 static const uint16 GDK_Shift_R
= 0xffe2;
17 public enum KnobMode
{
24 public class Knob
: Range
{
25 private static HashTable
<string, Cairo
.Surface
> images
= null;
27 // turn off debug logging
28 void debug(string format
, ...) {}
30 private Cairo
.Surface? cache_surface
= null;
32 public void* user_data
{ get; set; }
33 private IKnobImageSource _image_source
;
34 [Description(nick
="image source", blurb
="provides the routines for drawing the knob images")]
35 public IKnobImageSource image_source
{
37 if (_image_source
== null) {
38 var isource
= new
SimpleKnobImageSource ();
39 isource
.led_color
= color_from_string ("#9fc717");
40 _image_source
= isource
;
41 set_size_request((int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
47 if (value
!= null && value
!= _image_source
) {
48 _image_source
= value
;
50 set_size_request((int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
56 private KnobMode _knob_mode
;
57 [Description(nick
="knob mode", blurb
="determines, how the knob will work / look like")]
58 public KnobMode knob_mode
{
63 if (value
!= _knob_mode
) {
71 [Description(nick
="decimal places", blurb
="The value will be rounded to the given number of decimal places")]
72 public uint decimal_places
{ get; set; default = 2; }
80 images
= new HashTable
<string, Cairo
.Surface
> (GLib
.str_hash
, GLib
.str_equal
);
84 Adjustment adj
= new
Adjustment(0, 0, 1, 0.01, 0.5, 0);
86 //XXX set_flags (WidgetFlags.CAN_FOCUS);
87 add_events (Gdk
.EventMask
.KEY_PRESS_MASK
| Gdk
.EventMask
.KEY_RELEASE_MASK
);
88 draw
.connect(on_draw
);
89 motion_notify_event
.connect(on_motion_notify_event
);
92 public Knob
.with_adjustment (Adjustment an_adjustment
) {
93 set_adjustment (an_adjustment
);
96 Cairo
.Surface
create_cache_surface_if_necessary (Cairo
.Surface target
) {
97 string image_state
= "%s;knob_mode=%u".printf (image_source
.to_string (), knob_mode
);
99 debug ("looking up state: '%s', hash: '%u'\n", image_state
, GLib
.str_hash(image_state
));
100 Cairo
.Surface? cached_surface
= images
.lookup (image_state
);
101 debug ("Result: %u\n", (uint)cached_surface
);
102 if ( cached_surface
!= null) {
103 debug ("Cache hit!\n");
104 return cached_surface
;
107 debug ("Cache miss!\n");
109 // looks like its either first call or the widget has been resized.
110 // create the cache_surface.
111 var cache_width
= image_source
.get_knob_width () * image_source
.phases
;
112 var cache_height
= image_source
.get_knob_height ();
114 Cairo
.Surface surface
= new Cairo
.Surface
.similar (target
,
115 Cairo
.Content
.COLOR_ALPHA
,
119 Cairo
.Context cache_cr
= new Cairo
.Context ( surface
);
120 cache_cr
.set_source_rgba (0, 0, 0, 0);
121 cache_cr
.rectangle (0, 0, cache_width
, cache_height
);
124 image_source
.paint_knobs (cache_cr
,
126 image_source
.get_knob_width () / 2,
127 image_source
.get_knob_height () / 2);
129 images
.insert (image_state
.dup(), surface
);
130 debug ("HashTable size: %u\n", images
.size());
131 images
.foreach ((k
, v
) => { debug ("Key: '%s', Value: %x\n", (string)k
, (uint)v
); });
135 public bool on_draw (Cairo
.Context cr
) {
136 Gtk
.Allocation allocation
;
137 get_allocation(out allocation
);
138 Adjustment adj
= get_adjustment ();
140 //debug ("adjustment = %p value = %f\n", adj, adj.value);
141 int ox
= allocation
.x
, oy
= allocation
.y
;
143 ox
+= (int)(allocation
.width
- image_source
.get_knob_width ()) / 2;
144 oy
+= (int)(allocation
.height
- image_source
.get_knob_height ()) / 2;
146 int phase
= (int)((adj
.value
- adj
.lower
) * 64 / (adj
.upper
- adj
.lower
));
147 // skip middle phase except for true middle value
148 if (knob_mode
== KnobMode
.BIPOLAR
&& phase
== 32) {
149 double pt
= (adj
.value
- adj
.lower
) * 2.0 / (adj
.upper
- adj
.lower
) - 1.0;
158 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
159 if (knob_mode
== KnobMode
.ENDLESS
&& (phase
% 16) == 0) {
163 double nom
= adj
.lower
+ phase
* (adj
.upper
- adj
.lower
) / 64.0;
164 double diff
= (adj
.value
- nom
) / (adj
.upper
- adj
.lower
);
167 phase
= (phase
+ 1) % 64;
170 phase
= (phase
+ 63) % 64;
173 if (cache_surface
== null) {
174 cache_surface
= create_cache_surface_if_necessary (cr
.get_target ());
177 assert (cache_surface
!= null);
178 cr
.set_source_surface (cache_surface
, ox
- phase
* image_source
.get_knob_width (), oy
);
179 cr
.rectangle (ox
, oy
, image_source
.get_knob_width (), image_source
.get_knob_height ());
182 // debug ("exposed %p %d+%d", window, allocation.x, allocation.y);
184 get_style_context().render_focus (
187 (int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
193 public new
virtual void get_preferred_width (out int minimum_width
, out int natural_width
) {
194 minimum_width
= (int)image_source
.get_knob_width ();
195 natural_width
= (int)image_source
.get_knob_width ();
198 public new
virtual void get_preferred_height_for_width (int width
, out int minimum_height
, out int natural_height
) {
199 minimum_height
= (int)image_source
.get_knob_height ();
200 natural_height
= (int)image_source
.get_knob_height ();
203 public new
virtual void get_preferred_height (out int minimum_height
, out int natural_height
) {
204 minimum_height
= (int)image_source
.get_knob_height ();
205 natural_height
= (int)image_source
.get_knob_height ();
208 public new
virtual void get_preferred_width_for_height (int height
, out int minimum_width
, out int natural_width
) {
209 minimum_width
= (int)image_source
.get_knob_width ();
210 natural_width
= (int)image_source
.get_knob_width ();
213 public new
double get_value () {
214 debug ("Value: %1.30f\n", base.get_value());
215 var rounded
= GLib
.Math
.rint (base.get_value() * Math
.pow (10.0, decimal_places
))
216 / Math
.pow (10.0, decimal_places
);
217 debug ("Rounded Value: %1.30f\n", rounded
);
221 void knob_incr (int dir_down
) {
222 Adjustment adj
= get_adjustment ();
224 int oldstep
= (int)(0.5 + (adj
.value
- adj
.lower
) / adj
.step_increment
);
226 int nsteps
= (int)(0.5 + (adj
.upper
- adj
.lower
) / adj
.step_increment
); // less 1 actually
231 if (knob_mode
== KnobMode
.ENDLESS
&& step
>= nsteps
)
233 if (knob_mode
== KnobMode
.ENDLESS
&& step
< 0)
234 step
= nsteps
- (nsteps
- step
) % nsteps
;
236 // trying to reduce error cumulation here, by counting from lowest or from highest
237 double value
= (adj
.lower
+ step
* (adj
.upper
- adj
.lower
) / nsteps
);
239 // debug ("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
243 public override bool key_press_event (Gdk
.EventKey event
) {
244 Adjustment adj
= get_adjustment ();
246 switch (event
.keyval
)
249 set_value (adj
.lower
);
253 set_value (adj
.upper
);
266 start_value
= (int)get_value ();
274 private Gdk
.Cursor? cursor
= null;
276 public override bool key_release_event (Gdk
.EventKey event
) {
277 if (event
.keyval
== GDK_Shift_L
|| event
.keyval
== GDK_Shift_R
) {
278 start_value
= (int)get_value ();
286 public override bool button_press_event (Gdk
.EventButton event
)
290 start_x
= (int)event
.x
;
291 start_y
= (int)event
.y
;
292 start_value
= get_value ();
293 debug ("PRESS: start_value: %f, event.y: %d, start_y: %d", start_value
, (int)event
.y
, (int)start_y
);
295 cursor
= get_window().get_cursor ();
296 get_window().set_cursor (new Gdk
.Cursor (Gdk
.CursorType
.SB_V_DOUBLE_ARROW
));
300 public override bool button_release_event (Gdk
.EventButton event
)
306 get_window().set_cursor (cursor
);
308 get_window().set_cursor (new Gdk
.Cursor (Gdk
.CursorType
.ARROW
));
313 inline
double endless (double value
)
316 return Math
.fmod (value
, 1.0);
318 return Math
.fmod (1.0 - Math
.fmod (1.0 - value
, 1.0), 1.0);
321 inline
double deadzone (double value
, double incr
, double scale
)
323 double dzw
= 10.0 / scale
;
331 if (value
>= (0.5 - dzw
) && value
<= (0.5 + dzw
))
338 public bool on_motion_notify_event (Gdk
.EventMotion event
) {
339 Adjustment adj
= get_adjustment ();
340 double scale
= (event
.state
== Gdk
.ModifierType
.SHIFT_MASK
) ?
1000.0 : 100.0;
341 scale
/= (adj
.upper
- adj
.lower
);
346 //debug ("start_value: %d, event.y: %d, start_y: %d", (int)start_value, (int)event.y, (int)start_y);
347 if (knob_mode
== KnobMode
.ENDLESS
) {
348 set_value (endless ((start_value
- (event
.y
- start_y
) / scale
)));
349 } else if (knob_mode
== KnobMode
.BIPOLAR
) {
350 set_value (deadzone (start_value
, (-(event
.y
- start_y
) / scale
), scale
));
352 set_value (start_value
- (event
.y
- start_y
) / scale
);
356 last_y
= (int)event
.y
;
360 public override bool scroll_event (Gdk
.EventScroll event
) {
361 knob_incr (event
.direction
);
367 } // namespace Prolooks