GUI: support time signatures with denominator != 4 (like 6/8)
[stwbeast.git] / beast-gtk / bstpianoroll.c
1 /* BEAST - Better Audio System
2  * Copyright (C) 2002-2004 Tim Janik
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * A copy of the GNU Lesser General Public License should ship along
15  * with this library; if not, see http://www.gnu.org/copyleft/.
16  */
17 #include "bstpianoroll.h"
18 #include "bstasciipixbuf.h"
19 #include "bstskinconfig.h"
20 #include <string.h>
21 #include <math.h>
22
23
24 /* --- defines --- */
25 /* accessors */
26 #define STYLE(self)             (GTK_WIDGET (self)->style)
27 #define STATE(self)             (GTK_WIDGET (self)->state)
28 #define XTHICKNESS(self)        (STYLE (self)->xthickness)
29 #define YTHICKNESS(self)        (STYLE (self)->ythickness)
30 #define ALLOCATION(self)        (&GTK_WIDGET (self)->allocation)
31 #define N_OCTAVES(self)         (MAX_OCTAVE (self) - MIN_OCTAVE (self) + 1)
32 #define MAX_OCTAVE(self)        (SFI_NOTE_OCTAVE ((self)->max_note))
33 #define MAX_SEMITONE(self)      (SFI_NOTE_SEMITONE ((self)->max_note))
34 #define MIN_OCTAVE(self)        (SFI_NOTE_OCTAVE ((self)->min_note))
35 #define MIN_SEMITONE(self)      (SFI_NOTE_SEMITONE ((self)->min_note))
36 #define X_OFFSET(self)          (GXK_SCROLL_CANVAS (self)->x_offset)
37 #define Y_OFFSET(self)          (GXK_SCROLL_CANVAS (self)->y_offset)
38 #define PLAYOUT_HPANEL(self)    (gxk_scroll_canvas_get_pango_layout (GXK_SCROLL_CANVAS (self), 0))
39 #define COLOR_GC(self, i)       (GXK_SCROLL_CANVAS (self)->color_gc[i])
40 #define COLOR_GC_HGRID(self)    (COLOR_GC (self, CINDEX_HGRID))
41 #define COLOR_GC_VGRID(self)    (COLOR_GC (self, CINDEX_VGRID))
42 #define COLOR_GC_HBAR(self)     (COLOR_GC (self, CINDEX_HBAR))
43 #define COLOR_GC_VBAR(self)     (COLOR_GC (self, CINDEX_VBAR))
44 #define COLOR_GC_MBAR(self)     (COLOR_GC (self, CINDEX_MBAR))
45 #define COLOR_GC_POINTER(self)  (COLOR_GC (self, CINDEX_POINTER))
46 #define COLOR_GC_SELECT(self)   (COLOR_GC (self, CINDEX_SELECT))
47 #define CANVAS(self)            (GXK_SCROLL_CANVAS (self)->canvas)
48 #define HPANEL(self)            (GXK_SCROLL_CANVAS (self)->top_panel)
49 #define VPANEL(self)            (GXK_SCROLL_CANVAS (self)->left_panel)
50 /* layout (requisition) */
51 #define NOTE_HEIGHT(self)       ((gint) ((self)->vzoom * 1.2))          /* factor must be between 1 .. 2 */
52 #define OCTAVE_HEIGHT(self)     (14 * (self)->vzoom + 7 * NOTE_HEIGHT (self))   /* coord_to_note() */
53 #define KEYBOARD_WIDTH(self)    (32 + 2 * XTHICKNESS (self))
54 #define KEYBOARD_RATIO(self)    (2.9 / 5.)      /* black/white key ratio */
55 #define CMARK_WIDTH(self)       (1 + XTHICKNESS (self) + 1)
56 /* appearance */
57 #define KEY_DEFAULT_VPIXELS     (4)
58 #define QNOTE_HPIXELS           (30)    /* guideline */
59
60 enum {
61   CINDEX_C,
62   CINDEX_Cis,
63   CINDEX_D,
64   CINDEX_Dis,
65   CINDEX_E,
66   CINDEX_F,
67   CINDEX_Fis,
68   CINDEX_G,
69   CINDEX_Gis,
70   CINDEX_A,
71   CINDEX_Ais,
72   CINDEX_B,
73   CINDEX_HGRID,
74   CINDEX_VGRID,
75   CINDEX_HBAR,
76   CINDEX_VBAR,
77   CINDEX_MBAR,
78   CINDEX_POINTER,
79   CINDEX_SELECT,
80   CINDEX_COUNT
81 };
82
83 /* --- prototypes --- */
84 static void     bst_piano_roll_hsetup                   (BstPianoRoll           *self,
85                                                          guint                   ppb,
86                                                          guint                   bpt,
87                                                          guint                   tpqn,
88                                                          guint                   max_ticks,
89                                                          gfloat                  hzoom);
90
91 /* --- static variables --- */
92 static guint    signal_canvas_drag = 0;
93 static guint    signal_canvas_clicked = 0;
94 static guint    signal_piano_drag = 0;
95 static guint    signal_piano_clicked = 0;
96
97
98 /* --- functions --- */
99 G_DEFINE_TYPE (BstPianoRoll, bst_piano_roll, GXK_TYPE_SCROLL_CANVAS);
100
101 static void
102 piano_roll_class_setup_skin (BstPianoRollClass *class)
103 {
104   static GdkColor colors[CINDEX_COUNT] = {
105     /* C: */
106     { 0, 0x8080, 0x0000, 0x0000 },  /* dark red */
107     { 0, 0xa000, 0x0000, 0xa000 },  /* dark magenta */
108     /* D: */
109     { 0, 0x0000, 0x8000, 0x8000 },  /* dark turquoise */
110     { 0, 0x0000, 0xff00, 0xff00 },  /* light turquoise */
111     /* E: */
112     { 0, 0xff00, 0xff00, 0x0000 },  /* bright yellow */
113     /* F: */
114     { 0, 0xff00, 0x4000, 0x4000 },  /* light red */
115     { 0, 0xff00, 0x8000, 0x0000 },  /* bright orange */
116     /* G: */
117     { 0, 0xb000, 0x0000, 0x6000 },  /* dark pink */
118     { 0, 0xff00, 0x0000, 0x8000 },  /* light pink */
119     /* A: */
120     { 0, 0x0000, 0x7000, 0x0000 },  /* dark green */
121     { 0, 0x4000, 0xff00, 0x4000 },  /* bright green */
122     /* B: */
123     { 0, 0x4000, 0x4000, 0xff00 },  /* light blue */
124     /* drawing colors, index: 12+ */
125     { 0, 0x9e00, 0x9a00, 0x9100 },  /* hgrid */
126     { 0, 0x9e00, 0x9a00, 0x9100 },  /* vgrid */
127     { 0, 0x8000, 0x0000, 0x0000 },  /* hbar */
128     { 0, 0x9e00, 0x9a00, 0x9100 },  /* vbar */
129     { 0, 0xff00, 0x8000, 0x0000 },  /* mbar */
130     /* unconfigurable */
131     { 0, 0xca00, 0x2000, 0xcd00 },  /* pointer */
132     { 0, 0x0000, 0x0000, 0xff00 },  /* select (blue) */
133   };
134   GxkScrollCanvasClass *scroll_canvas_class = GXK_SCROLL_CANVAS_CLASS (class);
135   scroll_canvas_class->n_colors = G_N_ELEMENTS (colors);
136   scroll_canvas_class->colors = colors;
137   colors[CINDEX_HGRID] = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_hgrid));
138   colors[CINDEX_VGRID] = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_vgrid));
139   colors[CINDEX_HBAR] = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_hbar));
140   colors[CINDEX_VBAR] = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_vbar));
141   colors[CINDEX_MBAR] = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_mbar));
142   g_free (scroll_canvas_class->image_file_name);
143   scroll_canvas_class->image_file_name = BST_SKIN_CONFIG_STRDUP_PATH (piano_image);
144   scroll_canvas_class->image_tint = gdk_color_from_rgb (BST_SKIN_CONFIG (piano_color));
145   scroll_canvas_class->image_saturation = BST_SKIN_CONFIG (piano_shade) * 0.01;
146   gxk_scroll_canvas_class_skin_changed (scroll_canvas_class);
147 }
148
149 static void
150 bst_piano_roll_init (BstPianoRoll *self)
151 {
152   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
153   
154   GTK_WIDGET_SET_FLAGS (self, GTK_CAN_FOCUS);
155   
156   self->proxy = 0;
157   self->vzoom = KEY_DEFAULT_VPIXELS;
158   self->tpqn = 384;     /* default Ticks per Quarter Note */
159   self->ppb = 384;      /* default Parts (clock ticks) Per Beat */
160   self->bpt = 1;
161   self->max_ticks = 1;
162   self->hzoom = 1;
163   self->draw_qn_grid = TRUE;
164   self->draw_qqn_grid = TRUE;
165   self->release_closes_toplevel = TRUE;
166   self->min_note = SFI_MIN_NOTE;
167   self->max_note = SFI_MAX_NOTE;
168   gxk_scroll_canvas_set_canvas_cursor (scc, GDK_LEFT_PTR);
169   gxk_scroll_canvas_set_left_panel_cursor (scc, GDK_HAND2);
170   gxk_scroll_canvas_set_top_panel_cursor (scc, GDK_LEFT_PTR);
171   self->selection_tick = 0;
172   self->selection_duration = 0;
173   self->selection_min_note = 0;
174   self->selection_max_note = 0;
175   bst_piano_roll_hsetup (self, 384, 4, 384, 800 * 384, 1);
176   
177   bst_ascii_pixbuf_ref ();
178 }
179
180 static void
181 bst_piano_roll_destroy (GtkObject *object)
182 {
183   BstPianoRoll *self = BST_PIANO_ROLL (object);
184   
185   bst_piano_roll_set_proxy (self, 0);
186   
187   GTK_OBJECT_CLASS (bst_piano_roll_parent_class)->destroy (object);
188 }
189
190 static void
191 bst_piano_roll_dispose (GObject *object)
192 {
193   BstPianoRoll *self = BST_PIANO_ROLL (object);
194   
195   bst_piano_roll_set_proxy (self, 0);
196   
197   G_OBJECT_CLASS (bst_piano_roll_parent_class)->dispose (object);
198 }
199
200 static void
201 bst_piano_roll_finalize (GObject *object)
202 {
203   BstPianoRoll *self = BST_PIANO_ROLL (object);
204   
205   bst_piano_roll_set_proxy (self, 0);
206   
207   bst_ascii_pixbuf_unref ();
208   
209   G_OBJECT_CLASS (bst_piano_roll_parent_class)->finalize (object);
210 }
211
212 static void
213 bst_piano_roll_map (GtkWidget *widget)
214 {
215   BstPianoRoll *self = BST_PIANO_ROLL (widget);
216   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
217   
218   /* initially center the vscrollbar */
219   if (self->proxy)
220     gtk_adjustment_set_value (scc->vadjustment,
221                               (scc->vadjustment->upper -
222                                scc->vadjustment->lower -
223                                scc->vadjustment->page_size) / 2);
224
225   GTK_WIDGET_CLASS (bst_piano_roll_parent_class)->map (widget);
226 }
227
228 gfloat
229 bst_piano_roll_set_vzoom (BstPianoRoll *self,
230                           gfloat        vzoom)
231 {
232   g_return_val_if_fail (BST_IS_PIANO_ROLL (self), 0);
233   
234   self->vzoom = vzoom; //  * KEY_DEFAULT_VPIXELS;
235   self->vzoom = CLAMP (self->vzoom, 1, 16);
236   
237   gtk_widget_queue_resize (GTK_WIDGET (self));
238   
239   return self->vzoom;
240 }
241
242 static void
243 piano_roll_get_layout (GxkScrollCanvas        *scc,
244                        GxkScrollCanvasLayout  *layout)
245 {
246   BstPianoRoll *self = BST_PIANO_ROLL (scc);
247   PangoRectangle rect = { 0 };
248   pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
249   layout->top_panel_height = rect.height;
250   layout->left_panel_width = KEYBOARD_WIDTH (self);
251   layout->right_panel_width = 0;
252   layout->bottom_panel_height = 0;
253   layout->max_canvas_height = N_OCTAVES (self) * OCTAVE_HEIGHT (self);
254   layout->canvas_height = N_OCTAVES (self) * OCTAVE_HEIGHT (self);
255 }
256
257 static gdouble
258 ticks_to_pixels (BstPianoRoll *self,
259                  gdouble       ticks)
260 {
261   gdouble ppb = self->ppb;
262   gdouble tpixels = QNOTE_HPIXELS;
263   
264   /* compute pixel span of a tick range */
265   
266   tpixels *= self->hzoom / ppb * ticks;
267   if (ticks)
268     tpixels = MAX (tpixels, 1);
269   return MIN (G_MAXINT, tpixels);
270 }
271
272 static gdouble
273 pixels_to_ticks (BstPianoRoll *self,
274                  gdouble       pixels)
275 {
276   gdouble ppb = self->ppb;
277   gdouble ticks = 1.0 / (gdouble) QNOTE_HPIXELS;
278
279   /* compute tick span of a pixel range */
280
281   ticks = ticks * ppb / self->hzoom * pixels;
282   if (pixels > 0)
283     ticks = MAX (ticks, 1);
284   else
285     ticks = 0;
286   return MIN (G_MAXINT, ticks);
287 }
288
289 static gint
290 tick_to_coord (BstPianoRoll *self,
291                gint          tick)
292 {
293   return ticks_to_pixels (self, tick) - X_OFFSET (self);
294 }
295
296 static gint
297 coord_to_tick (BstPianoRoll *self,
298                gint          x,
299                gboolean      right_bound)
300 {
301   guint tick;
302   
303   x += X_OFFSET (self);
304   tick = pixels_to_ticks (self, x);
305   if (right_bound)
306     {
307       guint tick2 = pixels_to_ticks (self, x + 1);
308       
309       if (tick2 > tick)
310         tick = tick2 - 1;
311     }
312   return tick;
313 }
314
315 #define CROSSING_TACT           (1)
316 #define CROSSING_BEAT           (2)
317 #define CROSSING_QNOTE_Q        (3)
318
319 static guint
320 coord_check_crossing (BstPianoRoll *self,
321                       gint          x,
322                       guint         crossing)
323 {
324   guint ltick = coord_to_tick (self, x, FALSE);
325   guint rtick = coord_to_tick (self, x, TRUE);
326   guint lq = 0, rq = 0;
327   
328   /* catch _at_ tick boundary as well */
329   rtick += 1;
330   
331   switch (crossing)
332     {
333     case CROSSING_TACT:
334       lq = ltick / (self->ppb * self->bpt);
335       rq = rtick / (self->ppb * self->bpt);
336       break;
337     case CROSSING_BEAT:
338       lq = ltick / self->ppb;
339       rq = rtick / self->ppb;
340       break;
341     case CROSSING_QNOTE_Q:
342       lq = ltick * 4 / self->ppb;
343       rq = rtick * 4 / self->ppb;
344       break;
345     }
346   
347   return lq != rq;
348 }
349
350 #define DRAW_NONE       (0)
351 #define DRAW_START      (1)
352 #define DRAW_MIDDLE     (2)
353 #define DRAW_END        (3)
354
355 typedef struct {
356   gint  octave;
357   guint semitone;       /* 0 .. 11    within octave */
358   guint key;            /* 0 .. 6     index of white key */
359   guint key_frac;       /* 0 .. 4*z-1 fractional pixel index into key */
360   guint wstate;         /* DRAW_ START/MIDDLE/END of white key */
361   guint bstate;         /* DRAW_ NONE/START/MIDDLE/END of black key */
362   guint bmatch : 1;     /* TRUE if on black key (differs from bstate!=NONE) */
363   guint ces_fes : 1;    /* TRUE if on non-existant black key below C or F */
364   guint valid : 1;      /* FALSE if min/max octave/semitone are exceeded */
365   gint  valid_octave;
366   guint valid_semitone;
367 } NoteInfo;
368
369 static gint
370 note_to_pixels (BstPianoRoll *self,
371                 gint          note,
372                 gint         *height_p,
373                 gint         *ces_fes_height_p)
374 {
375   gint octave, ythickness = 1, z = self->vzoom, h = NOTE_HEIGHT (self), semitone = SFI_NOTE_SEMITONE (note);
376   gint oheight = OCTAVE_HEIGHT (self), y, zz = z + z, offs = 0, height = h;
377   
378   switch (semitone)
379     {
380     case 10:    offs += zz + h;
381     case  8:    offs += zz + h;
382     case  6:    offs += zz + h + zz + h;
383     case  3:    offs += zz + h;
384     case  1:    offs += z + h + (zz - h) / 2;
385       break;
386     case 11:    offs += h + zz;
387     case  9:    offs += h + zz;
388     case  7:    offs += h + zz;
389     case  5:    offs += h + zz;
390     case  4:    offs += h + zz;
391     case  2:    offs += h + zz;
392     case  0:    offs += z;
393       break;
394     }
395   octave = N_OCTAVES (self) - 1 - SFI_NOTE_OCTAVE (note) + MIN_OCTAVE (self);
396   y = octave * oheight;
397   y += oheight - offs - h;
398   
399   /* spacing out by a bit looks nicer */
400   if (z >= 4)
401     {
402       height += ythickness;
403     }
404   
405   if (height_p)
406     *height_p = height;
407   if (ces_fes_height_p)
408     *ces_fes_height_p = (semitone == 0 || semitone == 4 || semitone == 5 || semitone == 11) ? z : 0;
409   
410   return y;
411 }
412
413 static gint
414 note_to_coord (BstPianoRoll *self,
415                gint          note,
416                gint         *height_p,
417                gint         *ces_fes_height_p)
418 {
419   return note_to_pixels (self, note, height_p, ces_fes_height_p) - Y_OFFSET (self);
420 }
421
422 static gboolean
423 coord_to_note (BstPianoRoll *self,
424                gint          y,
425                NoteInfo     *info)
426 {
427   gint ythickness = 1, i, z = self->vzoom, h = NOTE_HEIGHT (self);
428   gint end_shift, start_shift, black_shift = 0;
429   gint oheight = OCTAVE_HEIGHT (self), kheight = 2 * z + h;
430   
431   y += Y_OFFSET (self);
432   info->octave = y / oheight;
433   i = y - info->octave * oheight;
434   i = oheight - 1 - i;          /* octave increases with decreasing y */
435   info->key = i / kheight;
436   info->key_frac = i - info->key * kheight;
437   i = info->key_frac;
438   info->octave = N_OCTAVES (self) - 1 - info->octave + MIN_OCTAVE (self);
439   
440   /* figure black notes */
441   end_shift = i >= z + h;
442   start_shift = i < z; /* + ythickness; */
443   info->semitone = 0;
444   info->ces_fes = ((info->key == 0 && start_shift) ||
445                    (info->key == 2 && end_shift) ||
446                    (info->key == 3 && start_shift) ||
447                    (info->key == 6 && end_shift));
448   switch (info->key)
449     {
450     case 3:     info->semitone += 5;
451     case 0:
452       info->semitone += 0 + end_shift;
453       black_shift = end_shift;
454       break;
455     case 5:     info->semitone += 2;
456     case 4:     info->semitone += 5;
457     case 1:
458       info->semitone += 2 + (start_shift ? -1 : end_shift);
459       black_shift = start_shift || end_shift;
460       break;
461     case 6:     info->semitone += 7;
462     case 2:
463       info->semitone += 4 - start_shift;
464       black_shift = start_shift;
465       break;
466     }
467   
468   /* pixel layout and note numbers:
469    * Iz|h|zIz|h|zIz|h|zIz|h|zIz|h|zIz|h|zIz|h|zI
470    * I 0 |#1#|2|#3#|4  I  5|#6#|7|#8#|9|#10|11 I
471    * I   |###| |###|   I   |###| |###| |###|   I
472    * I   +-+-+ +-+-+   I   +-+-+ +-+-+ +-+-+   I
473    * I     I     I     I     I     I     I     I
474    * +--0--+--1--+--2--+--3--+--4--+--5--+--6--+
475    * i=key_fraction, increases to right --->
476    */
477   
478   /* figure draw states */
479   if (i < ythickness)
480     info->wstate = DRAW_START;
481   else if (i < kheight - ythickness)
482     info->wstate = DRAW_MIDDLE;
483   else
484     info->wstate = DRAW_END;
485   if (!black_shift)
486     info->bstate = DRAW_NONE;
487   else if (i < z - ythickness)
488     info->bstate = DRAW_MIDDLE;
489   else if (i < z)
490     info->bstate = DRAW_START;
491   else if (i < z + h + ythickness)
492     info->bstate = DRAW_END;
493   else
494     info->bstate = DRAW_MIDDLE;
495   
496   /* behaviour fixup, ignore black note borders */
497   if (black_shift && info->bstate == DRAW_START)
498     {
499       info->bmatch = FALSE;
500       info->semitone += 1;
501     }
502   else if (black_shift && info->bstate == DRAW_END)
503     {
504       info->bmatch = FALSE;
505       info->semitone -= 1;
506     }
507   else
508     info->bmatch = TRUE;
509   
510   /* validate note */
511   if (y < 0 ||          /* we calc junk in this case, flag invalidity */
512       info->octave > MAX_OCTAVE (self) ||
513       (info->octave == MAX_OCTAVE (self) && info->semitone > MAX_SEMITONE (self)))
514     {
515       info->valid_octave = MAX_OCTAVE (self);
516       info->valid_semitone = MAX_SEMITONE (self);
517       info->valid = FALSE;
518     }
519   else if (info->octave < MIN_OCTAVE (self) ||
520            (info->octave == MIN_OCTAVE (self) && info->semitone < MIN_SEMITONE (self)))
521     {
522       info->valid_octave = MIN_OCTAVE (self);
523       info->valid_semitone = MIN_SEMITONE (self);
524       info->valid = FALSE;
525     }
526   else
527     {
528       info->valid_octave = info->octave;
529       info->valid_semitone = info->semitone;
530       info->valid = TRUE;
531     }
532   
533   return info->bmatch != 0;
534 }
535
536 static void
537 piano_roll_allocate_marker (BstPianoRoll    *self,
538                             GxkScrollMarker *marker)
539 {
540   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
541   gint ch = 10, x = tick_to_coord (self, marker[0].coords.x);
542   if (CANVAS (self))
543     gdk_window_get_size (CANVAS (self), NULL, &ch);
544   gxk_scroll_canvas_setup_marker (scc, &marker[0], &scc->canvas,
545                                   x - CMARK_WIDTH (self) / 2, 0,
546                                   CMARK_WIDTH (self), ch);
547 }
548
549 static void
550 piano_roll_move_marker (BstPianoRoll    *self,
551                         GxkScrollMarker *marker)
552 {
553   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
554   gint x = tick_to_coord (self, marker[0].coords.x);
555   gxk_scroll_canvas_move_marker (scc, &marker[0], x - CMARK_WIDTH (self) / 2, 0);
556 }
557
558 static void
559 bst_piano_roll_draw_marker (GxkScrollCanvas *scc,
560                             GdkWindow       *drawable,
561                             GdkRectangle    *area,
562                             GxkScrollMarker *marker)
563 {
564   BstPianoRoll *self = BST_PIANO_ROLL (scc);
565   BstPianoRollMarkerType mtype = marker->mtype;
566   gint x = marker->extends.x, y = marker->extends.y, width = marker->extends.width, height = marker->extends.height;
567   GdkGC *draw_gc;
568   switch (mtype)
569     {
570     case BST_PIANO_ROLL_MARKER_POINTER:
571       draw_gc = COLOR_GC_POINTER (self);
572       gdk_draw_rectangle (drawable, COLOR_GC_VBAR (self), FALSE, /* FALSE grows the rectangle by one pixel */
573                           x, y, width - 1, height - 1);
574       gdk_draw_rectangle (drawable, COLOR_GC_POINTER (self), TRUE,
575                           x + 1, y + 1, width - 2 * 1, height - 2 * 1);
576       break;
577     case BST_PIANO_ROLL_MARKER_SELECT:
578       draw_gc = COLOR_GC_SELECT (self);
579       gdk_draw_rectangle (drawable, draw_gc, TRUE,
580                           x + XTHICKNESS (self), y + YTHICKNESS (self),
581                           width - 2 * XTHICKNESS (self),
582                           height - 2 * YTHICKNESS (self));
583       gtk_paint_shadow (STYLE (self), drawable, STATE (self),
584                         GTK_SHADOW_IN, NULL, NULL, NULL,
585                         x, y, width, height);
586       break;
587     default:
588       break;
589     }
590 }
591
592 static void
593 piano_roll_reallocate_contents (GxkScrollCanvas *scc,
594                                 gint             xdiff,
595                                 gint             ydiff)
596 {
597   BstPianoRoll *self = BST_PIANO_ROLL (scc);
598   guint i;
599   for (i = 0; i < scc->n_markers; i++)
600     if (xdiff || ydiff)
601       piano_roll_move_marker (self, scc->markers + i);
602     else
603       piano_roll_allocate_marker (self, scc->markers + i);
604 }
605
606 static void
607 bst_piano_roll_overlap_grow_vpanel_area (BstPianoRoll *self,
608                                          GdkRectangle *area)
609 {
610   /* grow vpanel exposes by surrounding white keys */
611   area->y -= OCTAVE_HEIGHT (self) / 7;                  /* fudge 1 key upwards */
612   area->height += OCTAVE_HEIGHT (self) / 7;             /* compensate for y-=key */
613   area->height += OCTAVE_HEIGHT (self) / 7;             /* fudge 1 key downwards */
614 }
615
616 static void
617 bst_piano_roll_draw_vpanel (GxkScrollCanvas *scc,
618                             GdkWindow       *drawable,
619                             GdkRectangle    *area)
620 {
621   BstPianoRoll *self = BST_PIANO_ROLL (scc);
622   GdkGC *black_gc = STYLE (self)->fg_gc[GTK_STATE_NORMAL];
623   GdkGC *dark_gc = STYLE (self)->dark_gc[GTK_STATE_NORMAL];
624   GdkGC *light_gc = STYLE (self)->light_gc[GTK_STATE_NORMAL];
625   gint y, start_x = 0, white_x = KEYBOARD_WIDTH (self), black_x = white_x * KEYBOARD_RATIO (self);
626   gint width, height;
627   gdk_window_get_size (drawable, &width, &height);
628   bst_piano_roll_overlap_grow_vpanel_area (self, area);
629   
630   /* draw vertical frame lines */
631   gdk_draw_line (drawable, dark_gc, start_x + white_x - 1, area->y, start_x + white_x - 1, area->y + area->height - 1);
632   gdk_draw_line (drawable, light_gc, start_x, area->y, start_x, area->y + area->height - 1);
633
634   /* draw horizontal lines */
635   for (y = MAX (area->y, 0); y < area->y + area->height; y++)
636     {
637       gint x = black_x + 1;
638       NoteInfo info;
639       
640       coord_to_note (self, y, &info);
641       switch (info.bstate)
642         {
643         case DRAW_START:
644           gdk_draw_line (drawable, black_gc, start_x + 1, y, start_x + black_x - 1, y);
645           gdk_draw_line (drawable, dark_gc,  start_x + black_x, y, start_x + black_x, y);
646           break;
647         case DRAW_MIDDLE:
648           gdk_draw_line (drawable, black_gc, start_x + 1, y, start_x + black_x - 1, y);
649           gdk_draw_line (drawable, dark_gc,  start_x + black_x, y, start_x + black_x, y);
650           break;
651         case DRAW_END:
652           gdk_draw_line (drawable, dark_gc, start_x + 1, y, start_x + black_x, y);
653           break;
654         default:
655           x = 0;
656         }
657       switch (info.wstate)
658         {
659         case DRAW_START:
660           gdk_draw_line (drawable, dark_gc, start_x + x, y, start_x + white_x, y);
661           if (info.semitone == 0)       /* C */
662             {
663               gint pbheight, ypos, ythickness = 1, overlap = 1;
664               gint pbwidth = white_x - black_x + overlap;
665               GdkPixbuf *pixbuf;
666               
667               pbheight = OCTAVE_HEIGHT (self) / 7;
668               pbwidth /= 2;
669               ypos = y - pbheight + ythickness;
670               pixbuf = bst_ascii_pixbuf_new ('C', pbwidth, pbheight);
671               gdk_pixbuf_render_to_drawable (pixbuf, drawable, light_gc, 0, 0,
672                                              start_x + black_x, ypos, -1, -1,
673                                              GDK_RGB_DITHER_MAX, 0, 0);
674               g_object_unref (pixbuf);
675               if (info.octave < 0)
676                 {
677                   guint indent = pbwidth * 0.5;
678                   /* render a minus '-' for negative octaves into the 'C' */
679                   pixbuf = bst_ascii_pixbuf_new ('-', pbwidth - indent, pbheight - 1);
680                   gdk_pixbuf_render_to_drawable (pixbuf, drawable, light_gc, 0, 0,
681                                                  start_x + black_x + indent + overlap, ypos, -1, -1,
682                                                  GDK_RGB_DITHER_MAX, 0, 0);
683                   g_object_unref (pixbuf);
684                 }
685               pixbuf = bst_ascii_pixbuf_new (ABS (info.octave) + '0', pbwidth, pbheight);
686               gdk_pixbuf_render_to_drawable (pixbuf, drawable, light_gc, 0, 0,
687                                              start_x + black_x + pbwidth - overlap, ypos, -1, -1,
688                                              GDK_RGB_DITHER_MAX, 0, 0);
689               g_object_unref (pixbuf);
690             }
691           break;
692         case DRAW_MIDDLE:
693           // gdk_draw_line (drawable, white_gc, start_x + x, y, start_x + white_x, y);
694           break;
695         case DRAW_END:
696           gdk_draw_line (drawable, light_gc, start_x + x, y, start_x + white_x, y);
697           break;
698         }
699     }
700 }
701
702 static void
703 bst_piano_roll_draw_canvas (GxkScrollCanvas *scc,
704                             GdkWindow       *drawable,
705                             GdkRectangle    *area)
706 {
707   BstPianoRoll *self = BST_PIANO_ROLL (scc);
708   GdkGC *light_gc, *dark_gc = STYLE (self)->dark_gc[GTK_STATE_NORMAL];
709   gint pass, i, dlen, width, height, line_width = 0; /* line widths != 0 interfere with dash-settings on some X servers */
710   BsePartNoteSeq *pseq;
711   GXK_SCROLL_CANVAS_CLASS (bst_piano_roll_parent_class)->draw_canvas (scc, drawable, area);
712   gdk_window_get_size (drawable, &width, &height);
713
714   /* draw selection */
715   if (self->selection_duration)
716     {
717       gint x1, x2, y1, y2, h;
718       
719       x1 = tick_to_coord (self, self->selection_tick);
720       x2 = tick_to_coord (self, self->selection_tick + self->selection_duration);
721       y1 = note_to_coord (self, self->selection_max_note, &h, NULL);
722       y2 = note_to_coord (self, self->selection_min_note, &h, NULL);
723       y2 += h;
724       /* confine to 16bit coordinates for gdk to handle correctly */
725       x1 = MAX (x1, 0);
726       x2 = MIN (x2, width);
727       y1 = MAX (y1, 0);
728       y2 = MIN (y2, height);
729       gdk_draw_rectangle (drawable, GTK_WIDGET (self)->style->bg_gc[GTK_STATE_SELECTED], TRUE,
730                           x1, y1, MAX (x2 - x1, 0), MAX (y2 - y1, 0));
731     }
732
733   /* we do multiple passes to draw h/v grid lines for them to properly ovrlay */
734   for (pass = 1; pass <= 3; pass++)
735     {
736       /* draw vertical grid lines */
737       for (i = area->x; i < area->x + area->width; i++)
738         {
739           if (pass == 3 && coord_check_crossing (self, i, CROSSING_TACT))
740             {
741               GdkGC *draw_gc = COLOR_GC_VBAR (self);
742               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
743               gdk_draw_line (drawable, draw_gc, i, area->y, i, area->y + area->height - 1);
744             }
745           else if (pass == 1 && self->draw_qn_grid && coord_check_crossing (self, i, CROSSING_BEAT))
746             {
747               GdkGC *draw_gc = COLOR_GC_VGRID (self);
748               guint8 dash[3] = { 2, 2, 0 };
749               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
750               dlen = dash[0] + dash[1];
751               gdk_gc_set_dashes (draw_gc, (Y_OFFSET (self) + area->y + 1) % dlen, dash, 2);
752               gdk_draw_line (drawable, draw_gc, i, area->y, i, area->y + area->height - 1);
753               gdk_gc_set_line_attributes (draw_gc, 0, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
754             }
755           else if (pass == 1 && self->draw_qqn_grid && coord_check_crossing (self, i, CROSSING_QNOTE_Q))
756             {
757               GdkGC *draw_gc = COLOR_GC_VGRID (self);
758               guint8 dash[3] = { 1, 1, 0 };
759               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
760               dlen = dash[0] + dash[1];
761               gdk_gc_set_dashes (draw_gc, (Y_OFFSET (self) + area->y + 1) % dlen, dash, 2);
762               gdk_draw_line (drawable, draw_gc, i, area->y, i, area->y + area->height - 1);
763               gdk_gc_set_line_attributes (draw_gc, 0, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
764             }
765         }
766       /* draw horizontal grid lines */
767       for (i = area->y; i < area->y + area->height; i++)
768         {
769           NoteInfo info;
770           coord_to_note (self, i, &info);
771           if (info.wstate != DRAW_START)
772             continue;
773           if (pass == 3 && info.semitone == 0)  /* C */
774             {
775               GdkGC *draw_gc = COLOR_GC_HBAR (self);
776               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
777               gdk_draw_line (drawable, draw_gc, area->x, i, area->x + area->width - 1, i);
778             }
779           else if (pass == 2 && info.semitone == 5) /* F */
780             {
781               GdkGC *draw_gc = COLOR_GC_MBAR (self);
782               guint8 dash[3] = { 2, 2, 0 };
783               
784               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
785               dlen = dash[0] + dash[1];
786               gdk_gc_set_dashes (draw_gc, (X_OFFSET (self) + area->x + 1) % dlen, dash, 2);
787               gdk_draw_line (drawable, draw_gc, area->x, i, area->x + area->width - 1, i);
788               gdk_gc_set_line_attributes (draw_gc, 0, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
789             }
790           else if (pass == 1)
791             {
792               GdkGC *draw_gc = COLOR_GC_HGRID (self);
793               guint8 dash[3] = { 1, 1, 0 };
794               
795               gdk_gc_set_line_attributes (draw_gc, line_width, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER);
796               dlen = dash[0] + dash[1];
797               gdk_gc_set_dashes (draw_gc, (X_OFFSET (self) + area->x + 1) % dlen, dash, 2);
798               gdk_draw_line (drawable, draw_gc, area->x, i, area->x + area->width - 1, i);
799               gdk_gc_set_line_attributes (draw_gc, 0, GDK_LINE_SOLID, GDK_CAP_BUTT, GDK_JOIN_MITER);
800             }
801         }
802     }
803
804   /* draw notes */
805   light_gc = STYLE (self)->light_gc[GTK_STATE_NORMAL];
806   dark_gc = STYLE (self)->dark_gc[GTK_STATE_NORMAL];
807   pseq = self->proxy ? bse_part_list_notes_crossing (self->proxy,
808                                                      coord_to_tick (self, area->x, FALSE),
809                                                      coord_to_tick (self, area->x + area->width, FALSE)) : NULL;
810   for (i = 0; pseq && i < pseq->n_pnotes; i++)
811     {
812       BsePartNote *pnote = pseq->pnotes[i];
813       gint semitone = SFI_NOTE_SEMITONE (pnote->note);
814       guint start = pnote->tick, end = start + pnote->duration;
815       GdkGC *xdark_gc, *xlight_gc, *xnote_gc;
816       gint x1, x2, y1, y2, height;
817       gboolean selected = pnote->selected;
818       
819       selected |= (pnote->tick >= self->selection_tick &&
820                    pnote->tick < self->selection_tick + self->selection_duration &&
821                    pnote->note >= self->selection_min_note &&
822                    pnote->note <= self->selection_max_note);
823       if (selected)
824         {
825           xdark_gc = STYLE (self)->bg_gc[GTK_STATE_SELECTED];
826           xnote_gc = STYLE (self)->fg_gc[GTK_STATE_SELECTED];
827           xlight_gc = STYLE (self)->bg_gc[GTK_STATE_SELECTED];
828         }
829       else
830         {
831           xdark_gc = STYLE (self)->black_gc;
832           xnote_gc = COLOR_GC (self, semitone);
833           xlight_gc = dark_gc;
834         }
835       x1 = tick_to_coord (self, start);
836       x2 = tick_to_coord (self, end);
837       
838       y1 = note_to_coord (self, pnote->note, &height, NULL);
839       y2 = y1 + height - 1;
840       gdk_draw_line (drawable, xdark_gc, x1, y2, x2, y2);
841       gdk_draw_line (drawable, xdark_gc, x2, y1, x2, y2);
842       gdk_draw_rectangle (drawable, xnote_gc, TRUE, x1, y1, MAX (x2 - x1, 1), MAX (y2 - y1, 1));
843       if (y2 - y1 >= 3) /* work for zoom to micro size */
844         {
845           if (xlight_gc)
846             {
847               gdk_draw_line (drawable, xlight_gc, x1, y1, x2, y1);
848               gdk_draw_line (drawable, xlight_gc, x1, y1, x1, y2);
849             }
850         }
851     }
852 }
853
854 static void
855 bst_piano_roll_overlap_grow_hpanel_area (BstPianoRoll *self,
856                                          GdkRectangle *area)
857 {
858   gint i, x = area->x, xbound = x + area->width;
859   
860   /* grow hpanel exposes by surrounding tacts */
861   i = coord_to_tick (self, x, FALSE);
862   i /= self->ppb * self->bpt;
863   if (i > 0)
864     i -= 1;             /* fudge 1 tact to the left */
865   i *= self->ppb * self->bpt;
866   x = tick_to_coord (self, i);
867   i = coord_to_tick (self, xbound + 1, TRUE);
868   i /= self->ppb * self->bpt;
869   i += 2;               /* fudge 1 tact to the right (+1 for round-off) */
870   i *= self->ppb * self->bpt;
871   xbound = tick_to_coord (self, i);
872   
873   area->x = x;
874   area->width = xbound - area->x;
875 }
876
877 static void
878 bst_piano_roll_draw_hpanel (GxkScrollCanvas *scc,
879                             GdkWindow       *drawable,
880                             GdkRectangle    *area)
881 {
882   BstPianoRoll *self = BST_PIANO_ROLL (scc);
883   GdkGC *draw_gc = STYLE (self)->fg_gc[STATE (self)];
884   PangoRectangle rect = { 0 };
885   gchar buffer[64];
886   gint i, width, height;
887   gdk_window_get_size (drawable, &width, &height);
888   bst_piano_roll_overlap_grow_hpanel_area (self, area);
889   
890   /* draw tact/note numbers */
891   gdk_gc_set_clip_rectangle (draw_gc, area);
892   for (i = area->x; i < area->x + area->width; i++)
893     {
894       /* drawing qnote numbers is not of much use if we can't even draw
895        * the qnote quarter grid, so we special case draw_qqn_grid here
896        */
897       if (coord_check_crossing (self, i, CROSSING_TACT))
898         {
899           guint next_pixel, tact = coord_to_tick (self, i, TRUE) + 1;
900           
901           tact /= (self->ppb * self->bpt);
902           next_pixel = tick_to_coord (self, (tact + 1) * (self->ppb * self->bpt));
903           
904           g_snprintf (buffer, 64, "%u", tact + 1);
905           pango_layout_set_text (PLAYOUT_HPANEL (self), buffer, -1);
906           pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
907
908           /* draw this tact if there's enough space */
909           if (i + rect.width / 2 < (i + next_pixel) / 2)
910             gdk_draw_layout (drawable, draw_gc,
911                              i - rect.width / 2, (height - rect.height) / 2,
912                              PLAYOUT_HPANEL (self));
913         }
914       else if (self->draw_qqn_grid && coord_check_crossing (self, i, CROSSING_BEAT))
915         {
916           guint next_pixel, tact = coord_to_tick (self, i, TRUE) + 1, beat = tact;
917
918           tact /= (self->ppb * self->bpt);
919           beat /= self->ppb;
920           next_pixel = tick_to_coord (self, (beat + 1) * self->ppb);
921           beat = beat % self->bpt + 1;
922           if (beat == 1)
923             continue;   /* would draw on top of tact number */
924
925           g_snprintf (buffer, 64, ":%u", beat);
926           pango_layout_set_text (PLAYOUT_HPANEL (self), buffer, -1);
927           pango_layout_get_pixel_extents (PLAYOUT_HPANEL (self), NULL, &rect);
928           
929           /* draw this tact if there's enough space */
930           if (i + rect.width < (i + next_pixel) / 2)            /* don't half width, leave some more space */
931             gdk_draw_layout (drawable, draw_gc,
932                              i - rect.width / 2, (height - rect.height) / 2,
933                              PLAYOUT_HPANEL (self));
934         }
935     }
936   gdk_gc_set_clip_rectangle (draw_gc, NULL);
937 }
938
939 static void
940 piano_roll_queue_expose (BstPianoRoll *self,
941                          GdkWindow    *window,
942                          guint         note,
943                          guint         tick_start,
944                          guint         tick_end)
945 {
946   gint x1 = tick_to_coord (self, tick_start);
947   gint x2 = tick_to_coord (self, tick_end);
948   gint height, cfheight, y1 = note_to_coord (self, note, &height, &cfheight);
949   GdkRectangle area;
950   
951   area.x = x1;
952   area.width = x2 - x1;
953   area.x -= 3;                  /* add fudge */
954   area.width += 3 + 3;          /* add fudge */
955   area.y = y1 - cfheight;
956   area.height = height + 2 * cfheight;
957   area.y -= height / 2;         /* add fudge */
958   area.height += height;        /* add fudge */
959   if (window == VPANEL (self))
960     {
961       area.x = 0;
962       gdk_window_get_size (VPANEL (self), &area.width, NULL);
963     }
964   else if (window == HPANEL (self))
965     {
966       area.y = 0;
967       gdk_window_get_size (HPANEL (self), NULL, &area.height);
968     }
969   gdk_window_invalidate_rect (window, &area, TRUE);
970 }
971
972 static void
973 piano_roll_adjustment_changed (GxkScrollCanvas *scc,
974                                GtkAdjustment   *adj)
975 {
976   BstPianoRoll *self = BST_PIANO_ROLL (scc);
977   if (adj == scc->hadjustment)
978     {
979       double umin = ticks_to_pixels (self, self->max_ticks);                    /* lower bound for adj->upper based on max_ticks */
980       double umax = pixels_to_ticks (self, 1e+9);
981       umax = ticks_to_pixels (self, MIN (umax, 1e+9));                          /* confine to possible tick range */
982       umax = MIN (umax, 1e+9);                                                  /* upper bound for adj->upper based on pixels */
983       umin = MIN (umin, umax * 1.5), umax = MAX (umin, umax);                   /* properly confine boundaries */
984       /* guard against invalid changes */
985       if (adj->lower != 0 || fabs (adj->upper - CLAMP (adj->upper, umin, umax)) > 1e-7)
986         {
987           scc->hadjustment->lower = 0;
988           scc->hadjustment->upper = CLAMP (scc->hadjustment->upper, umin, umax);
989           gtk_adjustment_changed (adj);
990         }
991     }
992   if (adj == scc->vadjustment)
993     {
994     }
995 }
996
997 static void
998 piano_roll_update_adjustments (GxkScrollCanvas *scc,
999                                gboolean         hadj,
1000                                gboolean         vadj)
1001 {
1002   BstPianoRoll *self = BST_PIANO_ROLL (scc);
1003   
1004   if (hadj)
1005     {
1006       /* allow free boundary adjustments by the user between last_tick and 1e+9 ticks and pixels.
1007        * show rubberband behaviour if last_tick exceeds the 1e+9 boundary, i.e. adj->upper should
1008        * grow beyond 1e+9 if and only if last_tick exceeds 1e+9. beyond 1.5 * 1e+9 though, we simply
1009        * cut-off to properly constrain all quantities within 2^31 bits.
1010        */
1011       double umin = ticks_to_pixels (self, self->max_ticks);                    /* lower bound for adj->upper based on max_ticks */
1012       double umax = pixels_to_ticks (self, 1e+9);
1013       umax = ticks_to_pixels (self, MIN (umax, 1e+9));                          /* confine to possible tick range */
1014       umax = MIN (umax, 1e+9);                                                  /* upper bound for adj->upper based on pixels */
1015       umin = MIN (umin, umax * 1.5), umax = MAX (umin, umax);                   /* properly confine boundaries */
1016       scc->hadjustment->lower = 0;
1017       scc->hadjustment->upper = CLAMP (scc->hadjustment->upper, umin, umax);
1018       scc->hadjustment->step_increment = ticks_to_pixels (self, self->ppb);
1019       scc->hadjustment->page_increment = ticks_to_pixels (self, self->ppb * self->bpt);
1020     }
1021   if (vadj)
1022     {
1023       scc->vadjustment->upper = OCTAVE_HEIGHT (self) * N_OCTAVES (self);
1024       scc->vadjustment->step_increment = OCTAVE_HEIGHT (self) / 7;
1025     }
1026   GXK_SCROLL_CANVAS_CLASS (bst_piano_roll_parent_class)->update_adjustments (scc, hadj, vadj);
1027 }
1028
1029 static void
1030 bst_piano_roll_hsetup (BstPianoRoll *self,
1031                        guint         ppb,
1032                        guint         bpt,
1033                        guint         tpqn,
1034                        guint         max_ticks,
1035                        gfloat        hzoom)
1036 {
1037   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
1038   uint old_ppb = self->ppb;
1039   guint old_bpt = self->bpt;
1040   uint old_tpqn = self->tpqn;
1041   guint old_max_ticks = self->max_ticks;
1042   gfloat old_hzoom = self->hzoom;
1043   gdouble old_hpos = pixels_to_ticks (self, scc->hadjustment->value);
1044
1045   /* here, we setup all things necessary to determine our
1046    * horizontal layout. we avoid resizes if only max_ticks
1047    * changes, since the tick range might grow/shrink fairly
1048    * frequently.
1049    */
1050
1051   self->ppb = MAX (ppb, 1);
1052   self->bpt = MAX (bpt, 1);
1053   self->tpqn = MAX (tpqn, 1);
1054   self->max_ticks = MAX (max_ticks, 1);
1055   self->hzoom = CLAMP (hzoom, 0.01, 100);
1056   
1057   if (old_ppb != self->ppb ||
1058       old_bpt != self->bpt ||
1059       old_tpqn != self->tpqn ||
1060       old_hzoom != self->hzoom)
1061     {
1062       self->draw_qn_grid = ticks_to_pixels (self, self->ppb) >= 3;
1063       self->draw_qqn_grid = ticks_to_pixels (self, self->ppb / 4) >= 5;
1064       gtk_widget_queue_draw (GTK_WIDGET (self));
1065       scc->hadjustment->value = ticks_to_pixels (self, old_hpos); // fix start position when hzoom changes
1066       X_OFFSET (self) = GXK_SCROLL_CANVAS (self)->hadjustment->value;
1067       gxk_scroll_canvas_update_adjustments (GXK_SCROLL_CANVAS (self), TRUE, FALSE);
1068     }
1069   else if (old_max_ticks != self->max_ticks)
1070     {
1071       X_OFFSET (self) = GXK_SCROLL_CANVAS (self)->hadjustment->value;
1072       gxk_scroll_canvas_update_adjustments (GXK_SCROLL_CANVAS (self), TRUE, FALSE);
1073     }
1074 }
1075
1076 gfloat
1077 bst_piano_roll_set_hzoom (BstPianoRoll *self,
1078                           gfloat        hzoom)
1079 {
1080   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
1081   g_return_val_if_fail (BST_IS_PIANO_ROLL (self), 0);
1082   
1083   bst_piano_roll_hsetup (self, self->ppb, self->bpt, self->tpqn, self->max_ticks, hzoom);
1084   guint i;
1085   /* readjust markers */
1086   for (i = 0; i < scc->n_markers; i++)
1087     piano_roll_allocate_marker (self, scc->markers + i);
1088   
1089   return self->hzoom;
1090 }
1091
1092 static void
1093 piano_roll_handle_drag (GxkScrollCanvas     *scc,
1094                         GxkScrollCanvasDrag *scc_drag,
1095                         GdkEvent            *event)
1096 {
1097   BstPianoRoll *self = BST_PIANO_ROLL (scc);
1098   BstPianoRollDrag drag_mem = { 0 }, *drag = &drag_mem;
1099   gint hdrag = scc_drag->canvas_drag || scc_drag->top_panel_drag;
1100   gint vdrag = scc_drag->canvas_drag || scc_drag->left_panel_drag;
1101   /* copy over drag setup */
1102   memcpy (drag, scc_drag, sizeof (*scc_drag));  /* sizeof (*scc_drag) < sizeof (*drag) */
1103   drag->proll = self;
1104   /* calculate widget specific drag data */
1105   if (hdrag)
1106     drag->current_tick = coord_to_tick (self, MAX (drag->current_x, 0), FALSE);
1107   if (vdrag)
1108     {
1109       NoteInfo info;
1110       coord_to_note (self, MAX (drag->current_y, 0), &info);
1111       drag->current_note = SFI_NOTE_GENERIC (info.valid_octave, info.valid_semitone);
1112       drag->current_valid = info.valid && !info.ces_fes;
1113     }
1114   /* sync start-position fields */
1115   if (drag->type == GXK_DRAG_START)
1116     {
1117       drag->start_tick = self->start_tick = drag->current_tick;
1118       drag->start_note = self->start_note = drag->current_note;
1119       drag->start_valid = self->start_valid = drag->current_valid;
1120     }
1121   else
1122     {
1123       drag->start_tick = self->start_tick;
1124       drag->start_note = self->start_note;
1125       drag->start_valid = self->start_valid;
1126     }
1127   /* handle drag */
1128   if (drag->canvas_drag)
1129     g_signal_emit (self, signal_canvas_drag, 0, drag);
1130   else if (drag->left_panel_drag)
1131     g_signal_emit (self, signal_piano_drag, 0, drag);
1132   /* copy over drag reply */
1133   scc_drag->state = drag->state;
1134   /* resort to clicks for unhandled button presses */
1135   if (drag->type == GXK_DRAG_START && drag->state == GXK_DRAG_UNHANDLED &&
1136       event && event->type == GDK_BUTTON_PRESS)
1137     {
1138       drag->state = GXK_DRAG_HANDLED;
1139       if (drag->canvas_drag)
1140         g_signal_emit (self, signal_canvas_clicked, 0, drag->button, drag->start_tick, drag->start_note, event);
1141       else if (drag->left_panel_drag)
1142         g_signal_emit (self, signal_piano_clicked, 0, drag->button, drag->start_tick, drag->start_note, event);
1143     }
1144 }
1145
1146 static void
1147 piano_roll_song_pointer_changed (BstPianoRoll *self,
1148                                  SfiInt        position)
1149 {
1150   BsePartLink *plink = NULL;
1151   if (self->plinks && position >= 0)
1152     {
1153       /* find size via binary lookup */
1154       guint offset = 0, n_elements = self->plinks->n_plinks;
1155       while (offset < n_elements)
1156         {
1157           guint i = (offset + n_elements) >> 1;
1158           BsePartLink *current = self->plinks->plinks[i];
1159           gint cmp = position < current->tick ? -1 : position > current->tick;
1160           if (cmp > 0) /* clamp to within duration */
1161             cmp = position < current->tick + current->duration ? 0 : 1;
1162           if (cmp == 0)
1163             {
1164               plink = current;
1165               break;
1166             }
1167           else if (cmp < 0)
1168             n_elements = i;
1169           else /* (cmp > 0) */
1170             offset = i + 1;
1171         }
1172     }
1173   if (plink)
1174     bst_piano_roll_set_marker (self, 1, position - plink->tick, BST_PIANO_ROLL_MARKER_POINTER);
1175   else
1176     bst_piano_roll_set_marker (self, 1, position, 0);
1177 }
1178
1179 static void
1180 piano_roll_time_signature_changed (BstPianoRoll *self)
1181 {
1182   if (self->song)
1183     {
1184       SfiInt numerator, denominator, tpqn;
1185       bse_proxy_get (self->song, "numerator", &numerator, "denominator", &denominator, "tpqn", &tpqn, NULL);
1186       bst_piano_roll_hsetup (self, tpqn * 4 / denominator, numerator, tpqn, self->max_ticks, self->hzoom);
1187     }
1188 }
1189
1190 static void
1191 piano_roll_links_changed (BstPianoRoll *self)
1192 {
1193   if (self->plinks)
1194     bse_part_link_seq_free (self->plinks);
1195   self->plinks = NULL;
1196   if (self->proxy)
1197     {
1198       self->plinks = bse_part_list_links (self->proxy);
1199       if (self->plinks)
1200         self->plinks = bse_part_link_seq_copy_shallow (self->plinks);
1201     }
1202 }
1203
1204 static void
1205 piano_roll_range_changed (BstPianoRoll *self)
1206 {
1207   guint max_ticks;
1208   bse_proxy_get (self->proxy, "last-tick", &max_ticks, NULL);
1209   bst_piano_roll_hsetup (self, self->ppb, self->bpt, self->tpqn, MAX (max_ticks, 1), self->hzoom);
1210 }
1211
1212 static void
1213 piano_roll_update (BstPianoRoll *self,
1214                    guint         tick,
1215                    guint         duration,
1216                    gint          min_note,
1217                    gint          max_note)
1218 {
1219   gint note;
1220   duration = MAX (duration, 1);
1221   if (GTK_WIDGET_DRAWABLE (self))
1222     for (note = min_note; note <= max_note; note++)
1223       piano_roll_queue_expose (self, CANVAS (self), note, tick, tick + duration - 1);
1224   gxk_widget_update_actions (self); /* update controllers */
1225 }
1226
1227 static void
1228 piano_roll_release_proxy (BstPianoRoll *self)
1229 {
1230   gxk_toplevel_delete (GTK_WIDGET (self));
1231   bst_piano_roll_set_proxy (self, 0);
1232 }
1233
1234 void
1235 bst_piano_roll_set_proxy (BstPianoRoll *self,
1236                           SfiProxy      proxy)
1237 {
1238   g_return_if_fail (BST_IS_PIANO_ROLL (self));
1239   if (proxy)
1240     {
1241       g_return_if_fail (BSE_IS_ITEM (proxy));
1242       g_return_if_fail (bse_item_get_project (proxy) != 0);
1243     }
1244   
1245   if (self->proxy)
1246     {
1247       if (self->song)
1248         {
1249           bse_proxy_disconnect (self->song,
1250                                 "any_signal", piano_roll_release_proxy, self,
1251                                 "any_signal", piano_roll_song_pointer_changed, self,
1252                                 NULL);
1253           bse_item_unuse (self->song);
1254           self->song = 0;
1255         }
1256       if (self->plinks)
1257         bse_part_link_seq_free (self->plinks);
1258       self->plinks = NULL;
1259       bse_proxy_disconnect (self->proxy,
1260                             "any_signal", piano_roll_release_proxy, self,
1261                             "any_signal", piano_roll_range_changed, self,
1262                             "any_signal", piano_roll_links_changed, self,
1263                             "any_signal", piano_roll_update, self,
1264                             NULL);
1265       bse_item_unuse (self->proxy);
1266       piano_roll_song_pointer_changed (self, -1);
1267     }
1268   self->proxy = proxy;
1269   if (self->proxy)
1270     {
1271       bse_item_use (self->proxy);
1272       bse_proxy_connect (self->proxy,
1273                          "swapped_signal::release", piano_roll_release_proxy, self,
1274                          "swapped_signal::property-notify::last-tick", piano_roll_range_changed, self,
1275                          "swapped_signal::links-changed", piano_roll_links_changed, self,
1276                          "swapped_signal::range-changed", piano_roll_update, self,
1277                          NULL);
1278       self->min_note = bse_part_get_min_note (self->proxy);
1279       self->max_note = bse_part_get_max_note (self->proxy);
1280       piano_roll_range_changed (self);
1281       SfiProxy song = bse_item_get_parent (self->proxy);
1282       if (song)
1283         {
1284           self->song = song;
1285           bse_item_use (self->song);
1286           bse_proxy_connect (self->song,
1287                              "swapped_signal::release", piano_roll_release_proxy, self,
1288                              "swapped_signal::pointer-changed", piano_roll_song_pointer_changed, self,
1289                              "swapped_signal::property-notify::numerator", piano_roll_time_signature_changed, self,
1290                              "swapped_signal::property-notify::denominator", piano_roll_time_signature_changed, self,
1291                              NULL);
1292         }
1293       piano_roll_links_changed (self);
1294       piano_roll_song_pointer_changed (self, -1);
1295       piano_roll_time_signature_changed (self);
1296     }
1297   gtk_widget_queue_resize (GTK_WIDGET (self));
1298 }
1299
1300 static void
1301 piano_roll_queue_region (BstPianoRoll *self,
1302                          guint         tick,
1303                          guint         duration,
1304                          gint          min_note,
1305                          gint          max_note)
1306 {
1307   if (self->proxy && duration)  /* let the part extend the area by spanning notes if necessary */
1308     bse_part_queue_notes (self->proxy, tick, duration, min_note, max_note);
1309   piano_roll_update (self, tick, duration, min_note, max_note);
1310 }
1311
1312 void
1313 bst_piano_roll_set_view_selection (BstPianoRoll *self,
1314                                    guint         tick,
1315                                    guint         duration,
1316                                    gint          min_note,
1317                                    gint          max_note)
1318 {
1319   g_return_if_fail (BST_IS_PIANO_ROLL (self));
1320   
1321   if (min_note > max_note || !duration) /* invalid selection */
1322     {
1323       tick = 0;
1324       duration = 0;
1325       min_note = 0;
1326       max_note = 0;
1327     }
1328   
1329   if (self->selection_duration && duration)
1330     {
1331       /* if at least one corner of the old an the new selection
1332        * matches, it's probably worth updating only diff-regions
1333        */
1334       if ((tick == self->selection_tick ||
1335            tick + duration == self->selection_tick + self->selection_duration) &&
1336           (min_note == self->selection_min_note ||
1337            max_note == self->selection_max_note))
1338         {
1339           guint start, end;
1340           gint note_min, note_max;
1341           /* difference on the left */
1342           start = MIN (tick, self->selection_tick);
1343           end = MAX (tick, self->selection_tick);
1344           if (end != start)
1345             piano_roll_queue_region (self, start, end - start,
1346                                      MIN (min_note, self->selection_min_note),
1347                                      MAX (max_note, self->selection_max_note));
1348           /* difference on the right */
1349           start = MIN (tick + duration, self->selection_tick + self->selection_duration);
1350           end = MAX (tick + duration, self->selection_tick + self->selection_duration);
1351           if (end != start)
1352             piano_roll_queue_region (self, start, end - start,
1353                                      MIN (min_note, self->selection_min_note),
1354                                      MAX (max_note, self->selection_max_note));
1355           start = MIN (tick, self->selection_tick);
1356           end = MAX (tick + duration, self->selection_tick + self->selection_duration);
1357           /* difference on the top */
1358           note_max = MAX (max_note, self->selection_max_note);
1359           note_min = MIN (max_note, self->selection_max_note);
1360           if (note_max != note_min)
1361             piano_roll_queue_region (self, start, end - start, note_min, note_max);
1362           /* difference on the bottom */
1363           note_max = MAX (min_note, self->selection_min_note);
1364           note_min = MIN (min_note, self->selection_min_note);
1365           if (note_max != note_min)
1366             piano_roll_queue_region (self, start, end - start, note_min, note_max);
1367         }
1368       else
1369         {
1370           /* simply update new and old selection */
1371           piano_roll_queue_region (self, self->selection_tick, self->selection_duration,
1372                                    self->selection_min_note, self->selection_max_note);
1373           piano_roll_queue_region (self, tick, duration, min_note, max_note);
1374         }
1375     }
1376   else if (self->selection_duration)
1377     piano_roll_queue_region (self, self->selection_tick, self->selection_duration,
1378                              self->selection_min_note, self->selection_max_note);
1379   else /* duration != 0 */
1380     piano_roll_queue_region (self, tick, duration, min_note, max_note);
1381   self->selection_tick = tick;
1382   self->selection_duration = duration;
1383   self->selection_min_note = min_note;
1384   self->selection_max_note = max_note;
1385 }
1386
1387 gint
1388 bst_piano_roll_get_vpanel_width (BstPianoRoll *self)
1389 {
1390   gint width = 0;
1391   g_return_val_if_fail (BST_IS_PIANO_ROLL (self), 0);
1392   if (VPANEL (self))
1393     gdk_window_get_size (VPANEL (self), &width, NULL);
1394   else
1395     width = GXK_SCROLL_CANVAS (self)->layout.left_panel_width;
1396   return width;
1397 }
1398
1399 void
1400 bst_piano_roll_get_paste_pos (BstPianoRoll *self,
1401                               guint        *tick_p,
1402                               gint         *note_p)
1403 {
1404   guint tick, semitone;
1405   gint octave;
1406   
1407   g_return_if_fail (BST_IS_PIANO_ROLL (self));
1408   
1409   if (GTK_WIDGET_DRAWABLE (self))
1410     {
1411       NoteInfo info;
1412       gint x, y, width, height;
1413       gdk_window_get_pointer (CANVAS (self), &x, &y, NULL);
1414       gdk_window_get_size (CANVAS (self), &width, &height);
1415       if (x < 0 || y < 0 || x >= width || y >= height)
1416         {
1417           /* fallback value if the pointer is outside the window */
1418           x = width / 3;
1419           y = height / 3;
1420         }
1421       tick = coord_to_tick (self, MAX (x, 0), FALSE);
1422       coord_to_note (self, MAX (y, 0), &info);
1423       semitone = info.valid_semitone;
1424       octave = info.valid_octave;
1425     }
1426   else
1427     {
1428       semitone = 6;
1429       octave = (MIN_OCTAVE (self) + MAX_OCTAVE (self)) / 2;
1430       tick = 0;
1431     }
1432   if (note_p)
1433     *note_p = SFI_NOTE_MAKE_VALID (SFI_NOTE_GENERIC (octave, semitone));
1434   if (tick_p)
1435     *tick_p = tick;
1436 }
1437
1438 void
1439 bst_piano_roll_set_marker (BstPianoRoll          *self,
1440                            guint                  mark_index,
1441                            guint                  position,
1442                            BstPianoRollMarkerType mtype)
1443 {
1444   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self);
1445   GxkScrollMarker *marker;
1446   guint count;
1447   g_return_if_fail (mark_index > 0);
1448
1449   marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
1450   if (!marker && !mtype)
1451     return;
1452   else if (!marker && mtype)
1453     {
1454       gxk_scroll_canvas_add_marker (scc, mark_index);
1455       marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, &count);
1456     }
1457   else if (marker && !mtype)
1458     {
1459       while (marker)
1460         {
1461           gxk_scroll_canvas_remove_marker (scc, marker);
1462           marker = gxk_scroll_canvas_lookup_marker (scc, mark_index, NULL);
1463         }
1464       return;
1465     }
1466
1467   g_return_if_fail (count == 1);
1468
1469   marker[0].coords.x = position;
1470   if (marker[0].mtype != mtype || !marker[0].pixmap)
1471     {
1472       marker[0].mtype = mtype;
1473       piano_roll_allocate_marker (self, marker);
1474     }
1475   else
1476     piano_roll_move_marker (self, marker);
1477 }
1478
1479 static void
1480 bst_piano_roll_class_init (BstPianoRollClass *class)
1481 {
1482   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1483   GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);
1484   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
1485   GxkScrollCanvasClass *scroll_canvas_class = GXK_SCROLL_CANVAS_CLASS (class);
1486   
1487   gobject_class->dispose = bst_piano_roll_dispose;
1488   gobject_class->finalize = bst_piano_roll_finalize;
1489   
1490   object_class->destroy = bst_piano_roll_destroy;
1491
1492   widget_class->map = bst_piano_roll_map;
1493
1494   scroll_canvas_class->hscrollable = TRUE;
1495   scroll_canvas_class->vscrollable = TRUE;
1496   scroll_canvas_class->get_layout = piano_roll_get_layout;
1497   scroll_canvas_class->reallocate_contents = piano_roll_reallocate_contents;
1498   scroll_canvas_class->draw_marker = bst_piano_roll_draw_marker;
1499   scroll_canvas_class->draw_canvas = bst_piano_roll_draw_canvas;
1500   scroll_canvas_class->draw_top_panel = bst_piano_roll_draw_hpanel;
1501   scroll_canvas_class->draw_left_panel = bst_piano_roll_draw_vpanel;
1502   scroll_canvas_class->update_adjustments = piano_roll_update_adjustments;
1503   scroll_canvas_class->adjustment_changed = piano_roll_adjustment_changed;
1504   scroll_canvas_class->handle_drag = piano_roll_handle_drag;
1505
1506   bst_skin_config_add_notify ((BstSkinConfigNotify) piano_roll_class_setup_skin, class);
1507   piano_roll_class_setup_skin (class);
1508   
1509   class->canvas_clicked = NULL;
1510   
1511   signal_canvas_drag = g_signal_new ("canvas-drag", G_OBJECT_CLASS_TYPE (class),
1512                                      G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPianoRollClass, canvas_drag),
1513                                      NULL, NULL,
1514                                      bst_marshal_NONE__POINTER,
1515                                      G_TYPE_NONE, 1, G_TYPE_POINTER);
1516   signal_canvas_clicked = g_signal_new ("canvas-clicked", G_OBJECT_CLASS_TYPE (class),
1517                                         G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPianoRollClass, canvas_clicked),
1518                                         NULL, NULL,
1519                                         bst_marshal_NONE__UINT_UINT_INT_BOXED,
1520                                         G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_INT,
1521                                         GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1522   signal_piano_drag = g_signal_new ("piano-drag", G_OBJECT_CLASS_TYPE (class),
1523                                     G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPianoRollClass, piano_drag),
1524                                     NULL, NULL,
1525                                     bst_marshal_NONE__POINTER,
1526                                     G_TYPE_NONE, 1, G_TYPE_POINTER);
1527   signal_piano_clicked = g_signal_new ("piano-clicked", G_OBJECT_CLASS_TYPE (class),
1528                                        G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (BstPianoRollClass, piano_clicked),
1529                                        NULL, NULL,
1530                                        bst_marshal_NONE__UINT_INT_BOXED,
1531                                        G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT,
1532                                        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1533 }