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