2 * Custom controls (line graph, knob).
3 * Copyright (C) 2007 Krzysztof Foltman
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
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 GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General
16 * Public License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include <calf/custom_ctl.h>
22 #include <gdk/gdkkeysyms.h>
23 #include <cairo/cairo.h>
27 I don't really know how to do it, or if it can be done this way.
28 struct calf_ui_type_module
34 module = g_type_module_new();
35 g_type_module_set_name(module, "calf_custom_ctl");
36 g_type_module_use(module);
38 ~calf_ui_type_module()
40 g_type_module_unuse(module);
44 static calf_ui_type_module type_module;
48 calf_line_graph_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
50 g_assert(CALF_IS_LINE_GRAPH(widget
));
52 CalfLineGraph
*lg
= CALF_LINE_GRAPH(widget
);
53 int ox
= widget
->allocation
.x
+ 1, oy
= widget
->allocation
.y
+ 1;
54 int sx
= widget
->allocation
.width
- 2, sy
= widget
->allocation
.height
- 2;
56 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
58 GdkColor sc
= { 0, 0, 0, 0 };
60 gdk_cairo_set_source_color(c
, &sc
);
61 cairo_rectangle(c
, ox
, oy
, sx
, sy
);
62 cairo_clip_preserve(c
);
66 cairo_select_font_face(c
, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL
, CAIRO_FONT_WEIGHT_NORMAL
);
67 cairo_set_font_size(c
, 9);
71 bool vertical
= false;
72 cairo_set_line_width(c
, 1);
74 for(int phase
= 1; phase
<= 2; phase
++)
76 for(int gn
= 0; legend
= std::string(), cairo_set_source_rgba(c
, 1, 1, 1, 0.5), lg
->source
->get_gridline(lg
->source_id
, gn
, pos
, vertical
, legend
, &cimpl
); gn
++)
78 cairo_text_extents_t tx
;
80 cairo_text_extents(c
, legend
.c_str(), &tx
);
83 float x
= floor(ox
+ pos
* sx
) + 0.5;
86 cairo_move_to(c
, x
, oy
);
87 cairo_line_to(c
, x
, oy
+ sy
);
90 if (phase
== 2 && !legend
.empty()) {
92 cairo_set_source_rgba(c
, 1.0, 1.0, 1.0, 0.75);
93 cairo_move_to(c
, x
- (tx
.x_bearing
+ tx
.width
/ 2.0), oy
+ sy
- 2);
94 cairo_show_text(c
, legend
.c_str());
99 float y
= floor(oy
+ sy
/ 2 - (sy
/ 2 - 1) * pos
) + 0.5;
102 cairo_move_to(c
, ox
, y
);
103 cairo_line_to(c
, ox
+ sx
, y
);
106 if (phase
== 2 && !legend
.empty()) {
107 cairo_set_source_rgba(c
, 1.0, 1.0, 1.0, 0.75);
108 cairo_move_to(c
, ox
+ sx
- 2 - tx
.width
, y
+ tx
.height
/2 - 1);
109 cairo_show_text(c
, legend
.c_str());
114 float *data
= new float[2 * sx
];
115 GdkColor sc2
= { 0, 0, 65535, 0 };
116 gdk_cairo_set_source_color(c
, &sc2
);
117 cairo_set_line_join(c
, CAIRO_LINE_JOIN_MITER
);
118 cairo_set_line_width(c
, 1);
119 for(int gn
= 0; lg
->source
->get_graph(lg
->source_id
, gn
, data
, 2 * sx
, &cimpl
); gn
++)
121 for (int i
= 0; i
< 2 * sx
; i
++)
123 int y
= (int)(oy
+ sy
/ 2 - (sy
/ 2 - 1) * data
[i
]);
124 //if (y < oy) y = oy;
125 //if (y >= oy + sy) y = oy + sy - 1;
127 cairo_line_to(c
, ox
+ i
* 0.5, y
);
129 cairo_move_to(c
, ox
, y
);
136 GdkColor sc3
= { 0, 32767, 65535, 0 };
137 gdk_cairo_set_source_color(c
, &sc3
);
138 for(int gn
= 0; lg
->source
->get_dot(lg
->source_id
, gn
, x
, y
, size
= 3, &cimpl
); gn
++)
140 int yv
= (int)(oy
+ sy
/ 2 - (sy
/ 2 - 1) * y
);
141 cairo_arc(c
, ox
+ x
* sx
, yv
, size
, 0, 2 * M_PI
);
148 gtk_paint_shadow(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_IN
, NULL
, widget
, NULL
, ox
- 1, oy
- 1, sx
+ 2, sy
+ 2);
149 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
154 void calf_line_graph_set_square(CalfLineGraph
*graph
, bool is_square
)
156 g_assert(CALF_IS_LINE_GRAPH(graph
));
157 graph
->is_square
= is_square
;
161 calf_line_graph_size_request (GtkWidget
*widget
,
162 GtkRequisition
*requisition
)
164 g_assert(CALF_IS_LINE_GRAPH(widget
));
166 // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
170 calf_line_graph_size_allocate (GtkWidget
*widget
,
171 GtkAllocation
*allocation
)
173 g_assert(CALF_IS_LINE_GRAPH(widget
));
174 CalfLineGraph
*lg
= CALF_LINE_GRAPH(widget
);
176 widget
->allocation
= *allocation
;
177 GtkAllocation
&a
= widget
->allocation
;
180 if (a
.width
> a
.height
)
182 a
.x
+= (a
.width
- a
.height
) / 2;
185 if (a
.width
< a
.height
)
187 a
.y
+= (a
.height
- a
.width
) / 2;
194 calf_line_graph_class_init (CalfLineGraphClass
*klass
)
196 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
197 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
198 widget_class
->expose_event
= calf_line_graph_expose
;
199 widget_class
->size_request
= calf_line_graph_size_request
;
200 widget_class
->size_allocate
= calf_line_graph_size_allocate
;
204 calf_line_graph_init (CalfLineGraph
*self
)
206 GtkWidget
*widget
= GTK_WIDGET(self
);
207 GTK_WIDGET_SET_FLAGS (widget
, GTK_NO_WINDOW
);
208 widget
->requisition
.width
= 40;
209 widget
->requisition
.height
= 40;
213 calf_line_graph_new()
215 return GTK_WIDGET( g_object_new (CALF_TYPE_LINE_GRAPH
, NULL
));
219 calf_line_graph_get_type (void)
221 static GType type
= 0;
223 static const GTypeInfo type_info
= {
224 sizeof(CalfLineGraphClass
),
225 NULL
, /* base_init */
226 NULL
, /* base_finalize */
227 (GClassInitFunc
)calf_line_graph_class_init
,
228 NULL
, /* class_finalize */
229 NULL
, /* class_data */
230 sizeof(CalfLineGraph
),
232 (GInstanceInitFunc
)calf_line_graph_init
235 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
237 for (int i
= 0; ; i
++) {
238 char *name
= g_strdup_printf("CalfLineGraph%u%d", ((unsigned int)(intptr_t)calf_line_graph_class_init
) >> 16, i
);
239 if (g_type_from_name(name
)) {
243 type
= g_type_register_static( GTK_TYPE_WIDGET
,
254 ///////////////////////////////////////// vu meter ///////////////////////////////////////////////
257 calf_vumeter_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
259 g_assert(CALF_IS_VUMETER(widget
));
261 CalfVUMeter
*vu
= CALF_VUMETER(widget
);
262 int ox
= widget
->allocation
.x
+ 1, oy
= widget
->allocation
.y
+ 1;
263 int sx
= widget
->allocation
.width
- 2, sy
= widget
->allocation
.height
- 2;
265 cairo_t
*c
= gdk_cairo_create(GDK_DRAWABLE(widget
->window
));
267 GdkColor sc
= { 0, 0, 0, 0 };
268 gdk_cairo_set_source_color(c
, &sc
);
269 cairo_rectangle(c
, ox
, oy
, sx
, sy
);
271 cairo_set_line_width(c
, 1);
273 CalfVUMeterMode mode
= vu
->mode
;
275 for (int x
= ox
; x
<= ox
+ sx
; x
+= 3)
277 float ts
= (x
- ox
) * 1.0 / sx
;
278 float r
= 0.f
, g
= 0.f
, b
= 0.f
;
284 r
= ts
/ 0.75, g
= 1, b
= 0;
286 r
= 1, g
= 1 - (ts
- 0.75) / 0.25, b
= 0;
287 if (vu
->value
< ts
|| vu
->value
<= 0)
288 r
*= 0.5, g
*= 0.5, b
*= 0.5;
290 case VU_MONOCHROME_REVERSE
:
292 if (!(vu
->value
< ts
) || vu
->value
>= 1.0)
293 r
*= 0.5, g
*= 0.5, b
*= 0.5;
297 if (vu
->value
< ts
|| vu
->value
<= 0)
298 r
*= 0.5, g
*= 0.5, b
*= 0.5;
301 GdkColor sc2
= { 0, (guint16
)(65535 * r
), (guint16
)(65535 * g
), (guint16
)(65535 * b
) };
302 gdk_cairo_set_source_color(c
, &sc2
);
303 cairo_move_to(c
, x
, oy
);
304 cairo_line_to(c
, x
, oy
+ sy
+ 1);
305 cairo_move_to(c
, x
, oy
+ sy
);
306 cairo_line_to(c
, x
, oy
);
312 gtk_paint_shadow(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_IN
, NULL
, widget
, NULL
, ox
- 1, oy
- 1, sx
+ 2, sy
+ 2);
313 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
319 calf_vumeter_size_request (GtkWidget
*widget
,
320 GtkRequisition
*requisition
)
322 g_assert(CALF_IS_VUMETER(widget
));
324 requisition
->width
= 50;
325 requisition
->height
= 14;
329 calf_vumeter_size_allocate (GtkWidget
*widget
,
330 GtkAllocation
*allocation
)
332 g_assert(CALF_IS_VUMETER(widget
));
334 widget
->allocation
= *allocation
;
338 calf_vumeter_class_init (CalfVUMeterClass
*klass
)
340 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
341 widget_class
->expose_event
= calf_vumeter_expose
;
342 widget_class
->size_request
= calf_vumeter_size_request
;
343 widget_class
->size_allocate
= calf_vumeter_size_allocate
;
347 calf_vumeter_init (CalfVUMeter
*self
)
349 GtkWidget
*widget
= GTK_WIDGET(self
);
350 GTK_WIDGET_SET_FLAGS (widget
, GTK_NO_WINDOW
);
351 widget
->requisition
.width
= 40;
352 widget
->requisition
.height
= 40;
359 return GTK_WIDGET( g_object_new (CALF_TYPE_VUMETER
, NULL
));
363 calf_vumeter_get_type (void)
365 static GType type
= 0;
367 static const GTypeInfo type_info
= {
368 sizeof(CalfVUMeterClass
),
369 NULL
, /* base_init */
370 NULL
, /* base_finalize */
371 (GClassInitFunc
)calf_vumeter_class_init
,
372 NULL
, /* class_finalize */
373 NULL
, /* class_data */
376 (GInstanceInitFunc
)calf_vumeter_init
379 GTypeInfo
*type_info_copy
= new GTypeInfo(type_info
);
381 for (int i
= 0; ; i
++) {
382 char *name
= g_strdup_printf("CalfVUMeter%u%d", ((unsigned int)(intptr_t)calf_vumeter_class_init
) >> 16, i
);
383 if (g_type_from_name(name
)) {
387 type
= g_type_register_static( GTK_TYPE_WIDGET
,
398 extern void calf_vumeter_set_value(CalfVUMeter
*meter
, float value
)
400 if (value
!= meter
->value
)
402 meter
->value
= value
;
403 gtk_widget_queue_draw(GTK_WIDGET(meter
));
407 extern float calf_vumeter_get_value(CalfVUMeter
*meter
)
412 extern void calf_vumeter_set_mode(CalfVUMeter
*meter
, CalfVUMeterMode mode
)
414 if (mode
!= meter
->mode
)
417 gtk_widget_queue_draw(GTK_WIDGET(meter
));
421 extern CalfVUMeterMode
calf_vumeter_get_mode(CalfVUMeter
*meter
)
426 ///////////////////////////////////////// knob ///////////////////////////////////////////////
429 calf_knob_expose (GtkWidget
*widget
, GdkEventExpose
*event
)
431 g_assert(CALF_IS_KNOB(widget
));
433 CalfKnob
*self
= CALF_KNOB(widget
);
434 GdkWindow
*window
= widget
->window
;
435 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
437 // printf("adjustment = %p value = %f\n", adj, adj->value);
438 int ox
= widget
->allocation
.x
, oy
= widget
->allocation
.y
;
440 ox
+= (widget
->allocation
.width
- 40) / 2;
441 oy
+= (widget
->allocation
.height
- 40) / 2;
443 int phase
= (int)((adj
->value
- adj
->lower
) * 64 / (adj
->upper
- adj
->lower
));
444 // skip middle phase except for true middle value
445 if (self
->knob_type
== 1 && phase
== 32) {
446 double pt
= (adj
->value
- adj
->lower
) * 2.0 / (adj
->upper
- adj
->lower
) - 1.0;
452 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
453 if (self
->knob_type
== 3 && !(phase
% 16)) {
456 double nom
= adj
->lower
+ phase
* (adj
->upper
- adj
->lower
) / 64.0;
457 double diff
= (adj
->value
- nom
) / (adj
->upper
- adj
->lower
);
459 phase
= (phase
+ 1) % 64;
461 phase
= (phase
+ 63) % 64;
463 gdk_draw_pixbuf(GDK_DRAWABLE(widget
->window
), widget
->style
->fg_gc
[0], CALF_KNOB_CLASS(GTK_OBJECT_GET_CLASS(widget
))->knob_image
, phase
* 40, self
->knob_type
* 40, ox
, oy
, 40, 40, GDK_RGB_DITHER_NORMAL
, 0, 0);
464 // printf("exposed %p %d+%d\n", widget->window, widget->allocation.x, widget->allocation.y);
465 if (gtk_widget_is_focus(widget
))
467 gtk_paint_focus(widget
->style
, window
, GTK_STATE_NORMAL
, NULL
, widget
, NULL
, ox
, oy
, 40, 40);
474 calf_knob_size_request (GtkWidget
*widget
,
475 GtkRequisition
*requisition
)
477 g_assert(CALF_IS_KNOB(widget
));
479 // width/height is hardwired at 40px now
480 requisition
->width
= 40;
481 requisition
->height
= 40;
485 calf_knob_incr (GtkWidget
*widget
, int dir_down
)
487 g_assert(CALF_IS_KNOB(widget
));
488 CalfKnob
*self
= CALF_KNOB(widget
);
489 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
491 int oldstep
= (int)(0.5f
+ (adj
->value
- adj
->lower
) / adj
->step_increment
);
493 int nsteps
= (int)(0.5f
+ (adj
->upper
- adj
->lower
) / adj
->step_increment
); // less 1 actually
498 if (self
->knob_type
== 3 && step
>= nsteps
)
500 if (self
->knob_type
== 3 && step
< 0)
501 step
= nsteps
- (nsteps
- step
) % nsteps
;
503 // trying to reduce error cumulation here, by counting from lowest or from highest
504 float value
= adj
->lower
+ step
* double(adj
->upper
- adj
->lower
) / nsteps
;
505 gtk_range_set_value(GTK_RANGE(widget
), value
);
506 // printf("step %d:%d nsteps %d value %f:%f\n", oldstep, step, nsteps, oldvalue, value);
510 calf_knob_key_press (GtkWidget
*widget
, GdkEventKey
*event
)
512 g_assert(CALF_IS_KNOB(widget
));
513 CalfKnob
*self
= CALF_KNOB(widget
);
514 GtkAdjustment
*adj
= gtk_range_get_adjustment(GTK_RANGE(widget
));
516 switch(event
->keyval
)
519 gtk_range_set_value(GTK_RANGE(widget
), adj
->lower
);
523 gtk_range_set_value(GTK_RANGE(widget
), adj
->upper
);
527 calf_knob_incr(widget
, 0);
531 calf_knob_incr(widget
, 1);
536 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
537 self
->start_y
= self
->last_y
;
545 calf_knob_key_release (GtkWidget
*widget
, GdkEventKey
*event
)
547 g_assert(CALF_IS_KNOB(widget
));
548 CalfKnob
*self
= CALF_KNOB(widget
);
550 if(event
->keyval
== GDK_Shift_L
|| event
->keyval
== GDK_Shift_R
)
552 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
553 self
->start_y
= self
->last_y
;
561 calf_knob_button_press (GtkWidget
*widget
, GdkEventButton
*event
)
563 g_assert(CALF_IS_KNOB(widget
));
564 CalfKnob
*self
= CALF_KNOB(widget
);
566 // CalfKnob *lg = CALF_KNOB(widget);
567 gtk_widget_grab_focus(widget
);
568 gtk_grab_add(widget
);
569 self
->start_x
= event
->x
;
570 self
->start_y
= event
->y
;
571 self
->start_value
= gtk_range_get_value(GTK_RANGE(widget
));
577 calf_knob_button_release (GtkWidget
*widget
, GdkEventButton
*event
)
579 g_assert(CALF_IS_KNOB(widget
));
581 if (GTK_WIDGET_HAS_GRAB(widget
))
582 gtk_grab_remove(widget
);
586 static inline float endless(float value
)
589 return fmod(value
, 1.f
);
591 return fmod(1.f
- fmod(1.f
- value
, 1.f
), 1.f
);
594 static inline float deadzone(float value
, float incr
, float scale
)
596 float dzw
= 10 / scale
;
604 if (value
>= (0.5 - dzw
) && value
<= (0.5 + dzw
))
612 calf_knob_pointer_motion (GtkWidget
*widget
, GdkEventMotion
*event
)
614 g_assert(CALF_IS_KNOB(widget
));
615 CalfKnob
*self
= CALF_KNOB(widget
);
617 float scale
= (event
->state
& GDK_SHIFT_MASK
) ? 1000 : 100;
618 gboolean moved
= FALSE
;
620 if (GTK_WIDGET_HAS_GRAB(widget
))
622 if (self
->knob_type
== 3)
624 gtk_range_set_value(GTK_RANGE(widget
), endless(self
->start_value
- (event
->y
- self
->start_y
) / scale
));
627 if (self
->knob_type
== 1)
629 gtk_range_set_value(GTK_RANGE(widget
), deadzone(self
->start_value
, -(event
->y
- self
->start_y
) / scale
, scale
));
633 gtk_range_set_value(GTK_RANGE(widget
), self
->start_value
- (event
->y
- self
->start_y
) / scale
);
637 self
->last_y
= event
->y
;
642 calf_knob_scroll (GtkWidget
*widget
, GdkEventScroll
*event
)
644 calf_knob_incr(widget
, event
->direction
);
649 calf_knob_class_init (CalfKnobClass
*klass
)
651 // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
652 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS(klass
);
653 widget_class
->expose_event
= calf_knob_expose
;
654 widget_class
->size_request
= calf_knob_size_request
;
655 widget_class
->button_press_event
= calf_knob_button_press
;
656 widget_class
->button_release_event
= calf_knob_button_release
;
657 widget_class
->motion_notify_event
= calf_knob_pointer_motion
;
658 widget_class
->key_press_event
= calf_knob_key_press
;
659 widget_class
->key_release_event
= calf_knob_key_release
;
660 widget_class
->scroll_event
= calf_knob_scroll
;
661 GError
*error
= NULL
;
662 klass
->knob_image
= gdk_pixbuf_new_from_file(PKGLIBDIR
"/knob.png", &error
);
663 g_assert(klass
->knob_image
!= NULL
);
667 calf_knob_init (CalfKnob
*self
)
669 GtkWidget
*widget
= GTK_WIDGET(self
);
670 GTK_WIDGET_SET_FLAGS (GTK_WIDGET(self
), GTK_CAN_FOCUS
);
671 widget
->requisition
.width
= 40;
672 widget
->requisition
.height
= 40;
678 GtkAdjustment
*adj
= (GtkAdjustment
*)gtk_adjustment_new(0, 0, 1, 0.01, 0.5, 0);
679 return calf_knob_new_with_adjustment(adj
);
682 static gboolean
calf_knob_value_changed(gpointer obj
)
684 GtkWidget
*widget
= (GtkWidget
*)obj
;
685 gtk_widget_queue_draw(widget
);
689 GtkWidget
*calf_knob_new_with_adjustment(GtkAdjustment
*_adjustment
)
691 GtkWidget
*widget
= GTK_WIDGET( g_object_new (CALF_TYPE_KNOB
, NULL
));
693 gtk_range_set_adjustment(GTK_RANGE(widget
), _adjustment
);
694 gtk_signal_connect(GTK_OBJECT(widget
), "value-changed", G_CALLBACK(calf_knob_value_changed
), widget
);
700 calf_knob_get_type (void)
702 static GType type
= 0;
705 static const GTypeInfo type_info
= {
706 sizeof(CalfKnobClass
),
707 NULL
, /* base_init */
708 NULL
, /* base_finalize */
709 (GClassInitFunc
)calf_knob_class_init
,
710 NULL
, /* class_finalize */
711 NULL
, /* class_data */
714 (GInstanceInitFunc
)calf_knob_init
717 for (int i
= 0; ; i
++) {
718 char *name
= g_strdup_printf("CalfKnob%u%d",
719 ((unsigned int)(intptr_t)calf_knob_class_init
) >> 16, i
);
720 if (g_type_from_name(name
)) {
724 type
= g_type_register_static(GTK_TYPE_RANGE
,