GUI: use quantization setting to choose grid to draw
[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 static void
174 controller_change_quantization (BstPianoRollController *self)
175 {
176   bst_piano_roll_set_quant (self->proll, QUANTIZATION (self));
177 }
178
179
180 BstPianoRollController*
181 bst_piano_roll_controller_new (BstPianoRoll *proll)
182 {
183   BstPianoRollController *self;
184
185   g_return_val_if_fail (BST_IS_PIANO_ROLL (proll), NULL);
186
187   self = g_new0 (BstPianoRollController, 1);
188   self->proll = proll;
189   self->ref_count = 1;
190
191   self->ref_count++;
192   g_signal_connect_data (proll, "canvas-drag",
193                          G_CALLBACK (controller_canvas_drag),
194                          self, (GClosureNotify) bst_piano_roll_controller_unref,
195                          G_CONNECT_SWAPPED);
196   g_signal_connect_data (proll, "piano-drag",
197                          G_CALLBACK (controller_piano_drag),
198                          self, NULL,
199                          G_CONNECT_SWAPPED);
200   /* canvas tools */
201   self->canvas_rtools = gxk_action_group_new ();
202   gxk_action_group_select (self->canvas_rtools, BST_COMMON_ROLL_TOOL_INSERT);
203   /* note length selection */
204   self->note_rtools = gxk_action_group_new ();
205   gxk_action_group_select (self->note_rtools, 4);
206   /* quantization selection */
207   self->quant_rtools = gxk_action_group_new ();
208   gxk_action_group_select (self->quant_rtools, BST_QUANTIZE_NOTE_8);
209   g_signal_connect_swapped (self->quant_rtools, "changed",
210                             G_CALLBACK (controller_change_quantization), self);
211   controller_change_quantization (self);
212   /* update from action group */
213   g_signal_connect_swapped (self->canvas_rtools, "changed",
214                             G_CALLBACK (controller_reset_canvas_cursor), self);
215   controller_reset_canvas_cursor (self);
216   return self;
217 }
218
219 BstPianoRollController*
220 bst_piano_roll_controller_ref (BstPianoRollController *self)
221 {
222   g_return_val_if_fail (self != NULL, NULL);
223   g_return_val_if_fail (self->ref_count >= 1, NULL);
224
225   self->ref_count++;
226
227   return self;
228 }
229
230 void
231 bst_piano_roll_controller_unref (BstPianoRollController *self)
232 {
233   g_return_if_fail (self != NULL);
234   g_return_if_fail (self->ref_count >= 1);
235
236   self->ref_count--;
237   if (!self->ref_count)
238     {
239       gxk_action_group_dispose (self->canvas_rtools);
240       g_object_unref (self->canvas_rtools);
241       gxk_action_group_dispose (self->note_rtools);
242       g_object_unref (self->note_rtools);
243       gxk_action_group_dispose (self->quant_rtools);
244       g_object_unref (self->quant_rtools);
245       g_free (self);
246     }
247 }
248
249 static gboolean
250 bst_piano_roll_controller_check_action (BstPianoRollController *self,
251                                         gulong                  action_id,
252                                         guint64                 action_stamp)
253 {
254   switch (action_id)
255     {
256     case ACTION_SELECT_ALL:
257       return TRUE;
258     case ACTION_SELECT_NONE:
259     case ACTION_SELECT_INVERT:
260       return bst_piano_roll_controller_has_selection (self, action_stamp);
261     }
262   return FALSE;
263 }
264
265 static void
266 bst_piano_roll_controller_exec_action (BstPianoRollController *self,
267                                        gulong                  action_id)
268 {
269   SfiProxy part = self->proll->proxy;
270   switch (action_id)
271     {
272       BsePartNoteSeq *pseq;
273       guint i;
274     case ACTION_SELECT_ALL:
275       bse_part_select_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
276       break;
277     case ACTION_SELECT_NONE:
278       bse_part_deselect_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
279       break;
280     case ACTION_SELECT_INVERT:
281       pseq = bse_part_list_selected_notes (part);
282       bse_part_select_notes (part, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
283       for (i = 0; i < pseq->n_pnotes; i++)
284         {
285           BsePartNote *pnote = pseq->pnotes[i];
286           bse_part_deselect_event (part, pnote->id);
287         }
288       break;
289     }
290   gxk_widget_update_actions_downwards (self->proll);
291 }
292
293 static BstCommonRollTool
294 piano_canvas_button_tool (BstPianoRollController *self,
295                           guint                   button,
296                           guint                   have_object)
297 {
298   switch (self->canvas_rtools->action_id | /* user selected tool */
299           (have_object ? HAVE_OBJECT : 0))
300     {
301     case BST_COMMON_ROLL_TOOL_INSERT: /* background */
302       switch (button) {
303       case 1:  return BST_COMMON_ROLL_TOOL_INSERT;
304       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
305       default: return BST_COMMON_ROLL_TOOL_NONE;
306       }
307     case BST_COMMON_ROLL_TOOL_INSERT | HAVE_OBJECT:
308       switch (button) {
309       case 1:  return BST_COMMON_ROLL_TOOL_RESIZE;
310       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
311       default: return BST_COMMON_ROLL_TOOL_NONE;
312       }
313     case BST_COMMON_ROLL_TOOL_DELETE: /* background */
314       switch (button) {
315       case 1:  return BST_COMMON_ROLL_TOOL_DELETE;       /* user error */
316       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
317       default: return BST_COMMON_ROLL_TOOL_NONE;
318       }
319     case BST_COMMON_ROLL_TOOL_DELETE | HAVE_OBJECT:
320       switch (button) {
321       case 1:  return BST_COMMON_ROLL_TOOL_DELETE;
322       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
323       default: return BST_COMMON_ROLL_TOOL_NONE;
324       }
325     case BST_COMMON_ROLL_TOOL_ALIGN: /* background */
326       switch (button) {
327       case 1:  return BST_COMMON_ROLL_TOOL_ALIGN;
328       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
329       default: return BST_COMMON_ROLL_TOOL_NONE;
330       }
331     case BST_COMMON_ROLL_TOOL_ALIGN | HAVE_OBJECT:
332       switch (button) {
333       case 1:  return BST_COMMON_ROLL_TOOL_ALIGN;
334       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
335       default: return BST_COMMON_ROLL_TOOL_NONE;
336       }
337     case BST_COMMON_ROLL_TOOL_SELECT: /* background */
338       switch (button) {
339       case 1:  return BST_COMMON_ROLL_TOOL_SELECT;
340       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
341       default: return BST_COMMON_ROLL_TOOL_NONE;
342       }
343     case BST_COMMON_ROLL_TOOL_SELECT | HAVE_OBJECT:
344       switch (button) {
345       case 1:  return BST_COMMON_ROLL_TOOL_SELECT;
346       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
347       default: return BST_COMMON_ROLL_TOOL_NONE;
348       }
349     case BST_COMMON_ROLL_TOOL_VSELECT: /* background */
350       switch (button) {
351       case 1:  return BST_COMMON_ROLL_TOOL_VSELECT;
352       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;         /* user error */
353       default: return BST_COMMON_ROLL_TOOL_NONE;
354       }
355     case BST_COMMON_ROLL_TOOL_VSELECT | HAVE_OBJECT:
356       switch (button) {
357       case 1:  return BST_COMMON_ROLL_TOOL_VSELECT;
358       case 2:  return BST_COMMON_ROLL_TOOL_MOVE;
359       default: return BST_COMMON_ROLL_TOOL_NONE;
360       }
361     }
362   return BST_COMMON_ROLL_TOOL_NONE;
363 }
364
365 void
366 bst_piano_roll_controller_clear (BstPianoRollController *self)
367 {
368   BsePartNoteSeq *pseq;
369   SfiProxy proxy;
370   guint i;
371
372   g_return_if_fail (self != NULL);
373
374   proxy = self->proll->proxy;
375   pseq = bse_part_list_selected_notes (proxy);
376   bse_item_group_undo (proxy, "Clear Selection");
377   for (i = 0; i < pseq->n_pnotes; i++)
378     {
379       BsePartNote *pnote = pseq->pnotes[i];
380       bse_part_delete_event (proxy, pnote->id);
381     }
382   bse_item_ungroup_undo (proxy);
383 }
384
385 void
386 bst_piano_roll_controller_cut (BstPianoRollController *self)
387 {
388   BsePartNoteSeq *pseq;
389   SfiProxy proxy;
390   guint i;
391
392   g_return_if_fail (self != NULL);
393
394   proxy = self->proll->proxy;
395   pseq = bse_part_list_selected_notes (proxy);
396   bse_item_group_undo (proxy, "Cut Selection");
397   for (i = 0; i < pseq->n_pnotes; i++)
398     {
399       BsePartNote *pnote = pseq->pnotes[i];
400       bse_part_delete_event (proxy, pnote->id);
401     }
402   bst_piano_roll_controller_set_clipboard (pseq);
403   bse_item_ungroup_undo (proxy);
404 }
405
406 gboolean
407 bst_piano_roll_controller_copy (BstPianoRollController *self)
408 {
409   BsePartNoteSeq *pseq;
410   SfiProxy proxy;
411
412   g_return_val_if_fail (self != NULL, FALSE);
413
414   proxy = self->proll->proxy;
415   pseq = bse_part_list_selected_notes (proxy);
416   bst_piano_roll_controller_set_clipboard (pseq);
417   return pseq && pseq->n_pnotes;
418 }
419
420 void
421 bst_piano_roll_controller_paste (BstPianoRollController *self)
422 {
423   BsePartNoteSeq *pseq;
424   SfiProxy proxy;
425
426   g_return_if_fail (self != NULL);
427
428   proxy = self->proll->proxy;
429   pseq = bst_piano_roll_controller_get_clipboard ();
430   if (pseq)
431     {
432       guint i, paste_tick, ctick = self->proll->max_ticks;
433       gint cnote = 0;
434       gint paste_note;
435       bse_item_group_undo (proxy, "Paste Clipboard");
436       bse_part_deselect_notes (proxy, 0, self->proll->max_ticks, self->proll->min_note, self->proll->max_note);
437       bst_piano_roll_get_paste_pos (self->proll, &paste_tick, &paste_note);
438       paste_tick = bst_piano_roll_controller_quantize (self, paste_tick);
439       for (i = 0; i < pseq->n_pnotes; i++)
440         {
441           BsePartNote *pnote = pseq->pnotes[i];
442           ctick = MIN (ctick, pnote->tick);
443           cnote = MAX (cnote, pnote->note);
444         }
445       cnote = paste_note - cnote;
446       for (i = 0; i < pseq->n_pnotes; i++)
447         {
448           BsePartNote *pnote = pseq->pnotes[i];
449           guint id;
450           gint note;
451           note = pnote->note + cnote;
452           if (note >= 0)
453             {
454               id = bse_part_insert_note_auto (proxy,
455                                               pnote->tick - ctick + paste_tick,
456                                               pnote->duration,
457                                               note,
458                                               pnote->fine_tune,
459                                               pnote->velocity);
460               bse_part_select_event (proxy, id);
461             }
462         }
463       bse_item_ungroup_undo (proxy);
464     }
465 }
466
467 gboolean
468 bst_piano_roll_controller_clipboard_full (BstPianoRollController *self)
469 {
470   BsePartNoteSeq *pseq = bst_piano_roll_controller_get_clipboard ();
471   return pseq && pseq->n_pnotes;
472 }
473
474 gboolean
475 bst_piano_roll_controller_has_selection (BstPianoRollController *self,
476                                          guint64                 action_stamp)
477 {
478   if (self->cached_stamp != action_stamp)
479     {
480       SfiProxy part = self->proll->proxy;
481       if (part)
482         {
483           self->cached_stamp = action_stamp;
484           BsePartNoteSeq *pseq = bse_part_list_selected_notes (part);
485           self->cached_n_notes = pseq->n_pnotes;
486         }
487       else
488         self->cached_n_notes = 0;
489     }
490   return self->cached_n_notes > 0;
491 }
492
493 guint
494 bst_piano_roll_controller_quantize (BstPianoRollController *self,
495                                     guint                   fine_tick)
496 {
497   BseSongTiming *timing;
498   guint quant, tick, qtick;
499   g_return_val_if_fail (self != NULL, fine_tick);
500
501   timing = bse_part_get_timing (self->proll->proxy, fine_tick);
502   if (QUANTIZATION (self) == BST_QUANTIZE_NONE)
503     quant = 1;
504   else if (QUANTIZATION (self) == BST_QUANTIZE_TACT)
505     quant = timing->tpt;
506   else
507     quant = timing->tpqn * 4 / QUANTIZATION (self);
508   tick = fine_tick - timing->tick;
509   qtick = tick / quant;
510   qtick *= quant;
511   if (tick - qtick > quant / 2)
512     qtick += quant;
513   tick = timing->tick + qtick;
514   return tick;
515 }
516
517 static void
518 controller_update_canvas_cursor (BstPianoRollController *self,
519                                  BstCommonRollTool      tool)
520 {
521   GxkScrollCanvas *scc = GXK_SCROLL_CANVAS (self->proll);
522   switch (tool)
523     {
524     case BST_COMMON_ROLL_TOOL_INSERT:
525       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_PENCIL);
526       break;
527     case BST_COMMON_ROLL_TOOL_RESIZE:
528       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_SB_H_DOUBLE_ARROW);
529       break;
530     case BST_COMMON_ROLL_TOOL_MOVE:
531       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_FLEUR);
532       break;
533     case BST_COMMON_ROLL_TOOL_DELETE:
534       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_TARGET);
535       break;
536     case BST_COMMON_ROLL_TOOL_SELECT:
537       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_CROSSHAIR);
538       break;
539     case BST_COMMON_ROLL_TOOL_VSELECT:
540       gxk_scroll_canvas_set_canvas_cursor (scc, GDK_LEFT_SIDE);
541       break;
542     default:
543       gxk_scroll_canvas_set_canvas_cursor (scc, GXK_DEFAULT_CURSOR);
544       break;
545     }
546 }
547
548 static gboolean
549 check_hoverlap (SfiProxy part,
550                 guint    tick,
551                 guint    duration,
552                 gint     note,
553                 guint    except_tick,
554                 guint    except_duration)
555 {
556   if (duration)
557     {
558       BsePartNoteSeq *pseq = bse_part_check_overlap (part, tick, duration, note);
559       BsePartNote *pnote;
560       
561       if (pseq->n_pnotes == 0)
562         return FALSE;     /* no overlap */
563       if (pseq->n_pnotes > 1)
564         return TRUE;      /* definite overlap */
565       pnote = pseq->pnotes[0];
566       if (pnote->tick == except_tick &&
567           pnote->duration == except_duration)
568         return FALSE;     /* overlaps with exception */
569     }
570   return TRUE;
571 }
572
573 static void
574 move_start (BstPianoRollController *self,
575             BstPianoRollDrag       *drag)
576 {
577   SfiProxy part = self->proll->proxy;
578   if (self->obj_id)     /* got note to move */
579     {
580       self->xoffset = drag->start_tick - self->obj_tick;        /* drag offset */
581       controller_update_canvas_cursor (self, BST_COMMON_ROLL_TOOL_MOVE);
582       gxk_status_set (GXK_STATUS_WAIT, _("Move Note"), NULL);
583       drag->state = GXK_DRAG_CONTINUE;
584       if (bse_part_is_event_selected (part, self->obj_id))
585         self->sel_pseq = bse_part_note_seq_copy_shallow (bse_part_list_selected_notes (part));
586     }
587   else
588     {
589       gxk_status_set (GXK_STATUS_ERROR, _("Move Note"), _("No target"));
590       drag->state = GXK_DRAG_HANDLED;
591     }
592 }
593
594 static void
595 move_group_motion (BstPianoRollController *self,
596                    BstPianoRollDrag       *drag)
597 {
598   SfiProxy part = self->proll->proxy;
599   gint i, new_tick, old_note, new_note, delta_tick, delta_note;
600
601   new_tick = MAX (drag->current_tick, self->xoffset) - self->xoffset;
602   new_tick = bst_piano_roll_controller_quantize (self, new_tick);
603   old_note = self->obj_note;
604   new_note = drag->current_note;
605   delta_tick = self->obj_tick;
606   delta_note = old_note;
607   delta_tick -= new_tick;
608   delta_note -= new_note;
609   bse_item_group_undo (part, "Move Selection");
610   for (i = 0; i < self->sel_pseq->n_pnotes; i++)
611     {
612       BsePartNote *pnote = self->sel_pseq->pnotes[i];
613       gint tick = pnote->tick;
614       gint note = pnote->note;
615       note -= delta_note;
616       bse_part_change_note (part, pnote->id,
617                             MAX (tick - delta_tick, 0),
618                             pnote->duration,
619                             SFI_NOTE_CLAMP (note),
620                             pnote->fine_tune,
621                             pnote->velocity);
622     }
623   if (drag->type == GXK_DRAG_DONE)
624     {
625       bse_part_note_seq_free (self->sel_pseq);
626       self->sel_pseq = NULL;
627     }
628   bse_item_ungroup_undo (part);
629 }
630
631 static void
632 move_motion (BstPianoRollController *self,
633              BstPianoRollDrag       *drag)
634 {
635   SfiProxy part = self->proll->proxy;
636   gint new_tick;
637   gboolean note_changed;
638
639   if (self->sel_pseq)
640     {
641       move_group_motion (self, drag);
642       return;
643     }
644
645   new_tick = MAX (drag->current_tick, self->xoffset) - self->xoffset;
646   new_tick = bst_piano_roll_controller_quantize (self, new_tick);
647   note_changed = self->obj_note != drag->current_note;
648   if ((new_tick != self->obj_tick || note_changed) &&
649       !check_hoverlap (part, new_tick, self->obj_duration, drag->current_note,
650                        self->obj_tick, note_changed ? 0 : self->obj_duration))
651     {
652       bse_item_group_undo (part, "Move Note");
653       if (bse_part_delete_event (part, self->obj_id) != BSE_ERROR_NONE)
654         drag->state = GXK_DRAG_ERROR;
655       else
656         {
657           self->obj_id = bse_part_insert_note_auto (part, new_tick, self->obj_duration,
658                                                     drag->current_note, self->obj_fine_tune, self->obj_velocity);
659           self->obj_tick = new_tick;
660           self->obj_note = drag->current_note;
661           if (!self->obj_id)
662             drag->state = GXK_DRAG_ERROR;
663         }
664       bse_item_ungroup_undo (part);
665     }
666 }
667
668 static void
669 move_abort (BstPianoRollController *self,
670             BstPianoRollDrag       *drag)
671 {
672   if (self->sel_pseq)
673     {
674       bse_part_note_seq_free (self->sel_pseq);
675       self->sel_pseq = NULL;
676     }
677   gxk_status_set (GXK_STATUS_ERROR, _("Move Note"), _("Lost Note"));
678 }
679
680 static void
681 resize_start (BstPianoRollController *self,
682               BstPianoRollDrag       *drag)
683 {
684   if (self->obj_id)     /* got note for resize */
685     {
686       guint bound = self->obj_tick + self->obj_duration + 1;
687
688       /* set the fix-point (either note start or note end) */
689       if (drag->start_tick - self->obj_tick <= bound - drag->start_tick)
690         self->tick_bound = bound;
691       else
692         self->tick_bound = self->obj_tick;
693       controller_update_canvas_cursor (self, BST_COMMON_ROLL_TOOL_RESIZE);
694       gxk_status_set (GXK_STATUS_WAIT, _("Resize Note"), NULL);
695       drag->state = GXK_DRAG_CONTINUE;
696     }
697   else
698     {
699       gxk_status_set (GXK_STATUS_ERROR, _("Resize Note"), _("No target"));
700       drag->state = GXK_DRAG_HANDLED;
701     }
702 }
703
704 static void
705 resize_motion (BstPianoRollController *self,
706                BstPianoRollDrag       *drag)
707 {
708   SfiProxy part = self->proll->proxy;
709   guint new_bound, new_tick, new_duration;
710
711   /* calc new note around fix-point */
712   new_tick = bst_piano_roll_controller_quantize (self, drag->current_tick);
713   new_bound = MAX (new_tick, self->tick_bound);
714   new_tick = MIN (new_tick, self->tick_bound);
715   new_duration = new_bound - new_tick;
716   new_duration = MAX (new_duration, 1) - 1;
717
718   /* apply new note size */
719   if ((self->obj_tick != new_tick || new_duration != self->obj_duration) &&
720       !check_hoverlap (part, new_tick, new_duration, self->obj_note,
721                        self->obj_tick, self->obj_duration))
722     {
723       bse_item_group_undo (part, "Resize Note");
724       if (self->obj_id)
725         {
726           BseErrorType error = bse_part_delete_event (part, self->obj_id);
727           if (error)
728             drag->state = GXK_DRAG_ERROR;
729           self->obj_id = 0;
730         }
731       if (new_duration && drag->state != GXK_DRAG_ERROR)
732         {
733           self->obj_id = bse_part_insert_note_auto (part, new_tick, new_duration,
734                                                     self->obj_note, self->obj_fine_tune, self->obj_velocity);
735           self->obj_tick = new_tick;
736           self->obj_duration = new_duration;
737           if (!self->obj_id)
738             drag->state = GXK_DRAG_ERROR;
739         }
740       bse_item_ungroup_undo (part);
741     }
742 }
743
744 static void
745 resize_abort (BstPianoRollController *self,
746               BstPianoRollDrag       *drag)
747 {
748   gxk_status_set (GXK_STATUS_ERROR, _("Resize Note"), _("Lost Note"));
749 }
750
751 static void
752 delete_start (BstPianoRollController *self,
753               BstPianoRollDrag       *drag)
754 {
755   SfiProxy part = self->proll->proxy;
756   if (self->obj_id)     /* got note to delete */
757     {
758       BseErrorType error = bse_part_delete_event (part, self->obj_id);
759       bst_status_eprintf (error, _("Delete Note"));
760     }
761   else
762     gxk_status_set (GXK_STATUS_ERROR, _("Delete Note"), _("No target"));
763   drag->state = GXK_DRAG_HANDLED;
764 }
765
766 static void
767 insert_start (BstPianoRollController *self,
768               BstPianoRollDrag       *drag)
769 {
770   SfiProxy part = self->proll->proxy;
771   BseErrorType error = BSE_ERROR_NO_TARGET;
772   if (drag->start_valid)
773     {
774       guint qtick = bst_piano_roll_controller_quantize (self, drag->start_tick);
775       guint duration = drag->proll->tpqn * 4 / NOTE_LENGTH (self);
776       if (check_hoverlap (part, qtick, duration, drag->start_note, 0, 0))
777         error = BSE_ERROR_INVALID_OVERLAP;
778       else
779         {
780           bse_part_insert_note_auto (part, qtick, duration, drag->start_note, 0, 1.0);
781           error = BSE_ERROR_NONE;
782         }
783     }
784   bst_status_eprintf (error, _("Insert Note"));
785   drag->state = GXK_DRAG_HANDLED;
786 }
787
788 static void
789 select_start (BstPianoRollController *self,
790               BstPianoRollDrag       *drag)
791 {
792   drag->start_tick = bst_piano_roll_controller_quantize (self, drag->start_tick);
793   bst_piano_roll_set_view_selection (drag->proll, drag->start_tick, 0, 0, 0);
794   gxk_status_set (GXK_STATUS_WAIT, _("Select Region"), NULL);
795   drag->state = GXK_DRAG_CONTINUE;
796 }
797
798 static void
799 select_motion (BstPianoRollController *self,
800                BstPianoRollDrag       *drag)
801 {
802   SfiProxy part = self->proll->proxy;
803   guint start_tick = MIN (drag->start_tick, drag->current_tick);
804   guint end_tick = MAX (drag->start_tick, drag->current_tick);
805   gint min_note = MIN (drag->start_note, drag->current_note);
806   gint max_note = MAX (drag->start_note, drag->current_note);
807
808   bst_piano_roll_set_view_selection (drag->proll, start_tick, end_tick - start_tick, min_note, max_note);
809   if (drag->type == GXK_DRAG_DONE)
810     {
811       bse_part_select_notes_exclusive (part, start_tick, end_tick - start_tick, min_note, max_note);
812       bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
813     }
814 }
815
816 static void
817 select_abort (BstPianoRollController *self,
818               BstPianoRollDrag       *drag)
819 {
820   gxk_status_set (GXK_STATUS_ERROR, _("Select Region"), _("Aborted"));
821   bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
822 }
823
824 static void
825 vselect_start (BstPianoRollController *self,
826                BstPianoRollDrag       *drag)
827 {
828   drag->start_tick = bst_piano_roll_controller_quantize (self, drag->start_tick);
829   bst_piano_roll_set_view_selection (drag->proll, drag->start_tick, 0, drag->proll->min_note, drag->proll->max_note);
830   gxk_status_set (GXK_STATUS_WAIT, _("Vertical Select"), NULL);
831   drag->state = GXK_DRAG_CONTINUE;
832 }
833
834 static void
835 vselect_motion (BstPianoRollController *self,
836                 BstPianoRollDrag       *drag)
837 {
838   SfiProxy part = self->proll->proxy;
839   guint start_tick = MIN (drag->start_tick, drag->current_tick);
840   guint end_tick = MAX (drag->start_tick, drag->current_tick);
841
842   bst_piano_roll_set_view_selection (drag->proll, start_tick, end_tick - start_tick,
843                                      drag->proll->min_note, drag->proll->max_note);
844   if (drag->type == GXK_DRAG_DONE)
845     {
846       bse_part_select_notes_exclusive (part, start_tick, end_tick - start_tick,
847                                        drag->proll->min_note, drag->proll->max_note);
848       bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
849     }
850 }
851
852 static void
853 vselect_abort (BstPianoRollController *self,
854                BstPianoRollDrag       *drag)
855 {
856   gxk_status_set (GXK_STATUS_ERROR, _("Vertical Region"), _("Aborted"));
857   bst_piano_roll_set_view_selection (drag->proll, 0, 0, 0, 0);
858 }
859
860 #if 0
861 static void
862 generic_abort (BstPianoRollController *self,
863                BstPianoRollDrag       *drag)
864 {
865   gxk_status_set (GXK_STATUS_ERROR, _("Abortion"), NULL);
866 }
867 #endif
868
869 typedef void (*DragFunc) (BstPianoRollController *,
870                           BstPianoRollDrag       *);
871
872 void
873 controller_canvas_drag (BstPianoRollController *self,
874                         BstPianoRollDrag       *drag)
875 {
876   static struct {
877     BstCommonRollTool tool;
878     DragFunc start, motion, abort;
879   } tool_table[] = {
880     { BST_COMMON_ROLL_TOOL_INSERT,      insert_start,   NULL,           NULL,           },
881     { BST_COMMON_ROLL_TOOL_ALIGN,       insert_start,   NULL,           NULL,           },
882     { BST_COMMON_ROLL_TOOL_RESIZE,      resize_start,   resize_motion,  resize_abort,   },
883     { BST_COMMON_ROLL_TOOL_MOVE,        move_start,     move_motion,    move_abort,     },
884     { BST_COMMON_ROLL_TOOL_DELETE,      delete_start,   NULL,           NULL,           },
885     { BST_COMMON_ROLL_TOOL_SELECT,      select_start,   select_motion,  select_abort,   },
886     { BST_COMMON_ROLL_TOOL_VSELECT,     vselect_start,  vselect_motion, vselect_abort,  },
887   };
888   guint i;
889
890   if (drag->type == GXK_DRAG_START)
891     {
892       BstCommonRollTool tool = BST_COMMON_ROLL_TOOL_NONE;
893       BsePartNoteSeq *pseq;
894
895       /* setup drag data */
896       pseq = bse_part_get_notes (drag->proll->proxy, drag->start_tick, drag->start_note);
897       if (pseq->n_pnotes)
898         {
899           BsePartNote *pnote = pseq->pnotes[0];
900           self->obj_id = pnote->id;
901           self->obj_tick = pnote->tick;
902           self->obj_duration = pnote->duration;
903           self->obj_note = pnote->note;
904           self->obj_fine_tune = pnote->fine_tune;
905           self->obj_velocity = pnote->velocity;
906         }
907       else
908         {
909           self->obj_id = 0;
910           self->obj_tick = 0;
911           self->obj_duration = 0;
912           self->obj_note = 0;
913           self->obj_fine_tune = 0;
914           self->obj_velocity = 0;
915         }
916       if (self->sel_pseq)
917         g_warning ("leaking old drag selection (%p)", self->sel_pseq);
918       self->sel_pseq = NULL;
919       self->xoffset = 0;
920       self->tick_bound = 0;
921
922       /* find drag tool */
923       tool = piano_canvas_button_tool (self, drag->button, self->obj_id > 0);
924       for (i = 0; i < G_N_ELEMENTS (tool_table); i++)
925         if (tool_table[i].tool == tool)
926           break;
927       self->tool_index = i;
928       if (i >= G_N_ELEMENTS (tool_table))
929         return;         /* unhandled */
930     }
931   i = self->tool_index;
932   g_return_if_fail (i < G_N_ELEMENTS (tool_table));
933   switch (drag->type)
934     {
935     case GXK_DRAG_START:
936       if (tool_table[i].start)
937         tool_table[i].start (self, drag);
938       break;
939     case GXK_DRAG_MOTION:
940     case GXK_DRAG_DONE:
941       if (tool_table[i].motion)
942         tool_table[i].motion (self, drag);
943       break;
944     case GXK_DRAG_ABORT:
945       if (tool_table[i].abort)
946         tool_table[i].abort (self, drag);
947       break;
948     }
949   if (drag->type == GXK_DRAG_DONE ||
950       drag->type == GXK_DRAG_ABORT)
951     controller_reset_canvas_cursor (self);
952 }
953
954 void
955 controller_piano_drag (BstPianoRollController *self,
956                        BstPianoRollDrag       *drag)
957 {
958   SfiProxy part = self->proll->proxy;
959   SfiProxy song = bse_item_get_parent (part);
960   SfiProxy project = song ? bse_item_get_parent (song) : 0;
961   SfiProxy track = song ? bse_song_find_track_for_part (song, part) : 0;
962
963   // g_printerr ("piano drag event, note=%d (valid=%d)", drag->current_note, drag->current_valid);
964
965   if (project && track)
966     {
967       if (drag->type == GXK_DRAG_START ||
968           (drag->type == GXK_DRAG_MOTION &&
969            self->obj_note != drag->current_note))
970         {
971           BseErrorType error;
972           bse_project_auto_deactivate (project, 5 * 1000);
973           error = bse_project_activate (project);
974           self->obj_note = drag->current_note;
975           if (error == BSE_ERROR_NONE)
976             bse_song_synthesize_note (song, track, 384 * 4, self->obj_note, 0, 1.0);
977           bst_status_eprintf (error, _("Play note"));
978           drag->state = GXK_DRAG_CONTINUE;
979         }
980     }
981
982   if (drag->type == GXK_DRAG_START ||
983       drag->type == GXK_DRAG_MOTION)
984     drag->state = GXK_DRAG_CONTINUE;
985 }