51778e12abf28f5f9c9967efbc652eb9f7ad1d6c
[stwbeast.git] / beast-gtk / bstpianorollctrl.c
1 /* BEAST - Better Audio System
2  * Copyright (C) 2002-2003 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 "bstpianorollctrl.h"
18 #include "bsteventrollctrl.h"
19
20
21 #define NOTE_LENGTH(self)       ((self)->note_rtools->action_id)
22 #define QUANTIZATION(self)      ((self)->quant_rtools->action_id)
23 #define HAVE_OBJECT             (1 << 31)
24
25
26 /* --- prototypes --- */
27 static gboolean bst_piano_roll_controller_check_action  (BstPianoRollController *self,
28                                                          gulong                  action_id,
29                                                          guint64                 action_stamp);
30 static void     bst_piano_roll_controller_exec_action   (BstPianoRollController *self,
31                                                          gulong                  action_id);
32 static void     controller_canvas_drag                  (BstPianoRollController *self,
33                                                          BstPianoRollDrag       *drag);
34 static void     controller_piano_drag                   (BstPianoRollController *self,
35                                                          BstPianoRollDrag       *drag);
36 static void     controller_update_canvas_cursor         (BstPianoRollController *self,
37                                                          BstCommonRollTool       tool);
38
39
40 /* --- variables --- */
41 static BsePartNoteSeq *clipboard_pseq = NULL;
42
43 /* --- actions --- */
44 enum {
45   ACTION_NONE           = BST_COMMON_ROLL_TOOL_LAST,
46   ACTION_SELECT_ALL,
47   ACTION_SELECT_NONE,
48   ACTION_SELECT_INVERT,
49 };
50
51 /* --- functions --- */
52 GxkActionList*
53 bst_piano_roll_controller_select_actions (BstPianoRollController *self)
54 {
55   GxkActionList *alist = gxk_action_list_create ();
56   static const GxkStockAction actions[] = {
57     { N_("All"),                "<ctrl>A",              N_("Select all notes"),
58       ACTION_SELECT_ALL,        BST_STOCK_SELECT_ALL },
59     { N_("None"),               "<shift><ctrl>A",       N_("Unselect all notes"),
60       ACTION_SELECT_NONE,       BST_STOCK_SELECT_NONE },
61     { N_("Invert"),             "<ctrl>I",              N_("Invert the current selection"),
62       ACTION_SELECT_INVERT,     BST_STOCK_SELECT_INVERT },
63   };
64   gxk_action_list_add_actions (alist, G_N_ELEMENTS (actions), actions,
65                                NULL /*i18n_domain*/,
66                                (GxkActionCheck) bst_piano_roll_controller_check_action,
67                                (GxkActionExec) bst_piano_roll_controller_exec_action,
68                                self);
69   return alist;
70 }
71
72 GxkActionList*
73 bst_piano_roll_controller_canvas_actions (BstPianoRollController *self)
74 {
75   GxkActionList *alist = gxk_action_list_create_grouped (self->canvas_rtools);
76   static const GxkStockAction actions[] = {
77     { N_("Insert"),           "I",    N_("Insert/resize/move notes (mouse button 1 and 2)"),
78       BST_COMMON_ROLL_TOOL_INSERT,   BST_STOCK_PART_TOOL },
79     { N_("Delete"),           "D",    N_("Delete note (mouse button 1)"),
80       BST_COMMON_ROLL_TOOL_DELETE,   BST_STOCK_TRASHCAN },
81     { N_("Align Events"),     "A",    N_("Draw a line to align events to"),
82       BST_COMMON_ROLL_TOOL_ALIGN,    BST_STOCK_EVENT_CONTROL },
83     { N_("Select"),           "S",    N_("Rectangle select notes"),
84       BST_COMMON_ROLL_TOOL_SELECT,   BST_STOCK_RECT_SELECT },
85     { N_("Vertical Select"),  "V",    N_("Select tick range vertically"),
86       BST_COMMON_ROLL_TOOL_VSELECT,  BST_STOCK_VERT_SELECT },
87   };
88   gxk_action_list_add_actions (alist, G_N_ELEMENTS (actions), actions,
89                                NULL /*i18n_domain*/, NULL /*acheck*/, NULL /*aexec*/, NULL);
90   return alist;
91 }
92
93 GxkActionList*
94 bst_piano_roll_controller_note_actions (BstPianoRollController *self)
95 {
96   GxkActionList *alist = gxk_action_list_create_grouped (self->note_rtools);
97   static const GxkStockAction actions[] = {
98     { N_("1\\/1"),              "1",    N_("Insert whole notes"),
99       1,                        BST_STOCK_NOTE_1, },
100     { N_("1\\/2"),              "2",    N_("Insert half notes"),
101       2,                        BST_STOCK_NOTE_2, },
102     { N_("1\\/4"),              "4",    N_("Insert quarter notes"),
103       4,                        BST_STOCK_NOTE_4, },
104     { N_("1\\/8"),              "8",    N_("Insert eighths note"),
105       8,                        BST_STOCK_NOTE_8, },
106     { N_("1\\/16"),             "6",    N_("Insert sixteenth note"),
107       16,                       BST_STOCK_NOTE_16, },
108     { N_("1\\/32"),             "3",    N_("Insert thirty-second note"),
109       32,                       BST_STOCK_NOTE_32, },
110     { N_("1\\/64"),             "5",    N_("Insert sixty-fourth note"),
111       64,                       BST_STOCK_NOTE_64, },
112     { N_("1\\/128"),            "7",    N_("Insert hundred twenty-eighth note"),
113       128,                      BST_STOCK_NOTE_128, },
114   };
115   gxk_action_list_add_actions (alist, G_N_ELEMENTS (actions), actions,
116                                NULL /*i18n_domain*/, NULL /*acheck*/, NULL /*aexec*/, NULL);
117   return alist;
118 }
119
120 GxkActionList*
121 bst_piano_roll_controller_quant_actions (BstPianoRollController *self)
122 {
123   GxkActionList *alist = gxk_action_list_create_grouped (self->quant_rtools);
124   static const GxkStockAction actions[] = {
125       { N_("Q: Tact"),          "<ctrl>T",      N_("Quantize to tact boundaries"),
126         BST_QUANTIZE_TACT,      BST_STOCK_QTACT, },
127       { N_("Q: None"),          "<ctrl>0",      N_("No quantization selected"),
128         BST_QUANTIZE_NONE,      BST_STOCK_QNOTE_NONE, },
129       { N_("Q: 1\\/1"),         "<ctrl>1",      N_("Quantize to whole note boundaries"),
130         BST_QUANTIZE_NOTE_1,    BST_STOCK_QNOTE_1, },
131       { N_("Q: 1\\/2"),         "<ctrl>2",      N_("Quantize to half note boundaries"),
132         BST_QUANTIZE_NOTE_2,    BST_STOCK_QNOTE_2, },
133       { N_("Q: 1\\/4"),         "<ctrl>4",      N_("Quantize to quarter note boundaries"),
134         BST_QUANTIZE_NOTE_4,    BST_STOCK_QNOTE_4, },
135       { N_("Q: 1\\/8"),         "<ctrl>8",      N_("Quantize to eighths note boundaries"),
136         BST_QUANTIZE_NOTE_8,    BST_STOCK_QNOTE_8, },
137       { N_("Q: 1\\/16"),        "<ctrl>6",      N_("Quantize to sixteenth note boundaries"),
138         BST_QUANTIZE_NOTE_16,   BST_STOCK_QNOTE_16, },
139       { N_("Q: 1\\/32"),        "<ctrl>3",      N_("Quantize to thirty-second note boundaries"),
140         BST_QUANTIZE_NOTE_32,   BST_STOCK_QNOTE_32, },
141       { N_("Q: 1\\/64"),        "<ctrl>5",      N_("Quantize to sixty-fourth note boundaries"),
142         BST_QUANTIZE_NOTE_64,   BST_STOCK_QNOTE_64, },
143       { N_("Q: 1\\/128"),       "<ctrl>7",      N_("Quantize to hundred twenty-eighth note boundaries"),
144         BST_QUANTIZE_NOTE_128,  BST_STOCK_QNOTE_128, },
145   };
146   gxk_action_list_add_actions (alist, G_N_ELEMENTS (actions), actions,
147                                NULL /*i18n_domain*/, NULL /*acheck*/, NULL /*aexec*/, NULL);
148   return alist;
149 }
150
151 void
152 bst_piano_roll_controller_set_clipboard (BsePartNoteSeq *pseq)
153 {
154   if (clipboard_pseq)
155     bse_part_note_seq_free (clipboard_pseq);
156   clipboard_pseq = pseq && pseq->n_pnotes ? bse_part_note_seq_copy_shallow (pseq) : NULL;
157   if (clipboard_pseq)
158     bst_event_roll_controller_set_clipboard (NULL);
159 }
160
161 BsePartNoteSeq*
162 bst_piano_roll_controller_get_clipboard (void)
163 {
164   return clipboard_pseq;
165 }
166
167 static void
168 controller_reset_canvas_cursor (BstPianoRollController *self)
169 {
170   controller_update_canvas_cursor (self, self->canvas_rtools->action_id);
171 }
172
173 BstPianoRollController*
174 bst_piano_roll_controller_new (BstPianoRoll *proll)
175 {
176   BstPianoRollController *self;
177
178   g_return_val_if_fail (BST_IS_PIANO_ROLL (proll), NULL);
179
180   self = g_new0 (BstPianoRollController, 1);
181   self->proll = proll;
182   self->ref_count = 1;
183
184   self->ref_count++;
185   g_signal_connect_data (proll, "canvas-drag",
186                          G_CALLBACK (controller_canvas_drag),
187                          self, (GClosureNotify) bst_piano_roll_controller_unref,
188                          G_CONNECT_SWAPPED);
189   g_signal_connect_data (proll, "piano-drag",
190                          G_CALLBACK (controller_piano_drag),
191                          self, NULL,
192                          G_CONNECT_SWAPPED);
193   /* canvas tools */
194   self->canvas_rtools = gxk_action_group_new ();
195   gxk_action_group_select (self->canvas_rtools, BST_COMMON_ROLL_TOOL_INSERT);
196   /* note length selection */
197   self->note_rtools = gxk_action_group_new ();
198   gxk_action_group_select (self->note_rtools, 4);
199   /* quantization selection */
200   self->quant_rtools = gxk_action_group_new ();
201   gxk_action_group_select (self->quant_rtools, BST_QUANTIZE_NOTE_8);
202   /* update from action group */
203   g_signal_connect_swapped (self->canvas_rtools, "changed",
204                             G_CALLBACK (controller_reset_canvas_cursor), self);
205   controller_reset_canvas_cursor (self);
206   return self;
207 }
208
209 BstPianoRollController*
210 bst_piano_roll_controller_ref (BstPianoRollController *self)
211 {
212   g_return_val_if_fail (self != NULL, NULL);
213   g_return_val_if_fail (self->ref_count >= 1, NULL);
214
215   self->ref_count++;
216
217   return self;
218 }
219
220 void
221 bst_piano_roll_controller_unref (BstPianoRollController *self)
222 {
223   g_return_if_fail (self != NULL);
224   g_return_if_fail (self->ref_count >= 1);
225
226   self->ref_count--;
227   if (!self->ref_count)
228     {
229       gxk_action_group_dispose (self->canvas_rtools);
230       g_object_unref (self->canvas_rtools);
231       gxk_action_group_dispose (self->note_rtools);
232       g_object_unref (self->note_rtools);
233       gxk_action_group_dispose (self->quant_rtools);
234       g_object_unref (self->quant_rtools);
235       g_free (self);
236     }
237 }
238
239 static gboolean
240 bst_piano_roll_controller_check_action (BstPianoRollController *self,
241                                         gulong                  action_id,
242                                         guint64                 action_stamp)
243 {
244   switch (action_id)
245     {
246     case ACTION_SELECT_ALL:
247       return TRUE;
248     case ACTION_SELECT_NONE:
249     case ACTION_SELECT_INVERT:
250       return bst_piano_roll_controller_has_selection (self, action_stamp);
251     }
252   return FALSE;
253 }
254
255 static void
256 bst_piano_roll_controller_exec_action (BstPianoRollController *self,
257                                        gulong                  action_id)
258 {
259   SfiProxy part = self->proll->proxy;
260   switch (action_id)
261     {
262       BsePartNoteSeq *pseq;
263       guint i;
264     case ACTION_SELECT_ALL:
265       bse_part_select_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
266       break;
267     case ACTION_SELECT_NONE:
268       bse_part_deselect_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
269       break;
270     case ACTION_SELECT_INVERT:
271       pseq = bse_part_list_selected_notes (part);
272       bse_part_select_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
273       for (i = 0; i < pseq->n_pnotes; i++)
274         {
275           BsePartNote *pnote = pseq->pnotes[i];
276           bse_part_deselect_event (part, pnote->id);
277         }
278       break;
279     }
280   gxk_widget_update_actions_downwards (self->proll);
281 }
282
283 static BstCommonRollTool
284 piano_canvas_button_tool (BstPianoRollController *self,
285                           guint                   button,
286                           guint                   have_object)
287 {
288   switch (self->canvas_rtools->action_id | /* user selected tool */
289           (have_object ? HAVE_OBJECT : 0))
290     {
291     case BST_COMMON_ROLL_TOOL_INSERT: /* background */
292       switch (button) {
293       case 1:  return BST_COMMON_ROLL_TOOL_INSERT;
294       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
295       default: return BST_COMMON_ROLL_TOOL_NONE;
296       }
297     case BST_COMMON_ROLL_TOOL_INSERT | HAVE_OBJECT:
298       switch (button) {
299       case 1:  return BST_COMMON_ROLL_TOOL_RESIZE;
300       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
301       default: return BST_COMMON_ROLL_TOOL_NONE;
302       }
303     case BST_COMMON_ROLL_TOOL_DELETE: /* background */
304       switch (button) {
305       case 1:  return BST_COMMON_ROLL_TOOL_DELETE;       /* user error */
306       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
307       default: return BST_COMMON_ROLL_TOOL_NONE;
308       }
309     case BST_COMMON_ROLL_TOOL_DELETE | HAVE_OBJECT:
310       switch (button) {
311       case 1:  return BST_COMMON_ROLL_TOOL_DELETE;
312       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
313       default: return BST_COMMON_ROLL_TOOL_NONE;
314       }
315     case BST_COMMON_ROLL_TOOL_ALIGN: /* background */
316       switch (button) {
317       case 1:  return BST_COMMON_ROLL_TOOL_ALIGN;
318       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
319       default: return BST_COMMON_ROLL_TOOL_NONE;
320       }
321     case BST_COMMON_ROLL_TOOL_ALIGN | HAVE_OBJECT:
322       switch (button) {
323       case 1:  return BST_COMMON_ROLL_TOOL_ALIGN;
324       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
325       default: return BST_COMMON_ROLL_TOOL_NONE;
326       }
327     case BST_COMMON_ROLL_TOOL_SELECT: /* background */
328       switch (button) {
329       case 1:  return BST_COMMON_ROLL_TOOL_SELECT;
330       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
331       default: return BST_COMMON_ROLL_TOOL_NONE;
332       }
333     case BST_COMMON_ROLL_TOOL_SELECT | HAVE_OBJECT:
334       switch (button) {
335       case 1:  return BST_COMMON_ROLL_TOOL_SELECT;
336       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
337       default: return BST_COMMON_ROLL_TOOL_NONE;
338       }
339     case BST_COMMON_ROLL_TOOL_VSELECT: /* background */
340       switch (button) {
341       case 1:  return BST_COMMON_ROLL_TOOL_VSELECT;
342       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
343       default: return BST_COMMON_ROLL_TOOL_NONE;
344       }
345     case BST_COMMON_ROLL_TOOL_VSELECT | HAVE_OBJECT:
346       switch (button) {
347       case 1:  return BST_COMMON_ROLL_TOOL_VSELECT;
348       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
349       default: return BST_COMMON_ROLL_TOOL_NONE;
350       }
351     }
352   return BST_COMMON_ROLL_TOOL_NONE;
353 }
354
355 void
356 bst_piano_roll_controller_clear (BstPianoRollController *self)
357 {
358   BsePartNoteSeq *pseq;
359   SfiProxy proxy;
360   guint i;
361
362   g_return_if_fail (self != NULL);
363
364   proxy = self->proll->proxy;
365   pseq = bse_part_list_selected_notes (proxy);
366   bse_item_group_undo (proxy, "Clear Selection");
367   for (i = 0; i < pseq->n_pnotes; i++)
368     {
369       BsePartNote *pnote = pseq->pnotes[i];
370       bse_part_delete_event (proxy, pnote->id);
371     }
372   bse_item_ungroup_undo (proxy);
373 }
374
375 void
376 bst_piano_roll_controller_cut (BstPianoRollController *self)
377 {
378   BsePartNoteSeq *pseq;
379   SfiProxy proxy;
380   guint i;
381
382   g_return_if_fail (self != NULL);
383
384   proxy = self->proll->proxy;
385   pseq = bse_part_list_selected_notes (proxy);
386   bse_item_group_undo (proxy, "Cut Selection");
387   for (i = 0; i < pseq->n_pnotes; i++)
388     {
389       BsePartNote *pnote = pseq->pnotes[i];
390       bse_part_delete_event (proxy, pnote->id);
391     }
392   bst_piano_roll_controller_set_clipboard (pseq);
393   bse_item_ungroup_undo (proxy);
394 }
395
396 gboolean
397 bst_piano_roll_controller_copy (BstPianoRollController *self)
398 {
399   BsePartNoteSeq *pseq;
400   SfiProxy proxy;
401
402   g_return_val_if_fail (self != NULL, FALSE);
403
404   proxy = self->proll->proxy;
405   pseq = bse_part_list_selected_notes (proxy);
406   bst_piano_roll_controller_set_clipboard (pseq);
407   return pseq && pseq->n_pnotes;
408 }
409
410 void
411 bst_piano_roll_controller_paste (BstPianoRollController *self)
412 {
413   BsePartNoteSeq *pseq;
414   SfiProxy proxy;
415
416   g_return_if_fail (self != NULL);
417
418   proxy = self->proll->proxy;
419   pseq = bst_piano_roll_controller_get_clipboard ();
420   if (pseq)
421     {
422       guint i, paste_tick, ctick = self->proll->max_ticks;
423       gint cnote = 0;
424       gint paste_note;
425       bse_item_group_undo (proxy, "Paste Clipboard");
426       bse_part_deselect_notes (proxy, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
427       bst_piano_roll_get_paste_pos (self->proll, &paste_tick, &paste_note);
428       paste_tick = bst_piano_roll_controller_quantize (self, paste_tick);
429       for (i = 0; i < pseq->n_pnotes; i++)
430         {
431           BsePartNote *pnote = pseq->pnotes[i];
432           ctick = MIN (ctick, pnote->tick);
433           cnote = MAX (cnote, pnote->note);
434         }
435       cnote = paste_note - cnote;
436       for (i = 0; i < pseq->n_pnotes; i++)
437         {
438           BsePartNote *pnote = pseq->pnotes[i];
439           guint id;
440           gint note;
441           note = pnote->note + cnote;
442           if (note >= 0)
443             {
444               id = bse_part_insert_note_auto (proxy,
445                                               pnote->tick - ctick + paste_tick,
446                                               pnote->duration,
447                                               note,
448                                               pnote->fine_tune,
449                                               pnote->velocity);
450               bse_part_select_event (proxy, id);
451             }
452         }
453       bse_item_ungroup_undo (proxy);
454     }
455 }
456
457 gboolean
458 bst_piano_roll_controller_clipboard_full (BstPianoRollController *self)
459 {
460   BsePartNoteSeq *pseq = bst_piano_roll_controller_get_clipboard ();
461   return pseq && pseq->n_pnotes;
462 }
463
464 gboolean
465 bst_piano_roll_controller_has_selection (BstPianoRollController *self,
466                                          guint64                 action_stamp)
467 {
468   if (self->cached_stamp != action_stamp)
469     {
470       SfiProxy part = self->proll->proxy;
471       if (part)
472         {
473           self->cached_stamp = action_stamp;
474           BsePartNoteSeq *pseq = bse_part_list_selected_notes (part);
475           self->cached_n_notes = pseq->n_pnotes;
476         }
477       else
478         self->cached_n_notes = 0;
479     }
480   return self->cached_n_notes > 0;
481 }
482
483 guint
484 bst_piano_roll_controller_quantize (BstPianoRollController *self,
485                                     guint                   fine_tick)
486 {
487   BseSongTiming *timing;
488   guint quant, tick, qtick;
489   g_return_val_if_fail (self != NULL, fine_tick);
490
491   timing = bse_part_get_timing (self->proll->proxy, fine_tick);
492   if (QUANTIZATION (self) == BST_QUANTIZE_NONE)
493     quant = 1;
494   else if (QUANTIZATION (self) == BST_QUANTIZE_TACT)
495     quant = timing->tpt;
496   else
497     quant = timing->tpqn * 4 / QUANTIZATION (self);
498   tick = fine_tick - timing->tick;
499   qtick = tick / quant;
500   qtick *= quant;
501   if (tick - qtick > quant / 2)
502     qtick += quant;
503   tick = timing->tick + qtick;
504   return tick;
505 }
506
507 static void
508 controller_update_canvas_cursor (BstPianoRollController *self,
509                                  BstCommonRollTool      tool)
510 {
511   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self->proll);
512   switch (tool)
513     {
514     case BST_COMMON_ROLL_TOOL_INSERT:
515       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_PENCIL);
516       break;
517     case BST_COMMON_ROLL_TOOL_RESIZE:
518       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_SB_H_DOUBLE_ARROW);
519       break;
520     case BST_COMMON_ROLL_TOOL_MOVE:
521       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_FLEUR);
522       break;
523     case BST_COMMON_ROLL_TOOL_DELETE:
524       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_TARGET);
525       break;
526     case BST_COMMON_ROLL_TOOL_SELECT:
527       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_CROSSHAIR);
528       break;
529     case BST_COMMON_ROLL_TOOL_VSELECT:
530       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_LEFT_SIDE);
531       break;
532     default:
533       gxk_scroll_canvas_set_canvas_cursor (scc, GXK_DEFAULT_CURSOR);
534       break;
535     }
536 }
537
538 static gboolean
539 check_hoverlap (SfiProxy part,
540                 guint    tick,
541                 guint    duration,
542                 gint     note,
543                 guint    except_tick,
544                 guint    except_duration)
545 {
546   if (duration)
547     {
548       BsePartNoteSeq *pseq = bse_part_check_overlap (part, tick, duration, note);
549       BsePartNote *pnote;
550       
551       if (pseq->n_pnotes == 0)
552         return FALSE;     /* no overlap */
553       if (pseq->n_pnotes > 1)
554         return TRUE;      /* definite overlap */
555       pnote = pseq->pnotes[0];
556       if (pnote->tick == except_tick &&
557           pnote->duration == except_duration)
558         return FALSE;     /* overlaps with exception */
559     }
560   return TRUE;
561 }
562
563 static void
564 move_start (BstPianoRollController *self,
565             BstPianoRollDrag       *drag)
566 {
567   SfiProxy part = self->proll->proxy;
568   if (self->obj_id)     /* got note to move */
569     {
570       self->xoffset = drag->start_tick - self->obj_tick;        /* drag offset */
571       controller_update_canvas_cursor (self, BST_COMMON_ROLL_TOOL_MOVE);
572       gxk_status_set (GXK_STATUS_WAIT, _("Move Note"), NULL);
573       drag->state = GXK_DRAG_CONTINUE;
574       if (bse_part_is_event_selected (part, self->obj_id))
575         self->sel_pseq = bse_part_note_seq_copy_shallow (bse_part_list_selected_notes (part));
576     }
577   else
578     {
579       gxk_status_set (GXK_STATUS_ERROR, _("Move Note"), _("No target"));
580       drag->state = GXK_DRAG_HANDLED;
581     }
582 }
583
584 static void
585 move_group_motion (BstPianoRollController *self,
586                    BstPianoRollDrag       *drag)
587 {
588   SfiProxy part = self->proll->proxy;
589   gint i, new_tick, old_note, new_note, delta_tick, delta_note;
590
591   new_tick = MAX (drag->current_tick, self->xoffset) - self->xoffset;
592   new_tick = bst_piano_roll_controller_quantize (self, new_tick);
593   old_note = self->obj_note;
594   new_note = drag->current_note;
595   delta_tick = self->obj_tick;
596   delta_note = old_note;
597   delta_tick -= new_tick;
598   delta_note -= new_note;
599   bse_item_group_undo (part, "Move Selection");
600   for (i = 0; i < self->sel_pseq->n_pnotes; i++)
601     {
602       BsePartNote *pnote = self->sel_pseq->pnotes[i];
603       gint tick = pnote->tick;
604       gint note = pnote->note;
605       note -= delta_note;
606       bse_part_change_note (part, pnote->id,
607                             MAX (tick - delta_tick, 0),
608                             pnote->duration,
609                             SFI_NOTE_CLAMP (note),
610                             pnote->fine_tune,
611                             pnote->velocity);
612     }
613   if (drag->type == GXK_DRAG_DONE)
614     {
615       bse_part_note_seq_free (self->sel_pseq);
616       self->sel_pseq = NULL;
617     }
618   bse_item_ungroup_undo (part);
619 }
620
621 static void
622 move_motion (BstPianoRollController *self,
623              BstPianoRollDrag       *drag)
624 {
625   SfiProxy part = self->proll->proxy;
626   gint new_tick;
627   gboolean note_changed;
628
629   if (self->sel_pseq)
630     {
631       move_group_motion (self, drag);
632       return;
633     }
634
635   new_tick = MAX (drag->current_tick, self->xoffset) - self->xoffset;
636   new_tick = bst_piano_roll_controller_quantize (self, new_tick);
637   note_changed = self->obj_note != drag->current_note;
638   if ((new_tick != self->obj_tick || note_changed) &&
639       !check_hoverlap (part, new_tick, self->obj_duration, drag->current_note,
640                        self->obj_tick, note_changed ? 0 : self->obj_duration))
641     {
642       bse_item_group_undo (part, "Move Note");
643       if (bse_part_delete_event (part, self->obj_id) != BSE_ERROR_NONE)
644         drag->state = GXK_DRAG_ERROR;
645       else
646         {
647           self->obj_id = bse_part_insert_note_auto (part, new_tick, self->obj_duration,
648                                                     drag->current_note, self->obj_fine_tune, self->obj_velocity);
649           self->obj_tick = new_tick;
650           self->obj_note = drag->current_note;
651           if (!self->obj_id)
652             drag->state = GXK_DRAG_ERROR;
653         }
654       bse_item_ungroup_undo (part);
655     }
656 }
657
658 static void
659 move_abort (BstPianoRollController *self,
660             BstPianoRollDrag       *drag)
661 {
662   if (self->sel_pseq)
663     {
664       bse_part_note_seq_free (self->sel_pseq);
665       self->sel_pseq = NULL;
666     }
667   gxk_status_set (GXK_STATUS_ERROR, _("Move Note"), _("Lost Note"));
668 }
669
670 static void
671 resize_start (BstPianoRollController *self,
672               BstPianoRollDrag       *drag)
673 {
674   if (self->obj_id)     /* got note for resize */
675     {
676       guint bound = self->obj_tick + self->obj_duration + 1;
677
678       /* set the fix-point (either note start or note end) */
679       if (drag->start_tick - self->obj_tick <= bound - drag->start_tick)
680         self->tick_bound = bound;
681       else
682         self->tick_bound = self->obj_tick;
683       controller_update_canvas_cursor (self, BST_COMMON_ROLL_TOOL_RESIZE);
684       gxk_status_set (GXK_STATUS_WAIT, _("Resize Note"), NULL);
685       drag->state = GXK_DRAG_CONTINUE;
686     }
687   else
688     {
689       gxk_status_set (GXK_STATUS_ERROR, _("Resize Note"), _("No target"));
690       drag->state = GXK_DRAG_HANDLED;
691     }
692 }
693
694 static void
695 resize_motion (BstPianoRollController *self,
696                BstPianoRollDrag       *drag)
697 {
698   SfiProxy part = self->proll->proxy;
699   guint new_bound, new_tick, new_duration;
700
701   /* calc new note around fix-point */
702   new_tick = bst_piano_roll_controller_quantize (self, drag->current_tick);
703   new_bound = MAX (new_tick, self->tick_bound);
704   new_tick = MIN (new_tick, self->tick_bound);
705   new_duration = new_bound - new_tick;
706   new_duration = MAX (new_duration, 1) - 1;
707
708   /* apply new note size */
709   if ((self->obj_tick != new_tick || new_duration != self->obj_duration) &&
710       !check_hoverlap (part, new_tick, new_duration, self->obj_note,
711                        self->obj_tick, self->obj_duration))
712     {
713       bse_item_group_undo (part, "Resize Note");
714       if (self->obj_id)
715         {
716           BseErrorType error = bse_part_delete_event (part, self->obj_id);
717           if (error)
718             drag->state = GXK_DRAG_ERROR;
719           self->obj_id = 0;
720         }
721       if (new_duration && drag->state != GXK_DRAG_ERROR)
722         {
723           self->obj_id = bse_part_insert_note_auto (part, new_tick, new_duration,
724                                                     self->obj_note, self->obj_fine_tune, self->obj_velocity);
725           self->obj_tick = new_tick;
726           self->obj_duration = new_duration;
727           if (!self->obj_id)
728             drag->state = GXK_DRAG_ERROR;
729         }
730       bse_item_ungroup_undo (part);
731     }
732 }
733
734 static void
735 resize_abort (BstPianoRollController *self,
736               BstPianoRollDrag       *drag)
737 {
738   gxk_status_set (GXK_STATUS_ERROR, _("Resize Note"), _("Lost Note"));
739 }
740
741 static void
742 delete_start (BstPianoRollController *self,
743               BstPianoRollDrag       *drag)
744 {
745   SfiProxy part = self->proll->proxy;
746   if (self->obj_id)     /* got note to delete */
747     {
748       BseErrorType error = bse_part_delete_event (part, self->obj_id);
749       bst_status_eprintf (error, _("Delete Note"));
750     }
751   else
752     gxk_status_set (GXK_STATUS_ERROR, _("Delete Note"), _("No target"));
753   drag->state = GXK_DRAG_HANDLED;
754 }
755
756 static void
757 insert_start (BstPianoRollController *self,
758               BstPianoRollDrag       *drag)
759 {
760   SfiProxy part = self->proll->proxy;
761   BseErrorType error = BSE_ERROR_NO_TARGET;
762   if (drag->start_valid)
763     {
764       guint qtick = bst_piano_roll_controller_quantize (self, drag->start_tick);
765       guint duration = drag->proll->tpqn * 4 / NOTE_LENGTH (self);
766       if (check_hoverlap (part, qtick, duration, drag->start_note, 0, 0))
767         error = BSE_ERROR_INVALID_OVERLAP;
768       else
769         {
770           bse_part_insert_note_auto (part, qtick, duration, drag->start_note, 0, 1.0);
771           error = BSE_ERROR_NONE;
772         }
773     }
774   bst_status_eprintf (error, _("Insert Note"));
775   drag->state = GXK_DRAG_HANDLED;
776 }
777
778 static void
779 select_start (BstPianoRollController *self,
780               BstPianoRollDrag       *drag)
781 {
782   drag->start_tick = bst_piano_roll_controller_quantize (self, drag->start_tick);
783   bst_piano_roll_set_view_selection (drag->proll, drag->start_tick, 0, 0, 0);
784   gxk_status_set (GXK_STATUS_WAIT, _("Select Region"), NULL);
785   drag->state = GXK_DRAG_CONTINUE;
786 }
787
788 static void
789 select_motion (BstPianoRollController *self,
790                BstPianoRollDrag       *drag)
791 {
792   SfiProxy part = self->proll->proxy;
793   guint start_tick = MIN (drag->start_tick, drag->current_tick);
794   guint end_tick = MAX (drag->start_tick, drag->current_tick);
795   gint min_note = MIN (drag->start_note, drag->current_note);
796   gint max_note = MAX (drag->start_note, drag->current_note);
797
798   bst_piano_roll_set_view_selection (drag->proll, start_tick, end_tick - start_tick, min_note, max_note);
799   if (drag->type == GXK_DRAG_DONE)
800     {
801       bse_part_select_notes_exclusive (part, start_tick, end_tick - start_tick, min_note, max_note);
802       bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
803     }
804 }
805
806 static void
807 select_abort (BstPianoRollController *self,
808               BstPianoRollDrag       *drag)
809 {
810   gxk_status_set (GXK_STATUS_ERROR, _("Select Region"), _("Aborted"));
811   bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
812 }
813
814 static void
815 vselect_start (BstPianoRollController *self,
816                BstPianoRollDrag       *drag)
817 {
818   drag->start_tick = bst_piano_roll_controller_quantize (self, drag->start_tick);
819   bst_piano_roll_set_view_selection (drag->proll, drag->start_tick, 0, drag->proll->min_note, drag->proll->max_note);
820   gxk_status_set (GXK_STATUS_WAIT, _("Vertical Select"), NULL);
821   drag->state = GXK_DRAG_CONTINUE;
822 }
823
824 static void
825 vselect_motion (BstPianoRollController *self,
826                 BstPianoRollDrag       *drag)
827 {
828   SfiProxy part = self->proll->proxy;
829   guint start_tick = MIN (drag->start_tick, drag->current_tick);
830   guint end_tick = MAX (drag->start_tick, drag->current_tick);
831
832   bst_piano_roll_set_view_selection (drag->proll, start_tick, end_tick - start_tick,
833                                      drag->proll->min_note, drag->proll->max_note);
834   if (drag->type == GXK_DRAG_DONE)
835     {
836       bse_part_select_notes_exclusive (part, start_tick, end_tick - start_tick,
837                                        drag->proll->min_note, drag->proll->max_note);
838       bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
839     }
840 }
841
842 static void
843 vselect_abort (BstPianoRollController *self,
844                BstPianoRollDrag       *drag)
845 {
846   gxk_status_set (GXK_STATUS_ERROR, _("Vertical Region"), _("Aborted"));
847   bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
848 }
849
850 #if 0
851 static void
852 generic_abort (BstPianoRollController *self,
853                BstPianoRollDrag       *drag)
854 {
855   gxk_status_set (GXK_STATUS_ERROR, _("Abortion"), NULL);
856 }
857 #endif
858
859 typedef void (*DragFunc) (BstPianoRollController *,
860                           BstPianoRollDrag       *);
861
862 void
863 controller_canvas_drag (BstPianoRollController *self,
864                         BstPianoRollDrag       *drag)
865 {
866   static struct {
867     BstCommonRollTool tool;
868     DragFunc start, motion, abort;
869   } tool_table[] = {
870     { BST_COMMON_ROLL_TOOL_INSERT,      insert_start,   NULL,           NULL,           },
871     { BST_COMMON_ROLL_TOOL_ALIGN,       insert_start,   NULL,           NULL,           },
872     { BST_COMMON_ROLL_TOOL_RESIZE,      resize_start,   resize_motion,  resize_abort,   },
873     { BST_COMMON_ROLL_TOOL_MOVE,        move_start,     move_motion,    move_abort,     },
874     { BST_COMMON_ROLL_TOOL_DELETE,      delete_start,   NULL,           NULL,           },
875     { BST_COMMON_ROLL_TOOL_SELECT,      select_start,   select_motion,  select_abort,   },
876     { BST_COMMON_ROLL_TOOL_VSELECT,     vselect_start,  vselect_motion, vselect_abort,  },
877   };
878   guint i;
879
880   if (drag->type == GXK_DRAG_START)
881     {
882       BstCommonRollTool tool = BST_COMMON_ROLL_TOOL_NONE;
883       BsePartNoteSeq *pseq;
884
885       /* setup drag data */
886       pseq = bse_part_get_notes (drag->proll->proxy, drag->start_tick, drag->start_note);
887       if (pseq->n_pnotes)
888         {
889           BsePartNote *pnote = pseq->pnotes[0];
890           self->obj_id = pnote->id;
891           self->obj_tick = pnote->tick;
892           self->obj_duration = pnote->duration;
893           self->obj_note = pnote->note;
894           self->obj_fine_tune = pnote->fine_tune;
895           self->obj_velocity = pnote->velocity;
896         }
897       else
898         {
899           self->obj_id = 0;
900           self->obj_tick = 0;
901           self->obj_duration = 0;
902           self->obj_note = 0;
903           self->obj_fine_tune = 0;
904           self->obj_velocity = 0;
905         }
906       if (self->sel_pseq)
907         g_warning ("leaking old drag selection (%p)", self->sel_pseq);
908       self->sel_pseq = NULL;
909       self->xoffset = 0;
910       self->tick_bound = 0;
911
912       /* find drag tool */
913       tool = piano_canvas_button_tool (self, drag->button, self->obj_id > 0);
914       for (i = 0; i < G_N_ELEMENTS (tool_table); i++)
915         if (tool_table[i].tool == tool)
916           break;
917       self->tool_index = i;
918       if (i >= G_N_ELEMENTS (tool_table))
919         return;         /* unhandled */
920     }
921   i = self->tool_index;
922   g_return_if_fail (i < G_N_ELEMENTS (tool_table));
923   switch (drag->type)
924     {
925     case GXK_DRAG_START:
926       if (tool_table[i].start)
927         tool_table[i].start (self, drag);
928       break;
929     case GXK_DRAG_MOTION:
930     case GXK_DRAG_DONE:
931       if (tool_table[i].motion)
932         tool_table[i].motion (self, drag);
933       break;
934     case GXK_DRAG_ABORT:
935       if (tool_table[i].abort)
936         tool_table[i].abort (self, drag);
937       break;
938     }
939   if (drag->type == GXK_DRAG_DONE ||
940       drag->type == GXK_DRAG_ABORT)
941     controller_reset_canvas_cursor (self);
942 }
943
944 void
945 controller_piano_drag (BstPianoRollController *self,
946                        BstPianoRollDrag       *drag)
947 {
948   SfiProxy part = self->proll->proxy;
949   SfiProxy song = bse_item_get_parent (part);
950   SfiProxy project = song ? bse_item_get_parent (song) : 0;
951   SfiProxy track = song ? bse_song_find_track_for_part (song, part) : 0;
952
953   // g_printerr ("piano drag event, note=%d (valid=%d)", drag->current_note, drag->current_valid);
954
955   if (project && track)
956     {
957       if (drag->type == GXK_DRAG_START ||
958           (drag->type == GXK_DRAG_MOTION &&
959            self->obj_note != drag->current_note))
960         {
961           BseErrorType error;
962           bse_project_auto_deactivate (project, 5 * 1000);
963           error = bse_project_activate (project);
964           self->obj_note = drag->current_note;
965           if (error == BSE_ERROR_NONE)
966             bse_song_synthesize_note (song, track, 384 * 4, self->obj_note, 0, 1.0);
967           bst_status_eprintf (error, _("Play note"));
968           drag->state = GXK_DRAG_CONTINUE;
969         }
970     }
971
972   if (drag->type == GXK_DRAG_START ||
973       drag->type == GXK_DRAG_MOTION)
974     drag->state = GXK_DRAG_CONTINUE;
975 }