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