2 * Copyright (c) 2007, 2008 Edward Tomasz Napiera�ła <trasz@FreeBSD.org>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * This is piano_keyboard, piano keyboard-like GTK+ widget. It contains
29 * no MIDI-specific code.
31 * For questions and comments, contact Edward Tomasz Napierala <trasz@FreeBSD.org>.
38 #include <cairo/cairo.h>
40 #include <gdk/gdkkeysyms.h>
42 #include "gtk_pianokeyboard.h"
44 #define PIANO_KEYBOARD_DEFAULT_WIDTH 730
45 #define PIANO_KEYBOARD_DEFAULT_HEIGHT 70
54 static guint piano_keyboard_signals
[LAST_SIGNAL
] = { 0 };
57 draw_keyboard_cue(PianoKeyboard
*pk
, cairo_t
* cr
)
59 int w
= pk
->notes
[0].w
;
60 int h
= pk
->notes
[0].h
;
62 int first_note_in_lower_row
= (pk
->octave
+ 5) * 12;
63 int last_note_in_lower_row
= (pk
->octave
+ 6) * 12 - 1;
64 int first_note_in_higher_row
= (pk
->octave
+ 6) * 12;
65 int last_note_in_higher_row
= (pk
->octave
+ 7) * 12 + 4;
67 cairo_set_source_rgb (cr
, 1.0f
, 0.0f
, 0.0f
);
68 cairo_move_to (cr
, pk
->notes
[first_note_in_lower_row
].x
+ 3, h
- 6);
69 cairo_line_to (cr
, pk
->notes
[last_note_in_lower_row
].x
+ w
- 3, h
- 6);
72 cairo_set_source_rgb (cr
, 0.0f
, 0.0f
, 1.0f
);
73 cairo_move_to (cr
, pk
->notes
[first_note_in_higher_row
].x
+ 3, h
- 9);
74 cairo_line_to (cr
, pk
->notes
[last_note_in_higher_row
].x
+ w
- 3, h
- 9);
79 queue_note_draw (PianoKeyboard
* pk
, int note
)
81 GdkWindow
* w
= GTK_WIDGET(pk
)->window
;
86 r
.x
= pk
->notes
[note
].x
;
88 r
.width
= pk
->notes
[note
].w
;
89 r
.height
= pk
->notes
[note
].h
;
91 gdk_window_invalidate_rect (w
, &r
, TRUE
);
96 draw_note(PianoKeyboard
*pk
, cairo_t
* cr
, int note
)
98 int is_white
= pk
->notes
[note
].white
;
100 int x
= pk
->notes
[note
].x
;
101 int w
= pk
->notes
[note
].w
;
102 int h
= pk
->notes
[note
].h
;
104 if (pk
->notes
[note
].pressed
|| pk
->notes
[note
].sustained
) {
106 cairo_set_source_rgb (cr
, 0.60f
, 0.60f
, 0.60f
);
108 cairo_set_source_rgb (cr
, 0.50f
, 0.50f
, 0.50f
);
112 cairo_set_source_rgb (cr
, 1.0f
, 1.0f
, 1.0f
);
114 cairo_set_source_rgb (cr
, 0.0f
, 0.0f
, 0.0f
);
118 cairo_set_line_width (cr
, 1.0);
120 cairo_rectangle (cr
, x
, 0, w
, h
);
123 cairo_set_source_rgb(cr
, 0.0f
, 0.0f
, 0.0f
); /* black outline */
124 cairo_rectangle (cr
, x
, 0, w
, h
);
127 if (pk
->enable_keyboard_cue
) {
128 draw_keyboard_cue (pk
, cr
);
131 /* We need to redraw black keys that partially obscure the white one. */
132 if (note
< NNOTES
- 2 && !pk
->notes
[note
+ 1].white
) {
133 draw_note(pk
, cr
, note
+ 1);
136 if (note
> 0 && !pk
->notes
[note
- 1].white
) {
137 draw_note(pk
, cr
, note
- 1);
142 press_key(PianoKeyboard
*pk
, int key
)
145 assert(key
< NNOTES
);
147 pk
->maybe_stop_sustained_notes
= 0;
149 /* This is for keyboard autorepeat protection. */
150 if (pk
->notes
[key
].pressed
)
153 if (pk
->sustain_new_notes
)
154 pk
->notes
[key
].sustained
= 1;
156 pk
->notes
[key
].sustained
= 0;
158 pk
->notes
[key
].pressed
= 1;
160 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-on", key
);
161 queue_note_draw(pk
, key
);
167 release_key(PianoKeyboard
*pk
, int key
)
170 assert(key
< NNOTES
);
172 pk
->maybe_stop_sustained_notes
= 0;
174 if (!pk
->notes
[key
].pressed
)
177 if (pk
->sustain_new_notes
)
178 pk
->notes
[key
].sustained
= 1;
180 pk
->notes
[key
].pressed
= 0;
182 if (pk
->notes
[key
].sustained
)
185 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", key
);
186 queue_note_draw(pk
, key
);
192 rest (PianoKeyboard
* pk
)
194 g_signal_emit_by_name(GTK_WIDGET(pk
), "rest");
198 stop_unsustained_notes(PianoKeyboard
*pk
)
202 for (i
= 0; i
< NNOTES
; i
++) {
203 if (pk
->notes
[i
].pressed
&& !pk
->notes
[i
].sustained
) {
204 pk
->notes
[i
].pressed
= 0;
205 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", i
);
206 queue_note_draw(pk
, i
);
212 stop_sustained_notes(PianoKeyboard
*pk
)
216 for (i
= 0; i
< NNOTES
; i
++) {
217 if (pk
->notes
[i
].sustained
) {
218 pk
->notes
[i
].pressed
= 0;
219 pk
->notes
[i
].sustained
= 0;
220 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", i
);
221 queue_note_draw(pk
, i
);
227 key_binding(PianoKeyboard
*pk
, const char *key
)
229 gpointer notused
, note
;
232 assert(pk
->key_bindings
!= NULL
);
234 found
= g_hash_table_lookup_extended(pk
->key_bindings
, key
, ¬used
, ¬e
);
239 return (intptr_t)note
;
243 bind_key(PianoKeyboard
*pk
, const char *key
, int note
)
245 assert(pk
->key_bindings
!= NULL
);
247 g_hash_table_insert(pk
->key_bindings
, (gpointer
)key
, (gpointer
)((intptr_t)note
));
251 clear_notes(PianoKeyboard
*pk
)
253 assert(pk
->key_bindings
!= NULL
);
255 g_hash_table_remove_all(pk
->key_bindings
);
259 bind_keys_qwerty(PianoKeyboard
*pk
)
263 bind_key(pk
, "space", 128);
265 /* Lower keyboard row - "zxcvbnm". */
266 bind_key(pk
, "z", 12); /* C0 */
267 bind_key(pk
, "s", 13);
268 bind_key(pk
, "x", 14);
269 bind_key(pk
, "d", 15);
270 bind_key(pk
, "c", 16);
271 bind_key(pk
, "v", 17);
272 bind_key(pk
, "g", 18);
273 bind_key(pk
, "b", 19);
274 bind_key(pk
, "h", 20);
275 bind_key(pk
, "n", 21);
276 bind_key(pk
, "j", 22);
277 bind_key(pk
, "m", 23);
279 /* Upper keyboard row, first octave - "qwertyu". */
280 bind_key(pk
, "q", 24);
281 bind_key(pk
, "2", 25);
282 bind_key(pk
, "w", 26);
283 bind_key(pk
, "3", 27);
284 bind_key(pk
, "e", 28);
285 bind_key(pk
, "r", 29);
286 bind_key(pk
, "5", 30);
287 bind_key(pk
, "t", 31);
288 bind_key(pk
, "6", 32);
289 bind_key(pk
, "y", 33);
290 bind_key(pk
, "7", 34);
291 bind_key(pk
, "u", 35);
293 /* Upper keyboard row, the rest - "iop". */
294 bind_key(pk
, "i", 36);
295 bind_key(pk
, "9", 37);
296 bind_key(pk
, "o", 38);
297 bind_key(pk
, "0", 39);
298 bind_key(pk
, "p", 40);
302 bind_keys_qwertz(PianoKeyboard
*pk
)
304 bind_keys_qwerty(pk
);
306 /* The only difference between QWERTY and QWERTZ is that the "y" and "z" are swapped together. */
307 bind_key(pk
, "y", 12);
308 bind_key(pk
, "z", 33);
312 bind_keys_azerty(PianoKeyboard
*pk
)
316 bind_key(pk
, "space", 128);
318 /* Lower keyboard row - "wxcvbn,". */
319 bind_key(pk
, "w", 12); /* C0 */
320 bind_key(pk
, "s", 13);
321 bind_key(pk
, "x", 14);
322 bind_key(pk
, "d", 15);
323 bind_key(pk
, "c", 16);
324 bind_key(pk
, "v", 17);
325 bind_key(pk
, "g", 18);
326 bind_key(pk
, "b", 19);
327 bind_key(pk
, "h", 20);
328 bind_key(pk
, "n", 21);
329 bind_key(pk
, "j", 22);
330 bind_key(pk
, "comma", 23);
332 /* Upper keyboard row, first octave - "azertyu". */
333 bind_key(pk
, "a", 24);
334 bind_key(pk
, "eacute", 25);
335 bind_key(pk
, "z", 26);
336 bind_key(pk
, "quotedbl", 27);
337 bind_key(pk
, "e", 28);
338 bind_key(pk
, "r", 29);
339 bind_key(pk
, "parenleft", 30);
340 bind_key(pk
, "t", 31);
341 bind_key(pk
, "minus", 32);
342 bind_key(pk
, "y", 33);
343 bind_key(pk
, "egrave", 34);
344 bind_key(pk
, "u", 35);
346 /* Upper keyboard row, the rest - "iop". */
347 bind_key(pk
, "i", 36);
348 bind_key(pk
, "ccedilla", 37);
349 bind_key(pk
, "o", 38);
350 bind_key(pk
, "agrave", 39);
351 bind_key(pk
, "p", 40);
355 keyboard_event_handler(GtkWidget
*mk
, GdkEventKey
*event
, gpointer notused
)
361 PianoKeyboard
*pk
= PIANO_KEYBOARD(mk
);
363 /* We're not using event->keyval, because we need keyval with level set to 0.
364 E.g. if user holds Shift and presses '7', we want to get a '7', not '&'. */
365 kk
.keycode
= event
->hardware_keycode
;
369 keyval
= gdk_keymap_lookup_key(NULL
, &kk
);
371 key
= gdk_keyval_name(gdk_keyval_to_lower(keyval
));
374 g_message("gtk_keyval_name() returned NULL; please report this.");
378 note
= key_binding(pk
, key
);
381 /* Key was not bound. Maybe it's one of the keys handled in jack-keyboard.c. */
386 if (event
->type
== GDK_KEY_RELEASE
) {
393 note
+= pk
->octave
* 12;
396 assert(note
< NNOTES
);
398 if (event
->type
== GDK_KEY_PRESS
) {
401 } else if (event
->type
== GDK_KEY_RELEASE
) {
402 release_key(pk
, note
);
409 get_note_for_xy(PianoKeyboard
*pk
, int x
, int y
)
411 int height
= GTK_WIDGET(pk
)->allocation
.height
;
414 if (y
<= height
/ 2) {
415 for (note
= 0; note
< NNOTES
- 1; note
++) {
416 if (pk
->notes
[note
].white
)
419 if (x
>= pk
->notes
[note
].x
&& x
<= pk
->notes
[note
].x
+ pk
->notes
[note
].w
)
424 for (note
= 0; note
< NNOTES
- 1; note
++) {
425 if (!pk
->notes
[note
].white
)
428 if (x
>= pk
->notes
[note
].x
&& x
<= pk
->notes
[note
].x
+ pk
->notes
[note
].w
)
436 mouse_button_event_handler(PianoKeyboard
*pk
, GdkEventButton
*event
, gpointer notused
)
441 int note
= get_note_for_xy(pk
, x
, y
);
443 if (event
->button
!= 1)
446 if (event
->type
== GDK_BUTTON_PRESS
) {
447 /* This is possible when you make the window a little wider and then click
453 if (pk
->note_being_pressed_using_mouse
>= 0)
454 release_key(pk
, pk
->note_being_pressed_using_mouse
);
457 pk
->note_being_pressed_using_mouse
= note
;
459 } else if (event
->type
== GDK_BUTTON_RELEASE
) {
461 release_key(pk
, note
);
464 if (pk
->note_being_pressed_using_mouse
>= 0)
465 release_key(pk
, pk
->note_being_pressed_using_mouse
);
468 pk
->note_being_pressed_using_mouse
= -1;
476 mouse_motion_event_handler(PianoKeyboard
*pk
, GdkEventMotion
*event
, gpointer notused
)
480 if ((event
->state
& GDK_BUTTON1_MASK
) == 0)
483 note
= get_note_for_xy(pk
, event
->x
, event
->y
);
485 if (note
!= pk
->note_being_pressed_using_mouse
&& note
>= 0) {
487 if (pk
->note_being_pressed_using_mouse
>= 0)
488 release_key(pk
, pk
->note_being_pressed_using_mouse
);
490 pk
->note_being_pressed_using_mouse
= note
;
497 piano_keyboard_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
500 PianoKeyboard
*pk
= PIANO_KEYBOARD(widget
);
501 cairo_t
* cr
= gdk_cairo_create (GDK_DRAWABLE (GTK_WIDGET(pk
)->window
));
503 gdk_cairo_region (cr
, event
->region
);
506 for (i
= 0; i
< NNOTES
; i
++) {
509 r
.x
= pk
->notes
[i
].x
;
511 r
.width
= pk
->notes
[i
].w
;
512 r
.height
= pk
->notes
[i
].h
;
514 switch (gdk_region_rect_in (event
->region
, &r
)) {
515 case GDK_OVERLAP_RECTANGLE_PART
:
516 case GDK_OVERLAP_RECTANGLE_IN
:
517 draw_note (pk
, cr
, i
);
530 piano_keyboard_size_request(GtkWidget
* widget
, GtkRequisition
*requisition
)
532 requisition
->width
= PIANO_KEYBOARD_DEFAULT_WIDTH
;
533 requisition
->height
= PIANO_KEYBOARD_DEFAULT_HEIGHT
;
537 recompute_dimensions(PianoKeyboard
*pk
)
539 int number_of_white_keys
= (NNOTES
- 1) * (7.0 / 12.0);
549 int width
= GTK_WIDGET(pk
)->allocation
.width
;
550 int height
= GTK_WIDGET(pk
)->allocation
.height
;
552 key_width
= width
/ number_of_white_keys
;
553 black_key_width
= key_width
* 0.8;
554 useful_width
= number_of_white_keys
* key_width
;
555 pk
->widget_margin
= (width
- useful_width
) / 2;
557 for (note
= 0, white_key
= 0; note
< NNOTES
- 2; note
++) {
558 note_in_octave
= note
% 12;
560 if (note_in_octave
== 1 || note_in_octave
== 3 || note_in_octave
== 6 ||
561 note_in_octave
== 8 || note_in_octave
== 10) {
563 /* This note is black key. */
564 pk
->notes
[note
].x
= pk
->widget_margin
+ white_key
* key_width
- black_key_width
/ 2;
565 pk
->notes
[note
].w
= black_key_width
;
566 pk
->notes
[note
].h
= height
/ 2;
567 pk
->notes
[note
].white
= 0;
572 /* This note is white key. */
573 pk
->notes
[note
].x
= pk
->widget_margin
+ white_key
* key_width
;
574 pk
->notes
[note
].w
= key_width
;
575 pk
->notes
[note
].h
= height
;
576 pk
->notes
[note
].white
= 1;
583 piano_keyboard_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
585 /* XXX: Are these two needed? */
586 g_return_if_fail(widget
!= NULL
);
587 g_return_if_fail(allocation
!= NULL
);
589 widget
->allocation
= *allocation
;
591 recompute_dimensions(PIANO_KEYBOARD(widget
));
593 if (GTK_WIDGET_REALIZED(widget
)) {
594 gdk_window_move_resize (widget
->window
, allocation
->x
, allocation
->y
, allocation
->width
, allocation
->height
);
599 piano_keyboard_class_init(PianoKeyboardClass
*klass
)
601 GtkWidgetClass
*widget_klass
;
603 /* Set up signals. */
604 piano_keyboard_signals
[NOTE_ON_SIGNAL
] = g_signal_new ("note-on",
605 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
606 0, NULL
, NULL
, g_cclosure_marshal_VOID__INT
, G_TYPE_NONE
, 1, G_TYPE_INT
);
608 piano_keyboard_signals
[NOTE_OFF_SIGNAL
] = g_signal_new ("note-off",
609 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
610 0, NULL
, NULL
, g_cclosure_marshal_VOID__INT
, G_TYPE_NONE
, 1, G_TYPE_INT
);
612 piano_keyboard_signals
[REST_SIGNAL
] = g_signal_new ("rest",
613 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
614 0, NULL
, NULL
, g_cclosure_marshal_VOID__VOID
, G_TYPE_NONE
, 0);
616 widget_klass
= (GtkWidgetClass
*) klass
;
618 widget_klass
->expose_event
= piano_keyboard_expose
;
619 widget_klass
->size_request
= piano_keyboard_size_request
;
620 widget_klass
->size_allocate
= piano_keyboard_size_allocate
;
624 piano_keyboard_init(GtkWidget
*mk
)
626 gtk_widget_add_events(mk
, GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
);
628 g_signal_connect(G_OBJECT(mk
), "button-press-event", G_CALLBACK(mouse_button_event_handler
), NULL
);
629 g_signal_connect(G_OBJECT(mk
), "button-release-event", G_CALLBACK(mouse_button_event_handler
), NULL
);
630 g_signal_connect(G_OBJECT(mk
), "motion-notify-event", G_CALLBACK(mouse_motion_event_handler
), NULL
);
631 g_signal_connect(G_OBJECT(mk
), "key-press-event", G_CALLBACK(keyboard_event_handler
), NULL
);
632 g_signal_connect(G_OBJECT(mk
), "key-release-event", G_CALLBACK(keyboard_event_handler
), NULL
);
636 piano_keyboard_get_type(void)
638 static GType mk_type
= 0;
641 static const GTypeInfo mk_info
= {
642 sizeof(PianoKeyboardClass
),
643 NULL
, /* base_init */
644 NULL
, /* base_finalize */
645 (GClassInitFunc
) piano_keyboard_class_init
,
646 NULL
, /* class_finalize */
647 NULL
, /* class_data */
648 sizeof (PianoKeyboard
),
650 (GInstanceInitFunc
) piano_keyboard_init
,
654 mk_type
= g_type_register_static(GTK_TYPE_DRAWING_AREA
, "PianoKeyboard", &mk_info
, 0);
661 piano_keyboard_new(void)
663 GtkWidget
*widget
= gtk_type_new(piano_keyboard_get_type());
665 PianoKeyboard
*pk
= PIANO_KEYBOARD(widget
);
667 pk
->maybe_stop_sustained_notes
= 0;
668 pk
->sustain_new_notes
= 0;
669 pk
->enable_keyboard_cue
= 0;
671 pk
->note_being_pressed_using_mouse
= -1;
672 memset((void *)pk
->notes
, 0, sizeof(struct Note
) * NNOTES
);
673 pk
->key_bindings
= g_hash_table_new(g_str_hash
, g_str_equal
);
674 bind_keys_qwerty(pk
);
680 piano_keyboard_set_keyboard_cue(PianoKeyboard
*pk
, int enabled
)
682 pk
->enable_keyboard_cue
= enabled
;
686 piano_keyboard_sustain_press(PianoKeyboard
*pk
)
688 if (!pk
->sustain_new_notes
) {
689 pk
->sustain_new_notes
= 1;
690 pk
->maybe_stop_sustained_notes
= 1;
695 piano_keyboard_sustain_release(PianoKeyboard
*pk
)
697 if (pk
->maybe_stop_sustained_notes
)
698 stop_sustained_notes(pk
);
700 pk
->sustain_new_notes
= 0;
704 piano_keyboard_set_note_on(PianoKeyboard
*pk
, int note
)
706 if (pk
->notes
[note
].pressed
== 0) {
707 pk
->notes
[note
].pressed
= 1;
708 queue_note_draw (pk
, note
);
713 piano_keyboard_set_note_off(PianoKeyboard
*pk
, int note
)
715 if (pk
->notes
[note
].pressed
|| pk
->notes
[note
].sustained
) {
716 pk
->notes
[note
].pressed
= 0;
717 pk
->notes
[note
].sustained
= 0;
718 queue_note_draw (pk
, note
);
723 piano_keyboard_set_octave(PianoKeyboard
*pk
, int octave
)
725 stop_unsustained_notes(pk
);
727 gtk_widget_queue_draw(GTK_WIDGET(pk
));
731 piano_keyboard_set_keyboard_layout(PianoKeyboard
*pk
, const char *layout
)
735 if (!strcasecmp(layout
, "QWERTY")) {
736 bind_keys_qwerty(pk
);
738 } else if (!strcasecmp(layout
, "QWERTZ")) {
739 bind_keys_qwertz(pk
);
741 } else if (!strcasecmp(layout
, "AZERTY")) {
742 bind_keys_azerty(pk
);
745 /* Unknown layout name. */