Fix can_focus / remove deprecated HBox/VBox
[libprolooks.git] / src / Keyboard.vala
blob7f908ec840c752ca3cddb4663380d1de93a743a2
1 /*
2 Copyright 2009 by Hans Baier, Krzysztof Foltman
3 License: LGPLv2+
4 */
6 using Gtk;
8 namespace Prolooks {
10 /// Structure with information needed for drawing a single key
11 public struct KeyInfo {
12 public double x; ///< X coordinate of the top-left point of the key
13 public double y; ///< Y coordinate of the top-left point of the key
14 public double width; ///< key width
15 public double height; ///< key height
16 public int note; ///< MIDI note number
17 public bool black; ///< true if it's a black key, false if it's a white key
20 public class Keyboard : DrawingArea {
21 /// called before drawing key interior
22 /// @retval true do not draw the key
23 public signal bool pre_draw (Cairo.Context c, ref KeyInfo ki);
25 /// @retval true do not draw the outline
26 /// called before drawing key outline of white keys
27 public signal bool pre_draw_outline (Cairo.Context c, ref KeyInfo ki);
29 /// called after key is drawn using standard method (but not if drawing is skipped)
30 public signal void post_draw (Cairo.Context c, ref KeyInfo ki);
32 /// called after key is drawn
33 public signal void post_all (Cairo.Context c);
35 /// key was pressed
36 public signal void note_on (int note, int vel);
38 /// key was released
39 public signal void note_off (int note);
41 /// Range (number of white keys = number of octaves * 7 + 1)
42 int nkeys;
44 /// The note currently pressed via mouse selection
45 int last_key;
47 /// If true, the keyboard accepts mouse clicks and keys
48 [Description(blurb="whether the keyboard generates note_on / note_off signals when its keys are pressed")]
49 public bool interactive { get; set; }
51 static const int[] semitones_b = { 1, 3, -1, 6, 8, 10, -1 };
52 static const int[] semitones_w = { 0, 2, 4, 5, 7, 9, 11 };
55 construct {
56 set_can_focus(true);
57 nkeys = 7 * 3 + 1;
58 last_key = -1;
59 add_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK |
60 Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK |
61 Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
62 size_allocate.connect(on_size_allocate);
63 draw.connect(on_draw);
64 set_size_request(12 * nkeys + 1, 32);
67 /* Widget is asked to draw itself */
68 public bool on_draw (Cairo.Context c) {
69 Gtk.Allocation allocation;
70 get_allocation(out allocation);
72 Gdk.Color scWhiteKey = { 0, 65535, 65535, 65535 };
73 Gdk.Color scBlackKey = { 0, 0, 0, 0 };
74 Gdk.Color scOutline = { 0, 0, 0, 0 };
76 int sy = allocation.height - 1;
77 c.set_line_join (Cairo.LineJoin.MITER);
78 c.set_line_width (1);
80 for (int i = 0; i < nkeys; i++)
82 KeyInfo ki = { 0.5 + 12 * i, 0.5, 12, sy, 12 * (i / 7) + semitones_w[i % 7], false };
83 c.new_path ();
84 Gdk.cairo_set_source_color (c, scWhiteKey);
85 if (!pre_draw (c, ref ki))
87 c.rectangle (ki.x, ki.y, ki.width, ki.height);
88 c.fill_preserve ();
89 Gdk.cairo_set_source_color (c, scOutline);
90 if (!pre_draw_outline (c, ref ki))
91 c.stroke ();
92 else
93 c.new_path ();
94 post_draw (c, ref ki);
98 for (int i = 0; i < nkeys - 1; i++)
100 if (((1 << (i % 7)) & 59) != 0)
102 KeyInfo ki = { 8.5 + 12 * i, 0.5, 8, sy * 3 / 5, 12 * (i / 7) + semitones_b[i % 7], true };
103 c.new_path ();
104 c.rectangle (ki.x, ki.y, ki.width, ki.height);
105 Gdk.cairo_set_source_color (c, scBlackKey);
106 if (!pre_draw (c, ref ki))
108 c.fill ();
109 post_draw (c, ref ki);
114 post_all (c);
116 return true;
119 public new virtual void get_preferred_width (out int minimum_width, out int natural_width) {
120 minimum_width = 12 * nkeys + 1;
121 natural_width = 12 * nkeys + 1;
124 public new virtual void get_preferred_height_for_width (int width, out int minimum_height, out int natural_height) {
125 minimum_height = 32;
126 natural_height = 32;
129 public new virtual void get_preferred_height (out int minimum_height, out int natural_height) {
130 minimum_height = 32;
131 natural_height = 32;
134 public new virtual void get_preferred_width_for_height (int height, out int minimum_width, out int natural_width) {
135 minimum_width = 12 * nkeys + 1;
136 natural_width = 12 * nkeys + 1;
139 public void on_size_allocate (Gtk.Allocation allocation) {
140 set_allocation(allocation);
142 if (get_realized ()) {
143 get_window().move_resize (allocation.x + (allocation.width - allocation.width) / 2,
144 allocation.y,
145 allocation.width,
146 allocation.height );
150 public override bool key_press_event (Gdk.EventKey event) {
151 return false;
154 int pos_to_note (int x, int y, out int vel = null) {
155 Gtk.Allocation allocation;
156 get_parent().get_allocation(out allocation);
157 // first try black keys
158 if (y <= allocation.height * 3 / 5 && x >= 0 && (x - 8) % 12 < 8) {
159 int blackkey = (x - 8) / 12;
160 if (blackkey < nkeys && ((59 & (1 << (blackkey % 7))) != 0)) {
161 return semitones_b[blackkey % 7] + 12 * (blackkey / 7);
164 // if not a black key, then which white one?
165 int whitekey = x / 12;
167 // semitones within octave + 12 semitones per octave
168 return semitones_w[whitekey % 7] + 12 * (whitekey / 7);
171 public override bool button_press_event (Gdk.EventButton event) {
172 if (!interactive)
173 return false;
174 grab_focus ();
175 int vel = 127;
176 last_key = pos_to_note ((int)event.x, (int)event.y, out vel);
177 if (last_key != -1)
178 note_on (last_key, vel);
179 return false;
182 public override bool button_release_event (Gdk.EventButton event) {
183 if (!interactive)
184 return false;
185 if (last_key != -1)
186 note_off (last_key);
187 return false;
190 public override bool motion_notify_event (Gdk.EventMotion event) {
191 if (!interactive)
192 return false;
193 int vel = 127;
194 int key = pos_to_note ((int)event.x, (int)event.y, out vel);
195 if (key != last_key)
197 if (last_key != -1)
198 note_off (last_key);
199 last_key = key;
200 if (last_key != -1)
201 note_on (last_key, vel);
203 return false;
206 } // class Keyboard
208 } // namespace Prolooks