2 Copyright 2009 by Hans Baier, Krzysztof Foltman
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
);
36 public signal void note_on (int note
, int vel
);
39 public signal void note_off (int note
);
41 /// Range (number of white keys = number of octaves * 7 + 1)
44 /// The note currently pressed via mouse selection
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 };
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
);
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 };
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
);
89 Gdk
.cairo_set_source_color (c
, scOutline
);
90 if (!pre_draw_outline (c
, ref ki
))
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 };
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
))
109 post_draw (c
, ref ki
);
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
) {
129 public new
virtual void get_preferred_height (out int minimum_height
, out int natural_height
) {
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,
150 public override bool key_press_event (Gdk
.EventKey event
) {
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
) {
176 last_key
= pos_to_note ((int)event
.x
, (int)event
.y
, out vel
);
178 note_on (last_key
, vel
);
182 public override bool button_release_event (Gdk
.EventButton event
) {
190 public override bool motion_notify_event (Gdk
.EventMotion event
) {
194 int key
= pos_to_note ((int)event
.x
, (int)event
.y
, out vel
);
201 note_on (last_key
, vel
);
208 } // namespace Prolooks