Style fixes; no functional changes.
[jack-keyboard.git] / src / pianokeyboard.c
blobb8dde34db442ebb06a223b1fc0ab924234a4aede
1 /*-
2 * Copyright (c) 2007, 2008 Edward Tomasz NapieraƂa <trasz@FreeBSD.org>
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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
24 * SUCH DAMAGE.
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>.
34 #include <assert.h>
35 #include <string.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkkeysyms.h>
39 #include "pianokeyboard.h"
41 #define PIANO_KEYBOARD_DEFAULT_WIDTH 730
42 #define PIANO_KEYBOARD_DEFAULT_HEIGHT 70
44 enum {
45 NOTE_ON_SIGNAL,
46 NOTE_OFF_SIGNAL,
47 LAST_SIGNAL
50 static guint piano_keyboard_signals[LAST_SIGNAL] = { 0 };
52 static void
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;
58 GdkGC *gc;
60 w = pk->notes[0].w;
61 h = pk->notes[0].h;
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);
77 static void
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];
86 GtkWidget *widget;
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)
95 is_white = !is_white;
97 if (is_white)
98 gdk_gc_set_rgb_fg_color(gc, &white);
99 else
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;
120 * that didn't work.
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);
128 static int
129 press_key(PianoKeyboard *pk, int key)
131 assert(key >= 0);
132 assert(key < NNOTES);
134 pk->maybe_stop_sustained_notes = 0;
136 /* This is for keyboard autorepeat protection. */
137 if (pk->notes[key].pressed)
138 return (0);
140 if (pk->sustain_new_notes)
141 pk->notes[key].sustained = 1;
142 else
143 pk->notes[key].sustained = 0;
145 pk->notes[key].pressed = 1;
147 g_signal_emit_by_name(GTK_WIDGET(pk), "note-on", key);
148 draw_note(pk, key);
150 return (1);
153 static int
154 release_key(PianoKeyboard *pk, int key)
156 assert(key >= 0);
157 assert(key < NNOTES);
159 pk->maybe_stop_sustained_notes = 0;
161 if (!pk->notes[key].pressed)
162 return (0);
164 if (pk->sustain_new_notes)
165 pk->notes[key].sustained = 1;
167 pk->notes[key].pressed = 0;
169 if (pk->notes[key].sustained)
170 return (0);
172 g_signal_emit_by_name(GTK_WIDGET(pk), "note-off", key);
173 draw_note(pk, key);
175 return (1);
178 static void
179 stop_unsustained_notes(PianoKeyboard *pk)
181 int i;
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);
187 draw_note(pk, i);
192 static void
193 stop_sustained_notes(PianoKeyboard *pk)
195 int i;
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);
202 draw_note(pk, i);
207 static int
208 key_binding(PianoKeyboard *pk, const char *key)
210 gpointer notused, note;
211 gboolean found;
213 assert(pk->key_bindings != NULL);
215 found = g_hash_table_lookup_extended(pk->key_bindings, key, &notused, &note);
217 if (!found)
218 return (-1);
220 return ((int)note);
223 static void
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);
231 static void
232 clear_notes(PianoKeyboard *pk)
234 assert(pk->key_bindings != NULL);
236 g_hash_table_remove_all(pk->key_bindings);
239 static void
240 bind_keys_qwerty(PianoKeyboard *pk)
242 clear_notes(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);
280 static void
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);
290 static void
291 bind_keys_azerty(PianoKeyboard *pk)
293 clear_notes(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);
331 static gint
332 keyboard_event_handler(GtkWidget *mk, GdkEventKey *event, gpointer notused)
334 int note;
335 char *key;
336 guint keyval;
337 GdkKeymapKey kk;
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;
343 kk.level = 0;
344 kk.group = 0;
346 keyval = gdk_keymap_lookup_key(NULL, &kk);
348 key = gdk_keyval_name(gdk_keyval_to_lower(keyval));
350 if (key == NULL) {
351 g_message("gtk_keyval_name() returned NULL; please report this.");
352 return (FALSE);
355 note = key_binding(pk, key);
357 if (note < 0) {
358 /* Key was not bound. Maybe it's one of the keys handled in jack-keyboard.c. */
359 return (FALSE);
362 note += pk->octave * 12;
364 assert(note >= 0);
365 assert(note < NNOTES);
367 if (event->type == GDK_KEY_PRESS) {
368 press_key(pk, note);
370 } else if (event->type == GDK_KEY_RELEASE) {
371 release_key(pk, note);
374 return (TRUE);
377 static int
378 get_note_for_xy(PianoKeyboard *pk, int x, int y)
380 int height, note;
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)
387 continue;
389 if (x >= pk->notes[note].x && x <= pk->notes[note].x + pk->notes[note].w)
390 return (note);
394 for (note = 0; note < NNOTES - 1; note++) {
395 if (!pk->notes[note].white)
396 continue;
398 if (x >= pk->notes[note].x && x <= pk->notes[note].x + pk->notes[note].w)
399 return (note);
402 return (-1);
405 static gboolean
406 mouse_button_event_handler(PianoKeyboard *pk, GdkEventButton *event, gpointer notused)
408 int x, y, note;
410 x = event->x;
411 y = event->y;
413 note = get_note_for_xy(pk, x, y);
415 if (event->button != 1)
416 return (TRUE);
418 if (event->type == GDK_BUTTON_PRESS) {
419 /* This is possible when you make the window a little wider and then click
420 on the grey area. */
421 if (note < 0) {
422 return (TRUE);
425 if (pk->note_being_pressed_using_mouse >= 0)
426 release_key(pk, pk->note_being_pressed_using_mouse);
428 press_key(pk, note);
429 pk->note_being_pressed_using_mouse = note;
431 } else if (event->type == GDK_BUTTON_RELEASE) {
432 if (note >= 0) {
433 release_key(pk, note);
435 } else {
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;
444 return (TRUE);
447 static gboolean
448 mouse_motion_event_handler(PianoKeyboard *pk, GdkEventMotion *event, gpointer notused)
450 int note;
452 if ((event->state & GDK_BUTTON1_MASK) == 0)
453 return (TRUE);
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);
461 press_key(pk, note);
462 pk->note_being_pressed_using_mouse = note;
465 return (TRUE);
468 static gboolean
469 piano_keyboard_expose(GtkWidget *widget, GdkEventExpose *event)
471 int i;
472 PianoKeyboard *pk = PIANO_KEYBOARD(widget);
474 for (i = 0; i < NNOTES; i++)
475 draw_note(pk, i);
477 return (TRUE);
480 static void
481 piano_keyboard_size_request(GtkWidget *widget, GtkRequisition *requisition)
483 requisition->width = PIANO_KEYBOARD_DEFAULT_WIDTH;
484 requisition->height = PIANO_KEYBOARD_DEFAULT_HEIGHT;
487 static void
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;
515 continue;
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;
524 white_key++;
528 static void
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);
543 static void
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;
564 static void
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);
576 GType
577 piano_keyboard_get_type(void)
579 static GType mk_type = 0;
581 if (!mk_type) {
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),
590 0, /* n_preallocs */
591 (GInstanceInitFunc) piano_keyboard_init,
594 mk_type = g_type_register_static(GTK_TYPE_DRAWING_AREA, "PianoKeyboard", &mk_info, 0);
597 return (mk_type);
600 GtkWidget *
601 piano_keyboard_new(void)
603 GtkWidget *widget;
604 PianoKeyboard *pk;
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;
612 pk->octave = 4;
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);
618 return (widget);
621 void
622 piano_keyboard_set_keyboard_cue(PianoKeyboard *pk, int enabled)
624 pk->enable_keyboard_cue = enabled;
627 void
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;
636 void
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;
645 void
646 piano_keyboard_set_note_on(PianoKeyboard *pk, int note)
648 if (pk->notes[note].pressed == 0) {
649 pk->notes[note].pressed = 1;
650 draw_note(pk, note);
654 void
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;
660 draw_note(pk, note);
664 void
665 piano_keyboard_set_octave(PianoKeyboard *pk, int octave)
667 stop_unsustained_notes(pk);
668 pk->octave = octave;
669 gtk_widget_queue_draw(GTK_WIDGET(pk));
672 gboolean
673 piano_keyboard_set_keyboard_layout(PianoKeyboard *pk, const char *layout)
675 assert(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);
686 } else {
687 /* Unknown layout name. */
688 return (TRUE);
691 return (FALSE);