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;
28 private Cairo
.Surface? cache_surface
= null;
30 public void* user_data
{ get; set; }
31 private IKnobImageSource _image_source
;
32 [Description(nick
="image source", blurb
="provides the routines for drawing the knob images")]
33 public IKnobImageSource image_source
{
35 if (_image_source
== null) {
36 var isource
= new
SimpleKnobImageSource ();
37 isource
.led_color
= rgba_from_string ("#9fc717");
38 _image_source
= isource
;
39 set_size_request((int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
45 if (value
!= null && value
!= _image_source
) {
46 _image_source
= value
;
48 set_size_request((int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
54 private KnobMode _knob_mode
;
55 [Description(nick
="knob mode", blurb
="determines, how the knob will work / look like")]
56 public KnobMode knob_mode
{
61 if (value
!= _knob_mode
) {
69 [Description(nick
="decimal places", blurb
="The value will be rounded to the given number of decimal places")]
70 public uint decimal_places
{ get; set; default = 2; }
78 images
= new HashTable
<string, Cairo
.Surface
> (GLib
.str_hash
, GLib
.str_equal
);
82 Adjustment adj
= new
Adjustment(0, 0, 1, 0.01, 0.5, 0);
85 add_events (Gdk
.EventMask
.KEY_PRESS_MASK
| Gdk
.EventMask
.KEY_RELEASE_MASK
);
86 draw
.connect(on_draw
);
87 motion_notify_event
.connect(on_motion_notify_event
);
90 public Knob
.with_adjustment (Adjustment an_adjustment
) {
91 set_adjustment (an_adjustment
);
94 Cairo
.Surface
create_cache_surface_if_necessary (Cairo
.Surface target
) {
95 string image_state
= "%s;knob_mode=%u".printf (image_source
.to_string (), knob_mode
);
97 debug ("looking up state: '%s', hash: '%u'\n", image_state
, GLib
.str_hash(image_state
));
98 Cairo
.Surface? cached_surface
= images
.lookup (image_state
);
99 debug ("Result: %u\n", (uint)cached_surface
);
100 if ( cached_surface
!= null) {
101 debug ("Cache hit!\n");
102 return cached_surface
;
105 debug ("Cache miss!\n");
107 // looks like its either first call or the widget has been resized.
108 // create the cache_surface.
109 var cache_width
= image_source
.get_knob_width () * image_source
.phases
;
110 var cache_height
= image_source
.get_knob_height ();
112 Cairo
.Surface surface
= new Cairo
.Surface
.similar (target
,
113 Cairo
.Content
.COLOR_ALPHA
,
117 Cairo
.Context cache_cr
= new Cairo
.Context ( surface
);
118 cache_cr
.set_source_rgba (0, 0, 0, 0);
119 cache_cr
.rectangle (0, 0, cache_width
, cache_height
);
122 image_source
.paint_knobs (cache_cr
,
124 image_source
.get_knob_width () / 2,
125 image_source
.get_knob_height () / 2);
127 images
.insert (image_state
.dup(), surface
);
128 debug ("HashTable size: %u\n", images
.size());
129 images
.foreach ((k
, v
) => { debug ("Key: '%s', Value: %x\n", (string)k
, (uint)v
); });
133 public bool on_draw (Cairo
.Context cr
) {
134 Gtk
.Allocation allocation
;
135 get_allocation(out allocation
);
136 Adjustment adj
= get_adjustment ();
138 debug ("adjustment = %p value = %f\n", adj
, adj
.value
);
139 int ox
= allocation
.x
, oy
= allocation
.y
;
141 int phase
= (int)((adj
.value
- adj
.lower
) * 64 / (adj
.upper
- adj
.lower
));
142 // skip middle phase except for true middle value
143 if (knob_mode
== KnobMode
.BIPOLAR
&& phase
== 32) {
144 double pt
= (adj
.value
- adj
.lower
) * 2.0 / (adj
.upper
- adj
.lower
) - 1.0;
153 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
154 if (knob_mode
== KnobMode
.ENDLESS
&& (phase
% 16) == 0) {
158 double nom
= adj
.lower
+ phase
* (adj
.upper
- adj
.lower
) / 64.0;
159 double diff
= (adj
.value
- nom
) / (adj
.upper
- adj
.lower
);
162 phase
= (phase
+ 1) % 64;
165 phase
= (phase
+ 63) % 64;
168 if (cache_surface
== null) {
169 cache_surface
= create_cache_surface_if_necessary (cr
.get_target ());
172 assert (cache_surface
!= null);
173 cr
.set_source_surface (cache_surface
, - phase
* image_source
.get_knob_width (), oy
);
174 cr
.rectangle (0, 0, image_source
.get_knob_width (), image_source
.get_knob_height ());
177 debug ("exposed %p %d+%d", get_window(), allocation
.x
, allocation
.y
);
179 get_style_context().render_focus (
182 (int)image_source
.get_knob_width (), (int)image_source
.get_knob_height ());
188 public new
virtual void get_preferred_width (out int minimum_width
, out int natural_width
) {
189 minimum_width
= (int)image_source
.get_knob_width ();
190 natural_width
= (int)image_source
.get_knob_width ();
193 public new
virtual void get_preferred_height_for_width (int width
, out int minimum_height
, out int natural_height
) {
194 minimum_height
= (int)image_source
.get_knob_height ();
195 natural_height
= (int)image_source
.get_knob_height ();
198 public new
virtual void get_preferred_height (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_width_for_height (int height
, out int minimum_width
, out int natural_width
) {
204 minimum_width
= (int)image_source
.get_knob_width ();
205 natural_width
= (int)image_source
.get_knob_width ();
208 public new
double get_value () {
209 debug ("Value: %1.30f\n", base.get_value());
210 var rounded
= GLib
.Math
.rint (base.get_value() * Math
.pow (10.0, decimal_places
))
211 / Math
.pow (10.0, decimal_places
);
212 debug ("Rounded Value: %1.30f\n", rounded
);
216 void knob_incr (int dir_down
) {
217 Adjustment adj
= get_adjustment ();
219 int oldstep
= (int)(0.5 + (adj
.value
- adj
.lower
) / adj
.step_increment
);
221 int nsteps
= (int)(0.5 + (adj
.upper
- adj
.lower
) / adj
.step_increment
); // less 1 actually
226 if (knob_mode
== KnobMode
.ENDLESS
&& step
>= nsteps
)
228 if (knob_mode
== KnobMode
.ENDLESS
&& step
< 0)
229 step
= nsteps
- (nsteps
- step
) % nsteps
;
231 // trying to reduce error cumulation here, by counting from lowest or from highest
232 double value
= (adj
.lower
+ step
* (adj
.upper
- adj
.lower
) / nsteps
);
234 // debug ("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
238 public override bool key_press_event (Gdk
.EventKey event
) {
239 Adjustment adj
= get_adjustment ();
241 switch (event
.keyval
)
244 set_value (adj
.lower
);
248 set_value (adj
.upper
);
261 start_value
= (int)get_value ();
269 private Gdk
.Cursor? cursor
= null;
271 public override bool key_release_event (Gdk
.EventKey event
) {
272 if (event
.keyval
== GDK_Shift_L
|| event
.keyval
== GDK_Shift_R
) {
273 start_value
= (int)get_value ();
281 public override bool button_press_event (Gdk
.EventButton event
)
285 start_x
= (int)event
.x
;
286 start_y
= (int)event
.y
;
287 start_value
= get_value ();
288 debug ("PRESS: start_value: %f, event.y: %d, start_y: %d", start_value
, (int)event
.y
, (int)start_y
);
290 cursor
= get_window().get_cursor ();
291 get_window().set_cursor (new Gdk
.Cursor (Gdk
.CursorType
.SB_V_DOUBLE_ARROW
));
295 public override bool button_release_event (Gdk
.EventButton event
)
301 get_window().set_cursor (cursor
);
303 get_window().set_cursor (new Gdk
.Cursor (Gdk
.CursorType
.ARROW
));
308 inline
double endless (double value
)
311 return Math
.fmod (value
, 1.0);
313 return Math
.fmod (1.0 - Math
.fmod (1.0 - value
, 1.0), 1.0);
316 inline
double deadzone (double value
, double incr
, double scale
)
318 double dzw
= 10.0 / scale
;
326 if (value
>= (0.5 - dzw
) && value
<= (0.5 + dzw
))
333 public bool on_motion_notify_event (Gdk
.EventMotion event
) {
334 Adjustment adj
= get_adjustment ();
335 double scale
= (event
.state
== Gdk
.ModifierType
.SHIFT_MASK
) ?
1000.0 : 100.0;
336 scale
/= (adj
.upper
- adj
.lower
);
341 //debug ("start_value: %d, event.y: %d, start_y: %d", (int)start_value, (int)event.y, (int)start_y);
342 if (knob_mode
== KnobMode
.ENDLESS
) {
343 set_value (endless ((start_value
- (event
.y
- start_y
) / scale
)));
344 } else if (knob_mode
== KnobMode
.BIPOLAR
) {
345 set_value (deadzone (start_value
, (-(event
.y
- start_y
) / scale
), scale
));
347 set_value (start_value
- (event
.y
- start_y
) / scale
);
351 last_y
= (int)event
.y
;
355 public override bool scroll_event (Gdk
.EventScroll event
) {
356 knob_incr (event
.direction
);
362 } // namespace Prolooks