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>.
37 #include <gdk/gdkkeysyms.h>
39 #include "pianokeyboard.h"
41 #define PIANO_KEYBOARD_DEFAULT_WIDTH 730
42 #define PIANO_KEYBOARD_DEFAULT_HEIGHT 70
50 static guint piano_keyboard_signals
[LAST_SIGNAL
] = { 0 };
53 draw_keyboard_cue(PianoKeyboard
*pk
)
55 int w
, h
, first_note_in_lower_row
, last_note_in_lower_row
,
56 first_note_in_higher_row
, last_note_in_higher_row
;
63 gc
= GTK_WIDGET(pk
)->style
->fg_gc
[0];
65 first_note_in_lower_row
= (pk
->octave
+ 5) * 12;
66 last_note_in_lower_row
= (pk
->octave
+ 6) * 12 - 1;
67 first_note_in_higher_row
= (pk
->octave
+ 6) * 12;
68 last_note_in_higher_row
= (pk
->octave
+ 7) * 12 + 4;
70 gdk_draw_line(GTK_WIDGET(pk
)->window
, gc
, pk
->notes
[first_note_in_lower_row
].x
+ 3,
71 h
- 6, pk
->notes
[last_note_in_lower_row
].x
+ w
- 3, h
- 6);
73 gdk_draw_line(GTK_WIDGET(pk
)->window
, gc
, pk
->notes
[first_note_in_higher_row
].x
+ 3,
74 h
- 9, pk
->notes
[last_note_in_higher_row
].x
+ w
- 3, h
- 9);
78 draw_note(PianoKeyboard
*pk
, int note
)
80 int is_white
, x
, w
, h
;
82 GdkColor black
= {0, 0, 0, 0};
83 GdkColor white
= {0, 65535, 65535, 65535};
85 GdkGC
*gc
= GTK_WIDGET(pk
)->style
->fg_gc
[0];
88 is_white
= pk
->notes
[note
].white
;
90 x
= pk
->notes
[note
].x
;
91 w
= pk
->notes
[note
].w
;
92 h
= pk
->notes
[note
].h
;
94 if (pk
->notes
[note
].pressed
|| pk
->notes
[note
].sustained
)
98 gdk_gc_set_rgb_fg_color(gc
, &white
);
100 gdk_gc_set_rgb_fg_color(gc
, &black
);
102 gdk_draw_rectangle(GTK_WIDGET(pk
)->window
, gc
, TRUE
, x
, 0, w
, h
);
103 gdk_gc_set_rgb_fg_color(gc
, &black
);
104 gdk_draw_rectangle(GTK_WIDGET(pk
)->window
, gc
, FALSE
, x
, 0, w
, h
);
106 if (pk
->enable_keyboard_cue
)
107 draw_keyboard_cue(pk
);
109 /* We need to redraw black keys that partially obscure the white one. */
110 if (note
< NNOTES
- 2 && !pk
->notes
[note
+ 1].white
)
111 draw_note(pk
, note
+ 1);
113 if (note
> 0 && !pk
->notes
[note
- 1].white
)
114 draw_note(pk
, note
- 1);
117 * XXX: This doesn't really belong here. Originally I wanted to pack PianoKeyboard into GtkFrame
118 * packed into GtkAlignment. I failed to make it behave the way I want. GtkFrame would need
119 * to adapt to the "proper" size of PianoKeyboard, i.e. to the useful_width, not allocated width;
122 widget
= GTK_WIDGET(pk
);
123 gtk_paint_shadow(widget
->style
, widget
->window
, GTK_STATE_NORMAL
, GTK_SHADOW_IN
, NULL
,
124 widget
, NULL
, pk
->widget_margin
, 0, widget
->allocation
.width
- pk
->widget_margin
* 2 + 1,
125 widget
->allocation
.height
);
129 press_key(PianoKeyboard
*pk
, int key
)
132 assert(key
< NNOTES
);
134 pk
->maybe_stop_sustained_notes
= 0;
136 /* This is for keyboard autorepeat protection. */
137 if (pk
->notes
[key
].pressed
)
140 if (pk
->sustain_new_notes
)
141 pk
->notes
[key
].sustained
= 1;
143 pk
->notes
[key
].sustained
= 0;
145 pk
->notes
[key
].pressed
= 1;
147 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-on", key
);
154 release_key(PianoKeyboard
*pk
, int key
)
157 assert(key
< NNOTES
);
159 pk
->maybe_stop_sustained_notes
= 0;
161 if (!pk
->notes
[key
].pressed
)
164 if (pk
->sustain_new_notes
)
165 pk
->notes
[key
].sustained
= 1;
167 pk
->notes
[key
].pressed
= 0;
169 if (pk
->notes
[key
].sustained
)
172 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", key
);
179 stop_unsustained_notes(PianoKeyboard
*pk
)
183 for (i
= 0; i
< NNOTES
; i
++) {
184 if (pk
->notes
[i
].pressed
&& !pk
->notes
[i
].sustained
) {
185 pk
->notes
[i
].pressed
= 0;
186 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", i
);
193 stop_sustained_notes(PianoKeyboard
*pk
)
197 for (i
= 0; i
< NNOTES
; i
++) {
198 if (pk
->notes
[i
].sustained
) {
199 pk
->notes
[i
].pressed
= 0;
200 pk
->notes
[i
].sustained
= 0;
201 g_signal_emit_by_name(GTK_WIDGET(pk
), "note-off", i
);
208 key_binding(PianoKeyboard
*pk
, const char *key
)
210 gpointer notused
, note
;
213 assert(pk
->key_bindings
!= NULL
);
215 found
= g_hash_table_lookup_extended(pk
->key_bindings
, key
, ¬used
, ¬e
);
224 bind_key(PianoKeyboard
*pk
, const char *key
, int note
)
226 assert(pk
->key_bindings
!= NULL
);
228 g_hash_table_insert(pk
->key_bindings
, (gpointer
)key
, (gpointer
)note
);
232 clear_notes(PianoKeyboard
*pk
)
234 assert(pk
->key_bindings
!= NULL
);
236 g_hash_table_remove_all(pk
->key_bindings
);
240 bind_keys_qwerty(PianoKeyboard
*pk
)
244 /* Lower keyboard row - "zxcvbnm". */
245 bind_key(pk
, "z", 12); /* C0 */
246 bind_key(pk
, "s", 13);
247 bind_key(pk
, "x", 14);
248 bind_key(pk
, "d", 15);
249 bind_key(pk
, "c", 16);
250 bind_key(pk
, "v", 17);
251 bind_key(pk
, "g", 18);
252 bind_key(pk
, "b", 19);
253 bind_key(pk
, "h", 20);
254 bind_key(pk
, "n", 21);
255 bind_key(pk
, "j", 22);
256 bind_key(pk
, "m", 23);
258 /* Upper keyboard row, first octave - "qwertyu". */
259 bind_key(pk
, "q", 24);
260 bind_key(pk
, "2", 25);
261 bind_key(pk
, "w", 26);
262 bind_key(pk
, "3", 27);
263 bind_key(pk
, "e", 28);
264 bind_key(pk
, "r", 29);
265 bind_key(pk
, "5", 30);
266 bind_key(pk
, "t", 31);
267 bind_key(pk
, "6", 32);
268 bind_key(pk
, "y", 33);
269 bind_key(pk
, "7", 34);
270 bind_key(pk
, "u", 35);
272 /* Upper keyboard row, the rest - "iop". */
273 bind_key(pk
, "i", 36);
274 bind_key(pk
, "9", 37);
275 bind_key(pk
, "o", 38);
276 bind_key(pk
, "0", 39);
277 bind_key(pk
, "p", 40);
281 bind_keys_qwertz(PianoKeyboard
*pk
)
283 bind_keys_qwerty(pk
);
285 /* The only difference between QWERTY and QWERTZ is that the "y" and "z" are swapped together. */
286 bind_key(pk
, "y", 12);
287 bind_key(pk
, "z", 33);
291 bind_keys_azerty(PianoKeyboard
*pk
)
295 /* Lower keyboard row - "wxcvbn,". */
296 bind_key(pk
, "w", 12); /* C0 */
297 bind_key(pk
, "s", 13);
298 bind_key(pk
, "x", 14);
299 bind_key(pk
, "d", 15);
300 bind_key(pk
, "c", 16);
301 bind_key(pk
, "v", 17);
302 bind_key(pk
, "g", 18);
303 bind_key(pk
, "b", 19);
304 bind_key(pk
, "h", 20);
305 bind_key(pk
, "n", 21);
306 bind_key(pk
, "j", 22);
307 bind_key(pk
, "comma", 23);
309 /* Upper keyboard row, first octave - "azertyu". */
310 bind_key(pk
, "a", 24);
311 bind_key(pk
, "eacute", 25);
312 bind_key(pk
, "z", 26);
313 bind_key(pk
, "quotedbl", 27);
314 bind_key(pk
, "e", 28);
315 bind_key(pk
, "r", 29);
316 bind_key(pk
, "parenleft", 30);
317 bind_key(pk
, "t", 31);
318 bind_key(pk
, "minus", 32);
319 bind_key(pk
, "y", 33);
320 bind_key(pk
, "egrave", 34);
321 bind_key(pk
, "u", 35);
323 /* Upper keyboard row, the rest - "iop". */
324 bind_key(pk
, "i", 36);
325 bind_key(pk
, "ccedilla", 37);
326 bind_key(pk
, "o", 38);
327 bind_key(pk
, "agrave", 39);
328 bind_key(pk
, "p", 40);
332 keyboard_event_handler(GtkWidget
*mk
, GdkEventKey
*event
, gpointer notused
)
338 PianoKeyboard
*pk
= PIANO_KEYBOARD(mk
);
340 /* We're not using event->keyval, because we need keyval with level set to 0.
341 E.g. if user holds Shift and presses '7', we want to get a '7', not '&'. */
342 kk
.keycode
= event
->hardware_keycode
;
346 keyval
= gdk_keymap_lookup_key(NULL
, &kk
);
348 key
= gdk_keyval_name(gdk_keyval_to_lower(keyval
));
351 g_message("gtk_keyval_name() returned NULL; please report this.");
355 note
= key_binding(pk
, key
);
358 /* Key was not bound. Maybe it's one of the keys handled in jack-keyboard.c. */
362 note
+= pk
->octave
* 12;
365 assert(note
< NNOTES
);
367 if (event
->type
== GDK_KEY_PRESS
) {
370 } else if (event
->type
== GDK_KEY_RELEASE
) {
371 release_key(pk
, note
);
378 get_note_for_xy(PianoKeyboard
*pk
, int x
, int y
)
382 height
= GTK_WIDGET(pk
)->allocation
.height
;
384 if (y
<= height
/ 2) {
385 for (note
= 0; note
< NNOTES
- 1; note
++) {
386 if (pk
->notes
[note
].white
)
389 if (x
>= pk
->notes
[note
].x
&& x
<= pk
->notes
[note
].x
+ pk
->notes
[note
].w
)
394 for (note
= 0; note
< NNOTES
- 1; note
++) {
395 if (!pk
->notes
[note
].white
)
398 if (x
>= pk
->notes
[note
].x
&& x
<= pk
->notes
[note
].x
+ pk
->notes
[note
].w
)
406 mouse_button_event_handler(PianoKeyboard
*pk
, GdkEventButton
*event
, gpointer notused
)
413 note
= get_note_for_xy(pk
, x
, y
);
415 if (event
->button
!= 1)
418 if (event
->type
== GDK_BUTTON_PRESS
) {
419 /* This is possible when you make the window a little wider and then click
425 if (pk
->note_being_pressed_using_mouse
>= 0)
426 release_key(pk
, pk
->note_being_pressed_using_mouse
);
429 pk
->note_being_pressed_using_mouse
= note
;
431 } else if (event
->type
== GDK_BUTTON_RELEASE
) {
433 release_key(pk
, note
);
436 if (pk
->note_being_pressed_using_mouse
>= 0)
437 release_key(pk
, pk
->note_being_pressed_using_mouse
);
440 pk
->note_being_pressed_using_mouse
= -1;
448 mouse_motion_event_handler(PianoKeyboard
*pk
, GdkEventMotion
*event
, gpointer notused
)
452 if ((event
->state
& GDK_BUTTON1_MASK
) == 0)
455 note
= get_note_for_xy(pk
, event
->x
, event
->y
);
457 if (note
!= pk
->note_being_pressed_using_mouse
&& note
>= 0) {
459 if (pk
->note_being_pressed_using_mouse
>= 0)
460 release_key(pk
, pk
->note_being_pressed_using_mouse
);
462 pk
->note_being_pressed_using_mouse
= note
;
469 piano_keyboard_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
472 PianoKeyboard
*pk
= PIANO_KEYBOARD(widget
);
474 for (i
= 0; i
< NNOTES
; i
++)
481 piano_keyboard_size_request(GtkWidget
*widget
, GtkRequisition
*requisition
)
483 requisition
->width
= PIANO_KEYBOARD_DEFAULT_WIDTH
;
484 requisition
->height
= PIANO_KEYBOARD_DEFAULT_HEIGHT
;
488 recompute_dimensions(PianoKeyboard
*pk
)
490 int number_of_white_keys
, key_width
, black_key_width
, useful_width
, note
,
491 white_key
= 0, note_in_octave
, width
, height
;
493 number_of_white_keys
= (NNOTES
- 1) * (7.0 / 12.0);
495 width
= GTK_WIDGET(pk
)->allocation
.width
;
496 height
= GTK_WIDGET(pk
)->allocation
.height
;
498 key_width
= width
/ number_of_white_keys
;
499 black_key_width
= key_width
* 0.8;
500 useful_width
= number_of_white_keys
* key_width
;
501 pk
->widget_margin
= (width
- useful_width
) / 2;
503 for (note
= 0, white_key
= 0; note
< NNOTES
- 2; note
++) {
504 note_in_octave
= note
% 12;
506 if (note_in_octave
== 1 || note_in_octave
== 3 || note_in_octave
== 6 ||
507 note_in_octave
== 8 || note_in_octave
== 10) {
509 /* This note is black key. */
510 pk
->notes
[note
].x
= pk
->widget_margin
+ white_key
* key_width
- black_key_width
/ 2;
511 pk
->notes
[note
].w
= black_key_width
;
512 pk
->notes
[note
].h
= height
/ 2;
513 pk
->notes
[note
].white
= 0;
518 /* This note is white key. */
519 pk
->notes
[note
].x
= pk
->widget_margin
+ white_key
* key_width
;
520 pk
->notes
[note
].w
= key_width
;
521 pk
->notes
[note
].h
= height
;
522 pk
->notes
[note
].white
= 1;
529 piano_keyboard_size_allocate(GtkWidget
*widget
, GtkAllocation
*allocation
)
531 /* XXX: Are these two needed? */
532 g_return_if_fail(widget
!= NULL
);
533 g_return_if_fail(allocation
!= NULL
);
535 widget
->allocation
= *allocation
;
537 recompute_dimensions(PIANO_KEYBOARD(widget
));
539 if (GTK_WIDGET_REALIZED(widget
))
540 gdk_window_move_resize (widget
->window
, allocation
->x
, allocation
->y
, allocation
->width
, allocation
->height
);
544 piano_keyboard_class_init(PianoKeyboardClass
*klass
)
546 GtkWidgetClass
*widget_klass
;
548 /* Set up signals. */
549 piano_keyboard_signals
[NOTE_ON_SIGNAL
] = g_signal_new ("note-on",
550 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
551 0, NULL
, NULL
, g_cclosure_marshal_VOID__INT
, G_TYPE_NONE
, 1, G_TYPE_INT
);
553 piano_keyboard_signals
[NOTE_OFF_SIGNAL
] = g_signal_new ("note-off",
554 G_TYPE_FROM_CLASS (klass
), G_SIGNAL_RUN_FIRST
| G_SIGNAL_ACTION
,
555 0, NULL
, NULL
, g_cclosure_marshal_VOID__INT
, G_TYPE_NONE
, 1, G_TYPE_INT
);
557 widget_klass
= (GtkWidgetClass
*)klass
;
559 widget_klass
->expose_event
= piano_keyboard_expose
;
560 widget_klass
->size_request
= piano_keyboard_size_request
;
561 widget_klass
->size_allocate
= piano_keyboard_size_allocate
;
565 piano_keyboard_init(GtkWidget
*mk
)
567 gtk_widget_add_events(mk
, GDK_BUTTON_PRESS_MASK
| GDK_BUTTON_RELEASE_MASK
| GDK_POINTER_MOTION_MASK
);
569 g_signal_connect(G_OBJECT(mk
), "button-press-event", G_CALLBACK(mouse_button_event_handler
), NULL
);
570 g_signal_connect(G_OBJECT(mk
), "button-release-event", G_CALLBACK(mouse_button_event_handler
), NULL
);
571 g_signal_connect(G_OBJECT(mk
), "motion-notify-event", G_CALLBACK(mouse_motion_event_handler
), NULL
);
572 g_signal_connect(G_OBJECT(mk
), "key-press-event", G_CALLBACK(keyboard_event_handler
), NULL
);
573 g_signal_connect(G_OBJECT(mk
), "key-release-event", G_CALLBACK(keyboard_event_handler
), NULL
);
577 piano_keyboard_get_type(void)
579 static GType mk_type
= 0;
582 static const GTypeInfo mk_info
= {
583 sizeof(PianoKeyboardClass
),
584 NULL
, /* base_init */
585 NULL
, /* base_finalize */
586 (GClassInitFunc
) piano_keyboard_class_init
,
587 NULL
, /* class_finalize */
588 NULL
, /* class_data */
589 sizeof (PianoKeyboard
),
591 (GInstanceInitFunc
) piano_keyboard_init
,
594 mk_type
= g_type_register_static(GTK_TYPE_DRAWING_AREA
, "PianoKeyboard", &mk_info
, 0);
601 piano_keyboard_new(void)
606 widget
= gtk_type_new(piano_keyboard_get_type());
607 pk
= PIANO_KEYBOARD(widget
);
609 pk
->maybe_stop_sustained_notes
= 0;
610 pk
->sustain_new_notes
= 0;
611 pk
->enable_keyboard_cue
= 0;
613 pk
->note_being_pressed_using_mouse
= -1;
614 memset((void *)pk
->notes
, 0, sizeof(struct Note
) * NNOTES
);
615 pk
->key_bindings
= g_hash_table_new(g_str_hash
, g_str_equal
);
616 bind_keys_qwerty(pk
);
622 piano_keyboard_set_keyboard_cue(PianoKeyboard
*pk
, int enabled
)
624 pk
->enable_keyboard_cue
= enabled
;
628 piano_keyboard_sustain_press(PianoKeyboard
*pk
)
630 if (!pk
->sustain_new_notes
) {
631 pk
->sustain_new_notes
= 1;
632 pk
->maybe_stop_sustained_notes
= 1;
637 piano_keyboard_sustain_release(PianoKeyboard
*pk
)
639 if (pk
->maybe_stop_sustained_notes
)
640 stop_sustained_notes(pk
);
642 pk
->sustain_new_notes
= 0;
646 piano_keyboard_set_note_on(PianoKeyboard
*pk
, int note
)
648 if (pk
->notes
[note
].pressed
== 0) {
649 pk
->notes
[note
].pressed
= 1;
655 piano_keyboard_set_note_off(PianoKeyboard
*pk
, int note
)
657 if (pk
->notes
[note
].pressed
|| pk
->notes
[note
].sustained
) {
658 pk
->notes
[note
].pressed
= 0;
659 pk
->notes
[note
].sustained
= 0;
665 piano_keyboard_set_octave(PianoKeyboard
*pk
, int octave
)
667 stop_unsustained_notes(pk
);
669 gtk_widget_queue_draw(GTK_WIDGET(pk
));
673 piano_keyboard_set_keyboard_layout(PianoKeyboard
*pk
, const char *layout
)
677 if (!strcasecmp(layout
, "QWERTY")) {
678 bind_keys_qwerty(pk
);
680 } else if (!strcasecmp(layout
, "QWERTZ")) {
681 bind_keys_qwertz(pk
);
683 } else if (!strcasecmp(layout
, "AZERTY")) {
684 bind_keys_azerty(pk
);
687 /* Unknown layout name. */