Remove uses of deprecated HBox/VBox
[libprolooks.git] / src / Knob.vala
blobce8f9d3b85301a74b91cdf7c9a180e63bfbaa70d
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
8 namespace Prolooks {
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 {
18 POSITIVE,
19 BIPOLAR,
20 NEGATIVE,
21 ENDLESS
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 {
36 get {
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 ());
43 return _image_source;
46 set {
47 if (value != null && value != _image_source) {
48 _image_source = value;
49 cache_surface = null;
50 set_size_request((int)image_source.get_knob_width (), (int)image_source.get_knob_height ());
51 queue_draw ();
56 private KnobMode _knob_mode;
57 [Description(nick="knob mode", blurb="determines, how the knob will work / look like")]
58 public KnobMode knob_mode {
59 get {
60 return _knob_mode;
62 set {
63 if (value != _knob_mode) {
64 _knob_mode = value;
65 cache_surface = null;
66 queue_draw ();
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; }
74 double start_value;
75 int start_x;
76 int start_y;
77 int last_y;
79 static construct {
80 images = new HashTable<string, Cairo.Surface> (GLib.str_hash, GLib.str_equal);
83 construct {
84 Adjustment adj = new Adjustment(0, 0, 1, 0.01, 0.5, 0);
85 set_adjustment (adj);
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,
116 (int)cache_width,
117 (int)cache_height);
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);
122 cache_cr.fill ();
124 image_source.paint_knobs (cache_cr,
125 knob_mode,
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); });
132 return surface;
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;
151 if (pt < 0)
152 phase = 31;
154 if (pt > 0)
155 phase = 33;
158 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
159 if (knob_mode == KnobMode.ENDLESS && (phase % 16) == 0) {
160 if (phase == 64)
161 phase = 0;
163 double nom = adj.lower + phase * (adj.upper - adj.lower) / 64.0;
164 double diff = (adj.value - nom) / (adj.upper - adj.lower);
166 if (diff > 0.0001)
167 phase = (phase + 1) % 64;
169 if (diff < -0.0001)
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 ());
180 cr.fill ();
182 // debug ("exposed %p %d+%d", window, allocation.x, allocation.y);
183 if (is_focus) {
184 get_style_context().render_focus (
186 ox, oy,
187 (int)image_source.get_knob_width (), (int)image_source.get_knob_height ());
190 return true;
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);
218 return 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);
225 int step;
226 int nsteps = (int)(0.5 + (adj.upper - adj.lower) / adj.step_increment); // less 1 actually
227 if (dir_down != 0)
228 step = oldstep - 1;
229 else
230 step = oldstep + 1;
231 if (knob_mode == KnobMode.ENDLESS && step >= nsteps)
232 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);
238 set_value (value);
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)
248 case GDK_Home:
249 set_value (adj.lower);
250 return true;
252 case GDK_End:
253 set_value (adj.upper);
254 return true;
256 case GDK_Up:
257 knob_incr (0);
258 return true;
260 case GDK_Down:
261 knob_incr (1);
262 return true;
264 case GDK_Shift_L:
265 case GDK_Shift_R:
266 start_value = (int)get_value ();
267 start_y = last_y;
268 return true;
271 return false;
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 ();
279 start_y = last_y;
280 return true;
283 return false;
286 public override bool button_press_event (Gdk.EventButton event)
288 grab_focus ();
289 grab_add (this);
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));
297 return true;
300 public override bool button_release_event (Gdk.EventButton event)
302 if (has_grab ()) {
303 grab_remove (this);
305 if (cursor != null)
306 get_window().set_cursor (cursor);
307 else
308 get_window().set_cursor (new Gdk.Cursor (Gdk.CursorType.ARROW));
310 return false;
313 inline double endless (double value)
315 if (value >= 0.0)
316 return Math.fmod (value, 1.0);
317 else
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;
324 if (value >= 0.501)
325 value += dzw;
326 if (value < 0.499)
327 value -= dzw;
329 value += incr;
331 if (value >= (0.5 - dzw) && value <= (0.5 + dzw))
332 return 0.5;
333 if (value < 0.5)
334 return value + dzw;
335 return value - 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);
342 bool moved = false;
344 if (has_grab ())
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));
351 } else {
352 set_value (start_value - (event.y - start_y) / scale);
354 moved = true;
356 last_y = (int)event.y;
357 return moved;
360 public override bool scroll_event (Gdk.EventScroll event) {
361 knob_incr (event.direction);
362 return true;
365 } // class Knob
367 } // namespace Prolooks