Fix can_focus / remove deprecated HBox/VBox
[libprolooks.git] / src / LineGraph.vala
blob39377401e9c022fb48ed106852c0e2123c281b21
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
7 using Gdk;
8 using Cairo;
10 namespace Prolooks {
12 public interface ILineProperties : GLib.Object {
13 public abstract void set_line_color (float r, float g, float b, float a = 1.0f);
14 public abstract void set_line_width (float width);
17 public class CairoLineProperties : GLib.Object, ILineProperties {
18 public Context context { get; set; }
19 public void set_line_color (float r, float g, float b, float a = 1.0f) { context.set_source_rgba (r, g, b, a); }
20 public void set_line_width (float width) { context.set_line_width (width); }
23 public interface IGraphSource : GLib.Object {
24 /** Obtain subindex'th graph of parameter 'index'
25 * @param index parameter/graph number (usually tied to particular plugin control port)
26 * @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters)
27 * @param data buffer for normalized output values
28 * @param points number of points to fill
29 * @param line_props line properties to adjust (for multicolour graphs etc.)
30 * @retval true graph data was returned; subindex+1 graph may or may not be available
31 * @retval false graph data was not returned; subindex+1 graph does not exist either
33 public abstract bool get_graph (int index, int subindex, ref float[] data, int points, ref ILineProperties line_props);
35 /** Obtain subindex'th dot of parameter 'index'
36 * @param index parameter/dot number (usually tied to particular plugin control port)
37 * @param subindex dot number (there may be multiple dots graphs for one parameter)
39 public abstract bool get_dot (int index, int subindex, ref float x, ref float y, ref int size, ref ILineProperties line_props);
41 /** Obtain subindex'th dot of parameter 'index'
42 * @param index parameter/dot number (usually tied to particular plugin control port)
43 * @param subindex dot number (there may be multiple dots graphs for one parameter)
46 /** Defines how the graph line of the given subindex will look like
48 public abstract void set_subindex_look (ILineProperties line_props, int subindex);
50 public abstract bool get_gridline (int index, int subindex, ref float pos, ref bool vertical, ref string legend, ref ILineProperties line_props);
52 /** Obtain subindex'th static graph of parameter index (static graphs are only dependent on parameter value, not plugin state)
53 * @param index parameter/graph number (usually tied to particular plugin control port)
54 * @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters)
55 * @param value parameter value to pick the graph for
56 * @param data buffer for normalized output values
57 * @param points number of points to fill
58 * @param line_props line properties to adjust (for multicolour graphs etc.)
59 * @retval true graph data was returned; subindex+1 graph may or may not be available
60 * @retval false graph data was not returned; subindex+1 graph does not exist either
62 public abstract bool get_static_graph (int index, int subindex, float value, ref float[] data, int points, ref ILineProperties line_props);
64 /** Return which graphs need to be redrawn and which can be cached for later reuse
65 * @param generation 0 (at start) or the last value returned by the function (corresponds to a set of input values)
66 * @param subindex_graph First graph that has to be redrawn (because it depends on values that might have changed)
67 * @param subindex_dot First dot that has to be redrawn
68 * @param subindex_gridline First gridline/legend that has to be redrawn
69 * @retval Current generation (to pass when calling the function next time); if different than passed generation value, call the function again to retrieve which graph offsets should be put into cache
71 public abstract int get_changed_offsets (int generation, ref int subindex_graph, ref int subindex_dot, ref int subindex_gridline);
74 public class DefaultGraphSource : GLib.Object, IGraphSource {
75 public virtual bool get_graph (int index, int subindex, ref float[] data, int points, ref ILineProperties line_props) {
76 return false;
79 public virtual bool get_dot (int index, int subindex, ref float x, ref float y, ref int size, ref ILineProperties line_props) {
80 return false;
83 public virtual void set_subindex_look (ILineProperties line_props, int subindex)
85 if ((subindex & 1) != 0) {
86 Cairo.Color c = new Cairo.Color.from_string (DisplayBase.text_color_default);
87 line_props.set_line_color ((float) c.red, (float) c.green, (float) c.blue);
88 } else {
89 line_props.set_line_color (0f, 1f, 0.75f);
91 line_props.set_line_width (1.5f);
94 public virtual bool get_gridline (int index, int subindex, ref float pos, ref bool vertical, ref string legend, ref ILineProperties line_props) {
95 return false;
98 public virtual bool get_static_graph (int index, int subindex, float value, ref float[] data, int points, ref ILineProperties line_props) {
99 return false;
102 public virtual int get_changed_offsets (int generation, ref int subindex_graph, ref int subindex_dot, ref int subindex_gridline) {
103 subindex_graph = subindex_dot = subindex_gridline = 0;
104 return 0;
108 public interface IHasFrequencyResponse : GLib.Object {
109 public abstract float freq_gain (int subindex, float freq, float srate);
110 public abstract int sampling_rate ();
113 public class TestFrequencyResponseSource : IHasFrequencyResponse, GLib.Object {
114 public float freq_gain (int subindex, float freq, float srate) {
115 return freq / srate;
117 public int sampling_rate() {
118 return 44100;
122 public class FrequencyResponseGraphSource : DefaultGraphSource {
123 public int max_index { get; set; }
124 public int max_subindex { get; set ;}
126 construct {
127 max_index = 1;
128 max_subindex = 1;
129 _freq_response_source = new TestFrequencyResponseSource ();
132 private IHasFrequencyResponse _freq_response_source;
134 public IHasFrequencyResponse freq_response_source {
135 get {
136 return _freq_response_source;
138 set {
139 _freq_response_source = value;
143 public static inline float dB_grid (float amp) {
144 return Math.logf (amp) / Math.logf (256.0f) + 0.4f;
147 public static bool get_freq_graph (IHasFrequencyResponse fx, int subindex, ref float[] data, int points)
149 for (int i = 0; i < points; i++)
151 double freq = 20.0f * Math.pow (20000.0f / 20.0f, i * 1.0f / (float)points);
152 data[i] = dB_grid (fx.freq_gain (subindex, (float)freq, fx.sampling_rate ()));
154 return true;
157 public override bool get_graph (int index, int subindex, ref float[] data, int points, ref ILineProperties line_props) {
158 if (_freq_response_source != null && index <= max_index && subindex <= max_subindex) {
159 return get_freq_graph (_freq_response_source, subindex, ref data, points);
160 } else {
161 return false;
165 public override bool get_gridline (int index, int subindex, ref float pos, ref bool vertical, ref string legend, ref ILineProperties line_props) {
166 return get_gridline_use (index, subindex, ref pos, ref vertical, ref legend, ref line_props, true);
169 public bool get_gridline_use (int index, int subindex, ref float pos, ref bool vertical, ref string legend, ref ILineProperties line_props, bool use_frequencies = true) {
170 if (subindex < 0 )
171 return false;
173 if (use_frequencies)
175 if (subindex < 28)
177 vertical = true;
178 if (subindex == 9) legend = "100 Hz";
179 if (subindex == 18) legend = "1 kHz";
180 if (subindex == 27) legend = "10 kHz";
181 float freq = 100;
182 if (subindex < 9)
183 freq = 10 * (subindex + 1);
184 else if (subindex < 18)
185 freq = 100 * (subindex - 9 + 1);
186 else if (subindex < 27)
187 freq = 1000 * (subindex - 18 + 1);
188 else
189 freq = 10000 * (subindex - 27 + 1);
190 pos = Math.logf (freq / 20.0f) / Math.logf (1000f);
191 if (legend.length != 0)
192 line_props.set_line_color (0.25f, 0.25f, 0.25f, 0.75f);
193 else
194 line_props.set_line_color (0.25f, 0.25f, 0.25f, 0.5f);
195 return true;
197 subindex -= 28;
199 if (subindex >= 32)
200 return false;
201 float gain = 16.0f / (float) (1 << subindex);
202 pos = dB_grid (gain);
203 if (pos < -1)
204 return false;
205 if (subindex != 4)
206 line_props.set_line_color (0.25f, 0.25f, 0.25f, ((subindex & 1) != 0) ? 0.5f : 0.75f);
207 if ((subindex & 1) == 0)
209 legend = "%d dB".printf (24 - 6 * subindex);
211 vertical = false;
212 return true;
215 int get_changed_offsets (int generation, ref int subindex_graph, ref int subindex_dot, ref int subindex_gridline)
217 subindex_graph = 0;
218 subindex_dot = 0;
219 subindex_gridline = (generation != 0) ? int.MAX : 0;
220 return 1;
224 public class LineGraph : DrawingArea {
226 public IGraphSource source { get; set; }
228 private int source_id;
229 private bool is_square;
230 private Surface? cache_surface;
231 private int last_generation;
233 construct {
234 set_size_request (40, 40);
235 cache_surface = null;
236 last_generation = 0;
237 var src = new FrequencyResponseGraphSource();
238 source = src;
239 draw.connect(on_draw);
240 size_allocate.connect(on_size_allocate);
243 private void copy_cache_to_window (Context c) {
244 c.save ();
245 c.set_source_surface (cache_surface, 0, 0);
246 c.paint ();
247 c.restore ();
250 /* unused
251 private void copy_window_to_cache (Context c) {
252 Context cache_cr = new Context (cache_surface);
253 Surface window_surface = c.get_target ();
254 cache_cr.set_source_surface (window_surface, 0, 0);
255 cache_cr.paint ();
259 public static void draw_grid (Context c, ref string legend, bool vertical, float pos, int phase, int sx, int sy) {
260 int ox = 1, oy = 1;
261 TextExtents tx = TextExtents ();
263 if (legend.length != 0) {
264 c.text_extents (legend, out tx);
267 if (vertical)
269 float x = Math.floorf (ox + pos * sx) + 0.5f;
271 if (phase == 1)
273 c.move_to (x, oy);
274 c.line_to (x, oy + sy);
275 c.stroke ();
278 if (phase == 2 && (legend.length != 0)) {
280 c.set_source_rgba (1.0, 1.0, 1.0, 0.75);
281 c.move_to (x - (tx.x_bearing + tx.width / 2.0), oy + sy - 2);
282 c.show_text (legend);
285 else
287 float y = Math.floorf (oy + sy / 2 - (sy / 2 - 1) * pos) + 0.5f;
289 if (phase == 1)
291 c.move_to (ox, y);
292 c.line_to (ox + sx, y);
293 c.stroke ();
296 if (phase == 2 && (legend.length != 0)) {
297 c.set_source_rgba (1.0, 1.0, 1.0, 0.75);
298 c.move_to (ox + sx - 2 - tx.width, y + tx.height/2 - 1);
299 c.show_text (legend);
305 static void draw_graph (Context c, ref float[] data, int sx, int sy) {
306 int ox=1, oy=1;
308 for (int i = 0; i < 2 * sx; i++)
310 int y = (int) (oy + sy / 2 - (sy / 2 - 1) * data[i]);
311 //if (y < oy) y = oy;
312 //if (y >= oy + sy) y = oy + sy - 1;
313 if (i != 0) {
314 c.line_to (ox + i * 0.5, y);
315 } else {
316 c.move_to (ox, y);
319 c.stroke ();
322 public bool on_draw (Cairo.Context c) {
323 Gtk.Allocation allocation;
324 get_allocation(out allocation);
326 int ox = 1, oy = 1;
327 int sx = allocation.width - 2, sy = allocation.height - 2;
329 Gdk.Color sc = { 0, 0, 0, 0 };
331 bool cache_dirty = false;
333 if (cache_surface == null) {
334 // looks like its either first call or the widget has been resized.
335 // create the cache_surface.
336 Surface window_surface = c.get_target ();
337 cache_surface = new Surface.similar (window_surface,
338 Content.COLOR,
339 allocation.width,
340 allocation.height);
342 //Context cache_cr = new Context (cache_surface);
343 //cache_cr.set_source_surface (window_surface, 0,0);
344 //cache_cr.paint ();
346 cache_dirty = true;
349 c.select_font_face ("Bitstream Vera Sans", FontSlant.NORMAL, FontWeight.NORMAL);
350 c.set_font_size (9);
352 cairo_set_source_color (c, sc);
353 c.rectangle (ox, oy, sx, sy);
354 c.clip ();
355 CairoLineProperties cairoimpl = new CairoLineProperties ();
356 cairoimpl.context = c;
357 ILineProperties cimpl = cairoimpl;
359 if (source != null) {
360 float pos = 0;
361 bool vertical = false;
362 string legend;
363 float[] data = new float[2 * sx];
364 Gdk.Color sc2 = { 0, 0, 65535, 0 };
365 float x = 0, y = 0;
366 int size = 0;
367 Gdk.Color sc3 = { 0, 32767, 65535, 0 };
369 int graph_n = 0, grid_n = 0, dot_n = 0, grid_n_save = 0;
371 int cache_graph_index = 0, cache_dot_index = 0, cache_grid_index = 0;
372 int gen_index = source.get_changed_offsets (last_generation, ref cache_graph_index, ref cache_dot_index, ref cache_grid_index);
374 if (cache_dirty || (gen_index != last_generation)) {
375 Context cache_cr = new Context (cache_surface);
376 cache_cr.select_font_face ("Bitstream Vera Sans", FontSlant.NORMAL, FontWeight.NORMAL);
377 cache_cr.set_font_size (9);
379 cairo_set_source_color (cache_cr, sc);
380 cache_cr.rectangle (ox, oy, sx, sy);
381 cache_cr.clip_preserve ();
382 cache_cr.fill ();
384 CairoLineProperties cache_cairoimpl = new CairoLineProperties ();
385 cache_cairoimpl.context = cache_cr;
386 ILineProperties cache_cimpl = cache_cairoimpl;
388 source.get_changed_offsets (gen_index, ref cache_graph_index, ref cache_dot_index, ref cache_grid_index);
389 last_generation = gen_index;
391 cache_cr.set_line_width (1);
392 for (int phase = 1; phase <= 2; phase++)
394 for (grid_n = 0;; grid_n++)
396 legend = "";
397 cache_cr.set_source_rgba (1, 1, 1, 0.5);
398 if ((grid_n < cache_grid_index) &&
399 source.get_gridline (source_id, grid_n, ref pos, ref vertical, ref legend, ref cache_cimpl)) {
400 draw_grid (cache_cr, ref legend, vertical, pos, phase, sx, sy);
401 } else {
402 break;
406 grid_n_save = grid_n;
408 cairo_set_source_color (cache_cr, sc2);
409 cache_cr.set_line_join (LineJoin.MITER);
410 cache_cr.set_line_width (1);
412 for (graph_n = 0;
413 (graph_n<cache_graph_index) &&
414 source.get_graph (source_id, graph_n, ref data, 2 * sx, ref cache_cimpl);
415 graph_n++)
417 draw_graph (cache_cr, ref data, sx, sy);
420 cairo_set_source_color (cache_cr, sc3);
422 for (dot_n = 0;; dot_n++)
424 size = 3;
425 if ((dot_n<cache_dot_index) &&
426 source.get_dot (source_id, dot_n, ref x, ref y, ref size, ref cache_cimpl)) {
428 int yv = (int) (oy + sy / 2 - (sy / 2 - 1) * y);
429 cache_cr.arc (ox + x * sx, yv, size, 0, 2 * Math.PI);
430 cache_cr.fill ();
431 } else {
432 break;
436 cache_cr = null;
437 copy_cache_to_window (c);
439 } else {
440 grid_n_save = cache_grid_index;
441 graph_n = cache_graph_index;
442 dot_n = cache_dot_index;
443 copy_cache_to_window (c);
446 c.set_line_width (1);
448 for (int phase = 1; phase <= 2; phase++)
450 for (int gn = grid_n_save;; gn++)
452 legend = "";
453 c.set_source_rgba (1, 1, 1, 0.5);
454 if (source.get_gridline (source_id, gn, ref pos, ref vertical, ref legend, ref cimpl)) {
455 draw_grid (c, ref legend, vertical, pos, phase, sx, sy);
456 } else {
457 break;
462 cairo_set_source_color (c, sc2);
463 c.set_line_join (LineJoin.MITER);
464 c.set_line_width (1);
466 for (int gn = graph_n;
467 source.get_graph (source_id, gn, ref data, 2 * sx, ref cimpl);
468 gn++)
470 source.set_subindex_look (cimpl, gn);
471 draw_graph (c, ref data, sx, sy);
474 cairo_set_source_color (c, sc3);
475 for (int gn = dot_n;; gn++)
477 size = 3;
478 if (source.get_dot (source_id, gn, ref x, ref y, ref size, ref cimpl)) {
479 int yv = (int) (oy + sy / 2 - (sy / 2 - 1) * y);
480 c.arc (ox + x * sx, yv, size, 0, 2 * Math.PI);
481 c.fill ();
482 } else {
483 break;
489 get_style_context().render_frame(c, ox - 1, oy - 1, sx + 2, sy + 2);
490 //stderr.printf ("exposed %p %dx%d %d+%d\n", window, event.area.x, event.area.y, event.area.width, event.area.height);
492 return true;
495 /* unused
496 int update_if (int last_drawn_generation)
498 int generation = last_drawn_generation;
499 if (source != null)
501 int subgraph = 0, dot = 0, gridline = 0;
502 generation = source.get_changed_offsets (generation, ref subgraph, ref dot, ref gridline);
503 if (subgraph == int.MAX && dot == int.MAX && gridline == int.MAX && generation == last_drawn_generation) {
504 return generation;
507 queue_draw ();
509 return generation;
513 public void on_size_allocate (Gtk.Allocation allocation) {
514 set_allocation(allocation);
516 Gtk.Allocation a = allocation;
518 if (is_square)
520 if (a.width > a.height)
522 a.x += (a.width - a.height) / 2;
523 a.width = a.height;
525 if (a.width < a.height)
527 a.y += (a.height - a.width) / 2;
528 a.height = a.width;
532 base.size_allocate (a);
533 cache_surface = null;
537 } // namespace Prolooks