1 /**
2  * select.c
3  * operations for selecting, cutting, copying, and pasting music
4  *
5  * for Denemo, a gtk+ frontend to GNU Lilypond
6  * (c) 1999-2005 Matthew Hiller, Adam Tee, 2011 Richard Shann
7  *
8  *
9  */
10 
11 #include <string.h>
12 #include "display/calculatepositions.h"
13 #include "command/commandfuncs.h"
14 #include <denemo/denemo.h>
15 #include "display/draw.h"
16 #include "command/object.h"
17 #include "command/measure.h"
18 #include "command/select.h"
19 #include "command/staff.h"
20 #include "core/prefops.h"
21 #include "command/lyric.h"
22 #include "command/lilydirectives.h"
23 #include "command/score.h"
24 #include "core/cache.h"
25 #include "core/view.h"
26 #include "command/contexts.h"
27 #include "ui/moveviewport.h"
28 /*For save selection function*/
29 #include "core/utils.h"
30 /**
31  * The copy buffer is a GList of objnode *s -- at first, I was going
32  * to use staffnode *s, measurenode *s, and then objnode *s, but I
33  * realized that'd be overkill and just complicate the implementation
34  * unnecessarily.
35  *
36  * Each item in the copybuffer list corresponds to the stuff in
37  * the buffer on each staff.
38  */
39 
40 static void undo (DenemoProject * gui);
41 static void redo (DenemoProject * gui);
42 static GList *copybuffer = NULL;        // this is a list one for each staff of lists of objects
43 
44 static gint staffsinbuffer = 0;
45 static gint measurebreaksinbuffer = 0;
46 
47 static GList *clipboards = NULL;
48 typedef struct DenemoClipboard
49 {
50   GList *objectlist;
51   gint staffsinbuffer;
52   gint measurebreaksinbuffer;
53 } DenemoClipboard;
54 static GList *
clone_obj_list(GList * g)55 clone_obj_list (GList * g)
56 {
57   GList *ret = NULL;
58   do
59     {
60       ret = g_list_append (ret, dnm_clone_object (g->data));
61     }
62   while ((g = g->next));
63   return ret;
64 }
65 
66 // pushes the current copybuffer; pushes a NULL clipboard if none.
67 void
push_clipboard(void)68 push_clipboard (void)
69 {
70   GList *thecopy = NULL;
71   DenemoClipboard *clip = (DenemoClipboard *) g_malloc0 (sizeof (DenemoClipboard));
72   GList *g;
73   for (g = copybuffer; g; g = g->next)
74     {
75       thecopy = g_list_append (thecopy, clone_obj_list (g->data));
76     }
77   clip->objectlist = thecopy;
78   clip->measurebreaksinbuffer = measurebreaksinbuffer;
79   clip->staffsinbuffer = staffsinbuffer;
80   clipboards = g_list_prepend (clipboards, clip);
81 }
82 
83 gboolean
pop_clipboard(void)84 pop_clipboard (void)
85 {
86   GList *thecopy = NULL;
87   DenemoClipboard *clip;
88   if (clipboards == NULL)
89     return FALSE;
90   clip = (DenemoClipboard *) clipboards->data;
91   clipboards = g_list_remove (clipboards, clip);
92   clearbuffer ();
93   if (clip->objectlist)
94     {
95       thecopy = clip->objectlist;
96       measurebreaksinbuffer = clip->measurebreaksinbuffer;
97       staffsinbuffer = clip->staffsinbuffer;
98       copybuffer = thecopy;
99     }
100   g_free (clip);
101   return TRUE;
102 }
103 
104 /* returns the top clipboard popped off the stack.
105    The caller must free the clipboard with
106    when done */
107 GList *
pop_off_clipboard(void)108 pop_off_clipboard (void)
109 {
110   GList *thecopy = NULL;
111   if (clipboards && clipboards->data)
112     thecopy = ((DenemoClipboard *) clipboards->data)->objectlist;
113   if(clipboards)
114     g_free (clipboards->data);
115   clipboards = g_list_remove (clipboards, clipboards->data);
116   return thecopy;
117 }
118 
119 /**
120  *  sets current object to the given cursor position
121  *
122  */
123 void
setcurrentobject(DenemoMovement * si,gint cursorpos)124 setcurrentobject (DenemoMovement * si, gint cursorpos)
125 {
126 
127   g_debug ("Set Current Object Cursor pos %d\n", cursorpos);
128 
129   si->currentobject = g_list_nth ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects, cursorpos);
130   //g_assert (si->currentobject != NULL);
131 }
132 
133 /**
134  *  clearbuffer
135  *  Clears the copybuffer of data
136  *  Arguments - None
137  *  return - none
138  */
139 void
clearbuffer(void)140 clearbuffer (void)
141 {
142   g_list_foreach (copybuffer, freeobjlist, NULL);
143   g_list_free (copybuffer);
144   copybuffer = NULL;
145   staffsinbuffer = 0;
146   measurebreaksinbuffer = 0;
147 }
148 
149 void
free_clipboard(GList * clipboard)150 free_clipboard (GList * clipboard)
151 {
152   if (clipboard)
153     {
154       push_clipboard ();
155       copybuffer = clipboard;
156       measurebreaksinbuffer = 0;
157       staffsinbuffer = 1;
158       clearbuffer ();
159       pop_clipboard ();
160     }
161 }
162 
163 gint
get_staffs_in_clipboard(void)164 get_staffs_in_clipboard (void)
165 {
166   return staffsinbuffer;
167 }
168 
169 void
insert_clipboard(GList * clipboard)170 insert_clipboard (GList * clipboard)
171 {
172   if (clipboard)
173     {
174       push_clipboard ();
175       copybuffer = clipboard;
176       measurebreaksinbuffer = 0;
177       staffsinbuffer = 1;
178       call_out_to_guile ("(d-Paste)");
179       copybuffer = NULL;
180       pop_clipboard ();
181       displayhelper (Denemo.project);
182       score_status(Denemo.project, TRUE);
183     }
184 }
185 
186 /**
187  *  saveselection
188  *  Saves the current selection to a given file
189  *
190  *  @param si pointer to the score information structure
191  *  @return none
192  */
193 void
saveselection(DenemoMovement * si)194 saveselection (DenemoMovement * si)
195 {
196   if (si->markstaffnum == 0)    /* Indicator that there's no selection.  */
197     return;
198 
199   clearbuffer ();
200 
201   staffsinbuffer = si->selection.laststaffmarked - si->selection.firststaffmarked + 1;
202 
203   copytobuffer (si);
204   si->savebuffer = copybuffer;
205   /* Test code for save selection
206      FILE *fp;
207      GString *file = NULL;
208      file = g_string_new(get_user_data_dir());
209      g_string_append(file, "/denemoanalysispattern");
210 
211      filesaveselection(file->str, si);
212      clearbuffer ();
213      g_free(file);
214    */
215 }
216 
217 /**
218  *   copytobuffer
219  *   Copies selection to the copybuffer
220  *
221  *   @param si pointer to the score information structure
222  */
223 void
copytobuffer(DenemoMovement * si)224 copytobuffer (DenemoMovement * si)
225 {
226   staffnode *curstaff;
227   measurenode *curmeasure;
228   objnode *curobj;
229   objnode *theobjs;
230   DenemoObject *clonedobject;
231   gint i = 0, j = 0, k = 0;
232 
233   if (si->markstaffnum == 0)    /* Indicator that there's no selection.  */
234     return;
235 
236   clearbuffer ();
237 
238   staffsinbuffer = si->selection.laststaffmarked - si->selection.firststaffmarked + 1;
239   g_debug ("No staffs in copybuffer %d\n", staffsinbuffer);
240   /* Staff loop.  */
241   for (i = si->selection.firststaffmarked, curstaff = g_list_nth (si->thescore, i - 1); curstaff && i <= si->selection.laststaffmarked; curstaff = curstaff->next, i++)
242     {
243       if (((DenemoStaff *) curstaff->data)->is_parasite)
244         continue;
245       /* Initialize first ->data for copybuffer to NULL.  */
246       theobjs = NULL;
247       /* Measure loop.  */
248       for (j = si->selection.firstmeasuremarked, k = si->selection.firstobjmarked, curmeasure = g_list_nth (staff_first_measure_node (curstaff), j - 1); curmeasure && j <= si->selection.lastmeasuremarked; curmeasure = curmeasure->next, j++)
249         {
250           for (curobj = g_list_nth ((objnode *) ((DenemoMeasure*)curmeasure->data)->objects, k);
251                /* cursor_x is 0-indexed */
252                curobj && (j < si->selection.lastmeasuremarked || k <= si->selection.lastobjmarked); curobj = curobj->next, k++)
253             {
254               clonedobject = dnm_clone_object ((DenemoObject *) curobj->data);
255               theobjs = g_list_append (theobjs, clonedobject);
256             }                   /* End object loop */
257           g_debug ("cloned objects on staff \n");
258 
259           if (j < si->selection.lastmeasuremarked || k < si->selection.lastobjmarked)
260             {
261               if (!((j == si->selection.lastmeasuremarked)))
262                 {
263                   g_debug ("Insert measurebreak obj in copybuffer");
264                   /* ???outdated comment??? That is, there's another measure, the cursor is in appending
265                      position, or the selection spans multiple staffs, in which
266                      case another measure boundary should be added.  */
267                   theobjs = g_list_append (theobjs, newmeasurebreakobject ());
268                   if (i == si->selection.firststaffmarked)
269                     measurebreaksinbuffer++;
270                 }
271             }
272           k = 0;                /* Set it for next run through object loop */
273 
274         }                       /* End measure loop */
275       if ((staffsinbuffer > 1) && (i < si->selection.laststaffmarked))
276         {
277           theobjs = g_list_append (theobjs, newstaffbreakobject ());
278           g_debug ("Inserting Staffbreak object in copybuffer");
279         }
280       if (theobjs)
281         copybuffer = g_list_append (copybuffer, theobjs);
282     }                           /* End staff loop */
283 }
284 
285 
286 /**
287  *  cuttobuffer
288  *  Cuts selection to the copybuffer, removing it from the score
289  *
290  *  @param si pointer to score information structure
291  */
292 static void
cuttobuffer(DenemoMovement * si,gboolean copyfirst)293 cuttobuffer (DenemoMovement * si, gboolean copyfirst)
294 {
295   staffnode *curstaff;
296   measurenode *curmeasure;
297   objnode *tempobj;
298   gint i, jcounter,             //jcounter is marking the position of the measure currently being cleared I think
299     max;
300   if (!si->markstaffnum)
301     return;
302   take_snapshot ();
303   if (copyfirst)
304     copytobuffer (si);
305   gint staffs_removed_measures = 0;     // a count of removed measures in the case where multiple staffs are involved
306   gint lmeasurebreaksinbuffer = si->selection.lastmeasuremarked - si->selection.firstmeasuremarked;
307   gint lstaffsinbuffer = si->selection.laststaffmarked - si->selection.firststaffmarked + 1;
308   if (copyfirst)
309     {
310       if (!(lmeasurebreaksinbuffer == measurebreaksinbuffer))
311         g_warning ("logic of copy to buffer seems wrong about measure breaks");
312       if (!(lstaffsinbuffer == staffsinbuffer))
313         g_warning ("logic of copy to buffer seems wrong about staff breaks");
314     }
315   if (lstaffsinbuffer == 1)
316     {
317       /* Just a single staff is a special case, again.  */
318       jcounter = si->selection.firstmeasuremarked;      //currently clearing stuff from the firstmeasuremarked
319       curmeasure = g_list_nth (staff_first_measure_node (si->currentstaff), jcounter - 1);
320 
321       /* Clear the relevant part of the first measure selected */
322       if (lmeasurebreaksinbuffer)
323         max = G_MAXINT;
324       else
325         max = si->selection.lastobjmarked;
326       for (i = si->selection.firstobjmarked; ((tempobj = g_list_nth ((objnode *) ((DenemoMeasure*)curmeasure->data)->objects, si->selection.firstobjmarked)) && i <= max); i++)
327         {
328            ((DenemoMeasure*)curmeasure->data)->objects = g_list_remove_link ((objnode *)  ((DenemoMeasure*)curmeasure->data)->objects, tempobj);
329           freeobject ((DenemoObject *) tempobj->data);
330           g_list_free_1 (tempobj);
331         }
332       jcounter++;               //move on to the second measure being cleared
333       curmeasure = curmeasure->next;
334 
335       if (!si->thescore->next)
336         {
337           /* That is, the score has only this one staff
338              remove the (whole) measures between the first and last - which may be partial. */
339           if (lmeasurebreaksinbuffer - 1 > 0)
340             {
341               curmeasure = removemeasures (si, jcounter - 1, lmeasurebreaksinbuffer - 1, TRUE);
342               jcounter += lmeasurebreaksinbuffer - 1;   // increased by the number of measures *between* first and last marked
343             }
344         }
345       else
346         for (; curmeasure && jcounter < si->selection.lastmeasuremarked; curmeasure = curmeasure->next, jcounter++)
347           {
348             freeobjlist (((DenemoMeasure*)curmeasure->data)->objects, NULL);
349             ((DenemoMeasure*)curmeasure->data)->objects = NULL;
350           }
351       /* Now clear the relevant part of the last measure selected */
352       if (curmeasure && (jcounter <= si->selection.lastmeasuremarked))
353         {
354           for (i = 0; ((DenemoMeasure*)curmeasure->data)->objects && i <= si->selection.lastobjmarked; i++)
355             {
356               tempobj = (objnode *)  ((DenemoMeasure*)curmeasure->data)->objects;
357                ((DenemoMeasure*)curmeasure->data)->objects = g_list_remove_link ((objnode *)  ((DenemoMeasure*)curmeasure->data)->objects, tempobj);
358               freeobject ((DenemoObject *) tempobj->data);
359               g_list_free_1 (tempobj);
360             }
361           /* And delete it, if the measure's been cleared and there's only
362              one staff.  */
363 
364           if (!((DenemoMeasure*)curmeasure->data)->objects && !si->thescore->next)
365             removemeasures (si, g_list_position (staff_first_measure_node (si->currentstaff), curmeasure), 1, TRUE);
366         }
367 
368       cache_staff (si->currentstaff);
369 
370       staff_fix_note_heights ((DenemoStaff*)si->currentstaff->data);
371       staff_show_which_accidentals ((DenemoStaff *) si->currentstaff->data);
372       staff_beams_and_stems_dirs ((DenemoStaff *) si->currentstaff->data);
373     }                           // end of single staff
374   else
375     {                           /* Multiple staff selection */
376       if (lstaffsinbuffer == (gint) (g_list_length (si->thescore)))
377         {
378           /* Every staff was part of the selection */
379           if (lmeasurebreaksinbuffer > 0)
380             {
381 
382               removemeasures (si, si->selection.firstmeasuremarked - 1, lmeasurebreaksinbuffer + 1, TRUE);
383               staffs_removed_measures = lmeasurebreaksinbuffer;
384 
385               cache_all ();
386             }
387           else
388             for (curstaff = si->thescore; curstaff; curstaff = curstaff->next)
389               {
390                 curmeasure = g_list_nth (staff_first_measure_node (curstaff), si->selection.firstmeasuremarked - 1);
391                 freeobjlist ( ((DenemoMeasure*)curmeasure->data)->objects, NULL);
392                  ((DenemoMeasure*)curmeasure->data)->objects = NULL;
393 
394                 cache_staff (curstaff);
395                 staff_fix_note_heights ((DenemoStaff*)curstaff->data);
396                 staff_show_which_accidentals ((DenemoStaff *) curstaff->data);
397                 staff_beams_and_stems_dirs ((DenemoStaff *) curstaff->data);
398               }
399         }
400       else
401         {
402           /* Staff loop */
403           for (i = si->selection.firststaffmarked, curstaff = g_list_nth (si->thescore, i - 1); curstaff && i <= si->selection.laststaffmarked; curstaff = curstaff->next, i++)
404             {
405               if (((DenemoStaff *) curstaff->data)->is_parasite)
406                 continue;
407               /* Measure loop */
408               for (jcounter = si->selection.firstmeasuremarked, curmeasure = g_list_nth (staff_first_measure_node (curstaff), jcounter - 1); curmeasure && jcounter <= si->selection.lastmeasuremarked; curmeasure = curmeasure->next, jcounter++)
409                 {
410                   freeobjlist ( ((DenemoMeasure*)curmeasure->data)->objects, NULL);
411                    ((DenemoMeasure*)curmeasure->data)->objects = NULL;
412                 }
413 
414               cache_staff (curstaff);
415 
416               staff_show_which_accidentals ((DenemoStaff *) curstaff->data);
417               staff_beams_and_stems_dirs ((DenemoStaff *) curstaff->data);
418             }
419         }
420 
421 
422 
423     }
424   si->selection.firststaffmarked = si->markstaffnum = 0;        //only the latter is needed, but there was some confusion at one time...
425   /* And set some currents. This would probably be better to split off
426    * into a more-generalized version of setcurrents or something;
427    * what's here is more-or-less copied from dnm_deleteobject in
428    * commandfuncs */
429 
430   si->currentmeasurenum = si->selection.firstmeasuremarked - (staffs_removed_measures ? 1 : 0);
431 
432   if (si->currentmeasurenum < 1)
433     {
434       si->currentmeasurenum = 1;
435     }
436 
437 
438   si->currentmeasure = g_list_nth (staff_first_measure_node (si->currentstaff), si->currentmeasurenum - 1);
439 
440   si->cursor_x = si->selection.firstobjmarked;
441   if (si->cursor_x < (gint) (g_list_length ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects)))
442     {
443       si->currentobject = g_list_nth ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects, si->cursor_x);
444       si->cursor_appending = FALSE;
445     }
446   else
447     {
448       si->currentobject = g_list_last ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects);
449       si->cursor_appending = TRUE;
450     }
451   //if clef has been deleted we need to re-validate leftmost clef - would only apply if the clef being deleted was off the left side of screen - some sort of scripting scenario...
452   find_leftmost_allcontexts (si);
453 
454 
455   isoffleftside (Denemo.project);
456   isoffrightside (Denemo.project);
457 
458   score_status (Denemo.project, TRUE);
459 
460 }
461 
462 
463 
464 DenemoObjType
get_clip_obj_type(gint m,gint n)465 get_clip_obj_type (gint m, gint n)
466 {
467   if (copybuffer == NULL)
468     return -1;
469   GList *stafflist = g_list_nth (copybuffer, m);
470   if (stafflist == NULL)
471     return -1;
472   GList *curbufferobj = g_list_nth (stafflist->data, n);
473   if (curbufferobj == NULL || curbufferobj->data == NULL)
474     return -1;
475   return ((DenemoObject *) (curbufferobj->data))->type;
476 }
477 
478 gint
get_clip_objs(gint m)479 get_clip_objs (gint m)
480 {
481   if (copybuffer == NULL)
482     return -1;
483   GList *stafflist = g_list_nth (copybuffer, m);
484   if (stafflist == NULL)
485     return -1;
486   return g_list_length (stafflist->data);
487 }
488 
489 //FIXME yet another insert object, compare object_insert() in commandfuncs.c
490 
491 void
insert_object(DenemoObject * clonedobj)492 insert_object (DenemoObject * clonedobj)
493 {
494     object_insert (Denemo.project, clonedobj);
495 #if 0
496 
497   DenemoMovement *si = Denemo.project->movement;
498   staffnode *curstaff = si->currentstaff;
499   clonedobj->starttick = (si->currentobject ? ((DenemoObject *) si->currentobject->data)->starttickofnextnote : 0);
500   /* update undo information */
501   DenemoUndoData *undo;
502   if (!si->undo_guard)
503     {
504       undo = (DenemoUndoData *) g_malloc (sizeof (DenemoUndoData));
505       //undo->object = clonedobj;
506       //do position after inserting, so we can go back to it to delete
507     }
508   DenemoMeasure *m =si->currentmeasure->data;
509   ((DenemoMeasure*)si->currentmeasure->data)->objects =  g_list_insert (m->objects, clonedobj, si->cursor_x);
510 //  Denemo.project->movement->currentmeasure->data =  g_list_insert ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects, clonedobj, si->cursor_x); DANGER FIXME this was trying to insert an object
511 
512 
513   if (!si->undo_guard)
514     {
515       get_position (si, &undo->position);
516       undo->position.appending = 0;
517       undo->action = ACTION_INSERT;
518       update_undo_info (si, undo);
519     }
520 
521 
522 
523   si->cursor_x++;
524   if (si->cursor_appending)
525     si->currentobject = g_list_last ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects);
526   else
527     si->currentobject = g_list_nth ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects, si->cursor_x);
528 
529   if (si->currentobject == NULL)
530     {
531       g_warning ("problematic parameters on insert %d out of %d objects", si->cursor_x + 1, g_list_length ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects));
532       si->cursor_x--;
533       si->currentobject = g_list_nth ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects, si->cursor_x);
534     }
535 
536 
537   staff_beams_and_stems_dirs ((DenemoStaff *) curstaff->data);
538   find_xes_in_all_measures (si);
539 #endif
540 }
541 
542 // insert the nth object from the mth staff from the copybuffer into music at the cursor position
543 // return TRUE if inserted
544 gboolean
insert_clip_obj(gint m,gint n)545 insert_clip_obj (gint m, gint n)
546 {
547   DenemoMovement *si = Denemo.project->movement;
548   if (copybuffer == NULL)
549     return FALSE;
550   GList *stafflist = g_list_nth (copybuffer, m);
551   if (stafflist == NULL)
552     return FALSE;
553   objnode *curbufferobj = g_list_nth (stafflist->data, n);
554   if (curbufferobj == NULL)
555     return FALSE;
556   DenemoObject *clonedobj;
557   DenemoObject *curobj = (DenemoObject *) curbufferobj->data;
558   clonedobj = dnm_clone_object (curobj);
559   insert_object (clonedobj);
560   cache_measure (si->currentmeasure);
561 #if 0
562   octave_up_key (Denemo.project);   //FIXME up and down to fix clef change bug !!!!!!!!
563   octave_down_key (Denemo.project); //FIXME up and down to fix clef change bug !!!!!!!!
564 #endif
565   //reset_cursor_stats (si);
566   if (clonedobj->type == CLEF)
567     staff_fix_note_heights ((DenemoStaff *) si->currentstaff->data);
568   else
569     if (clonedobj->type == CHORD)
570         newclefify (clonedobj);
571   staff_beams_and_stems_dirs ((DenemoStaff *) si->currentstaff->data);
572   find_xes_in_all_measures (si);
573   showwhichaccidentals ((objnode *) ((DenemoMeasure*)si->currentmeasure->data)->objects);
574 
575   return TRUE;
576 }
577 
578 
579 DenemoObject *
get_mark_object(void)580 get_mark_object (void)
581 {
582   DenemoProject *gui = Denemo.project;
583   DenemoMovement *si = gui->movement;
584   if (!si->markstaffnum)
585     return NULL;
586   staffnode *curstaff = g_list_nth (si->thescore, si->selection.firststaffmarked - 1);
587   DenemoStaff *firststaff = (DenemoStaff *) curstaff->data;
588   measurenode *firstmeasure = g_list_nth (firststaff->themeasures, si->selection.firstmeasuremarked - 1);
589   objnode *firstobj = g_list_nth (((DenemoMeasure*)firstmeasure->data)->objects, si->selection.firstobjmarked);
590   //g_debug("First %d\n",  si->selection.firstobjmarked);
591   return firstobj ? ((DenemoObject *) firstobj->data) : NULL;
592 }
593 
594 DenemoObject *
get_point_object(void)595 get_point_object (void)
596 {
597   DenemoProject *gui = Denemo.project;
598   DenemoMovement *si = gui->movement;
599   if (!si->markstaffnum)
600     return NULL;
601   staffnode *curstaff = g_list_nth (si->thescore, si->selection.laststaffmarked - 1);
602   DenemoStaff *laststaff = (DenemoStaff *) curstaff->data;
603   measurenode *lastmeasure = g_list_nth (laststaff->themeasures, si->selection.lastmeasuremarked - 1);
604   objnode *lastobj = g_list_nth (((DenemoMeasure*)lastmeasure->data)->objects, si->selection.lastobjmarked);
605   return lastobj ? ((DenemoObject *) lastobj->data) : NULL;
606 }
607 
608 /**
609  * mark_boundaries_helper
610  * Helper function which marks the boundaries of the
611  * mark
612  *
613  * Inputs
614  * @param si pointer to the DenemoMovement structure
615  * @param mark_staff
616  * @param mark_measure -
617  * @param mark_object -
618  * @param point_staff -
619  * @param point_measure -
620  * @param point_object -
621  * @param type -
622  */
623 static void
mark_boundaries_helper(DenemoMovement * si,gint mark_staff,gint mark_measure,gint mark_object,gint point_staff,gint point_measure,gint point_object,enum drag_selection_type type)624 mark_boundaries_helper (DenemoMovement * si, gint mark_staff, gint mark_measure, gint mark_object, gint point_staff, gint point_measure, gint point_object, enum drag_selection_type type)
625 {
626   if (mark_staff)
627     {
628       si->selection.firststaffmarked = MIN (mark_staff, point_staff);
629       si->selection.laststaffmarked = MAX (mark_staff, point_staff);
630 
631       switch (type)
632         {
633         case NO_DRAG:
634           /* error, really.  */
635           break;
636         case NORMAL_SELECT:
637         case WHOLE_MEASURES:
638           /* I was thinking of handling these with a fallthrough, but
639              the commonality in setting si->selection.firstmeasuremarked and
640              si->selection.lastmeasuremarked caused it not to work out cleanly.  */
641           si->selection.firstmeasuremarked = MIN (mark_measure, point_measure);
642           si->selection.lastmeasuremarked = MAX (mark_measure, point_measure);
643           if (type == NORMAL_SELECT && si->selection.firststaffmarked == si->selection.laststaffmarked)
644             {
645               if (mark_measure < point_measure)
646                 {
647                   si->selection.firstobjmarked = mark_object;
648                   si->selection.lastobjmarked = point_object;
649                 }
650               else if (mark_measure > point_measure)
651                 {
652                   si->selection.firstobjmarked = point_object;
653                   si->selection.lastobjmarked = mark_object;
654                 }
655               else
656                 {               /* Same measure */
657                   si->selection.firstobjmarked = MIN (mark_object, point_object);
658                   si->selection.lastobjmarked = MAX (mark_object, point_object);
659                 }
660             }
661           else
662             {
663               si->selection.firstobjmarked = 0;
664               si->selection.lastobjmarked = G_MAXINT - 1;
665             }
666           break;
667         case WHOLE_STAFFS:
668           si->selection.firstmeasuremarked = 1;
669           si->selection.lastmeasuremarked = g_list_length (si->measurewidths);
670           si->selection.firstobjmarked = 0;
671           si->selection.lastobjmarked = G_MAXINT - 1;
672         }
673     }
674 }
675 
676 
677 /**
678  *  setmark
679  *  Sets the current mark for the start of the buffer
680  *
681  */
682 void
set_mark(GtkAction * action,DenemoScriptParam * param)683 set_mark (GtkAction* action, DenemoScriptParam * param)
684 {
685   DenemoMovement *si = Denemo.project->movement;
686   si->markstaffnum = si->currentstaffnum;
687   si->markmeasurenum = si->currentmeasurenum;
688   si->markcursor_x = si->cursor_x;
689   calcmarkboundaries (si);
690   if(!Denemo.non_interactive)
691     gtk_widget_queue_draw(Denemo.scorearea);
692 }
693 
694 /**
695  *  set_point
696  *  Sets the current cursor position as the end of the selection
697  *
698  */
699 void
set_point(GtkAction * action,DenemoScriptParam * param)700 set_point (GtkAction* action, DenemoScriptParam * param)
701 {
702   DenemoMovement *si = Denemo.project->movement;
703   if (si->markstaffnum)
704     {
705       mark_boundaries_helper (si, si->markstaffnum, si->markmeasurenum, si->markcursor_x, si->currentstaffnum, si->currentmeasurenum, si->cursor_x, NORMAL_SELECT);
706 
707     }
708   if(!Denemo.non_interactive)
709     gtk_widget_queue_draw(Denemo.scorearea);
710 }
711 
712 gboolean
mark_status(void)713 mark_status (void)
714 {
715   return Denemo.project->movement->markstaffnum != 0;
716 }
717 
718 /**
719  * unset_mark
720  * Remove the current mark
721  *
722  */
723 void
unset_mark(GtkAction * action,DenemoScriptParam * param)724 unset_mark (GtkAction* action, DenemoScriptParam * param)
725 {
726   DenemoMovement *si = Denemo.project->movement;
727   si->markstaffnum = 0;
728   calcmarkboundaries (si);
729   if(!Denemo.non_interactive)
730     gtk_widget_queue_draw(Denemo.scorearea);
731 }
732 
733 
734 gboolean
in_selection(DenemoMovement * si)735 in_selection (DenemoMovement * si)
736 {
737   if (si->markstaffnum)
738     {
739       if (si->currentstaffnum >= si->selection.firststaffmarked && si->currentstaffnum <= si->selection.laststaffmarked)
740         {
741           if (si->currentmeasurenum == si->selection.firstmeasuremarked)
742             {
743               if (si->currentmeasurenum == si->selection.lastmeasuremarked)
744                 {
745                   if ((si->cursor_x >= si->selection.firstobjmarked) && (si->cursor_x <= si->selection.lastobjmarked))
746                     return TRUE;
747                   else
748                     return FALSE;
749                 }
750               if (si->currentmeasurenum < si->selection.lastmeasuremarked)
751                 {
752                   if (si->cursor_x >= si->selection.firstobjmarked)
753                     return TRUE;
754                   return FALSE;
755                 }
756             }
757           if (si->currentmeasurenum > si->selection.firstmeasuremarked)
758             {
759               if (si->currentmeasurenum == si->selection.lastmeasuremarked)
760                 {
761                   if ((si->cursor_x <= si->selection.lastobjmarked))
762                     return TRUE;
763                   else
764                     return FALSE;
765                 }
766               if (si->currentmeasurenum < si->selection.lastmeasuremarked)
767                 return TRUE;
768             }
769         }
770     }
771   return FALSE;
772 }
773 
774 /* save/restore selection */
775 static gint firststaff;
776 static gint laststaff;
777 static gint firstobj;
778 static gint lastobj;
779 static gint firstmeasure;
780 static gint lastmeasure;
781 
782 void
save_selection(DenemoMovement * si)783 save_selection (DenemoMovement * si)
784 {
785   firststaff = si->selection.firststaffmarked;
786   laststaff = si->selection.laststaffmarked;
787   firstobj = si->selection.firstobjmarked;
788   lastobj = si->selection.lastobjmarked;
789   firstmeasure = si->selection.firstmeasuremarked;
790   lastmeasure = si->selection.lastmeasuremarked;
791 }
792 
793 void
restore_selection(DenemoMovement * si)794 restore_selection (DenemoMovement * si)
795 {
796   si->selection.firststaffmarked = firststaff;
797   si->selection.laststaffmarked = laststaff;
798   si->selection.firstobjmarked = firstobj;
799   si->selection.lastobjmarked = lastobj;
800   si->selection.firstmeasuremarked = firstmeasure;
801   si->selection.lastmeasuremarked = lastmeasure;
802 }
803 
804 
805 
806 /**
807  * goto_mark
808  * goto the current mark without changing the selection
809  *
810  *
811  */
812 void
goto_mark(GtkAction * action,DenemoScriptParam * param)813 goto_mark (GtkAction * action, DenemoScriptParam * param)
814 {
815   DenemoScriptParam local_param;
816   local_param.status = TRUE;
817   DenemoMovement *si = Denemo.project->movement;
818   if (!action)
819     ((DenemoScriptParam *) param)->status = si->markstaffnum;
820   else
821     param = &local_param;
822   if (si->markstaffnum)
823     {
824       save_selection (si);
825       set_currentmeasurenum (Denemo.project, si->markmeasurenum);
826       set_currentstaffnum (Denemo.project, si->markstaffnum);
827       while (si->cursor_x < si->markcursor_x && param->status)
828         cursorright (NULL, param);
829       restore_selection (si);
830       if (!action)
831         displayhelper (Denemo.project);
832     }
833 }
834 
835 /**
836  * goto_selection_start
837  * move cursor the first object in the selection without changing the selection
838  *
839  *
840  */
841 void
goto_selection_start(GtkAction * action,DenemoScriptParam * param)842 goto_selection_start (GtkAction * action, DenemoScriptParam * param)
843 {
844   DenemoMovement *si = Denemo.project->movement;
845   if (!action)
846     ((DenemoScriptParam *) param)->status = si->markstaffnum;
847   if (si->markstaffnum)
848     {
849       gint first = si->selection.firstobjmarked;
850       save_selection (si);
851       set_currentmeasurenum (Denemo.project, si->selection.firstmeasuremarked);
852       set_currentstaffnum (Denemo.project, si->selection.firststaffmarked);
853       while (si->cursor_x < first)
854         cursorright (NULL, NULL);
855       restore_selection (si);
856       if (!action)
857         displayhelper (Denemo.project);
858     }
859 }
860 
861 
862 
863 
864 static GSList *positions = NULL;
865 DenemoPosition *
pop_position(void)866 pop_position (void)
867 {
868   DenemoPosition *pos;
869   if (positions)
870     {
871       pos = positions->data;
872       positions = g_slist_delete_link (positions, positions);
873       return pos;
874     }
875   return NULL;
876 }
877 
878 void
get_position(DenemoMovement * si,DenemoPosition * pos)879 get_position (DenemoMovement * si, DenemoPosition * pos)
880 {
881   pos->movement = g_list_index (Denemo.project->movements, si) + 1;
882   pos->staff = si->currentstaffnum;
883   pos->measure = si->currentmeasurenum;
884   pos->object = si->currentobject ? si->cursor_x + 1 : 0;
885   pos->appending = si->cursor_appending;
886   pos->offend = si->cursoroffend;
887   pos->leftmeasurenum = si->leftmeasurenum;
888 }
889 
890 void
push_position(void)891 push_position (void)
892 {
893   DenemoMovement *si = Denemo.project->movement;
894   DenemoPosition *pos = (DenemoPosition *) g_malloc (sizeof (DenemoPosition));
895   get_position (si, pos);
896   if (pos->movement)
897     positions = g_slist_prepend (positions, pos);
898   else
899     g_free (pos);
900   //g_debug("%d %d %d %d \n", pos->movement, pos->staff, pos->measure, pos->object);
901 }
902 
903 static void
push_given_position(DenemoPosition * pos)904 push_given_position (DenemoPosition * pos)
905 {
906   DenemoPosition *position = (DenemoPosition *) g_malloc (sizeof (DenemoPosition));
907   memcpy (position, pos, sizeof (DenemoPosition));
908   positions = g_slist_prepend (positions, position);
909 }
910 
911 /**
912  *  copywrapper
913  *  Wrapper function for the copy command
914  *
915  * @param action pointer to the GTKAction event
916  * @param gui pointer to the DenemoProject structure
917  */
918 void
copywrapper(GtkAction * action,DenemoScriptParam * param)919 copywrapper (GtkAction * action, DenemoScriptParam * param)
920 {
921   DenemoProject *gui = Denemo.project;
922   copytobuffer (gui->movement);
923 }
924 
925 /**
926  * cutwrapper
927  * Wrapper function for the cut command
928  *
929  * @param action pointer to the GTKAction event
930  * @param gui pointer to the DenemoProject structure
931  */
932 void
cutwrapper(GtkAction * action,DenemoScriptParam * param)933 cutwrapper (GtkAction * action, DenemoScriptParam * param)
934 {
935   DenemoProject *gui = Denemo.project;
936   cuttobuffer (gui->movement, TRUE);
937   //check that measurewidths is long enough after cutting empty measures
938   displayhelper (gui);
939 }
940 
941 void
delete_selection(void)942 delete_selection (void)
943 {
944   DenemoProject *gui = Denemo.project;
945   cuttobuffer (gui->movement, FALSE);
946   displayhelper (gui);
947 }
948 
949 /**
950  * pastewrapper
951  * Wrapper function for the paste command
952  *
953  * @param gui pointer to the DenemoProject structure
954  * @param action pointer to the GtkAction event
955  */
956 void
pastewrapper(GtkAction * action,DenemoScriptParam * param)957 pastewrapper (GtkAction * action, DenemoScriptParam * param)
958 {
959   stage_undo (Denemo.project->movement, ACTION_STAGE_END);        //undo is a queue (ie stack) so we push the end first
960   call_out_to_guile ("(DenemoPaste)");
961   //FIXME if not success a ACTION_SCRIPT_ERROR will have been put in the undo queue...
962   stage_undo (Denemo.project->movement, ACTION_STAGE_START);
963 
964   score_status (Denemo.project, TRUE);
965 }
966 
967 
968 /**
969  * saveselwrapper
970  * Wrapper function for the Save selection command
971  *
972  * @param action pointer to the GtkAction event
973  * @param gui pointer to the DenemoProject structure
974  */
975 void
saveselwrapper(GtkAction * action,DenemoScriptParam * param)976 saveselwrapper (GtkAction * action, DenemoScriptParam * param)
977 {
978   DenemoProject *gui = Denemo.project;
979   saveselection (gui->movement);
980 }
981 
982 
983 /**
984  * calcmarkboundaries
985  * Wrapper function for the mark_boundaries_helper function
986  * drag selection type is set to NORMAL_SELECT
987  *
988  * Inputs
989  * scoreinfo - score information
990  */
991 void
calcmarkboundaries(DenemoMovement * si)992 calcmarkboundaries (DenemoMovement * si)
993 {
994   mark_boundaries_helper (si, si->markstaffnum, si->markmeasurenum, si->markcursor_x, si->currentstaffnum, si->currentmeasurenum, si->cursor_x, NORMAL_SELECT);
995 }
996 
997 void
swap_point_and_mark(GtkAction * action,DenemoScriptParam * param)998 swap_point_and_mark (GtkAction * action, DenemoScriptParam * param)
999 {
1000   DenemoMovement *si = Denemo.project->movement;
1001   gint temp = si->currentstaffnum;
1002   si->currentstaffnum = si->markstaffnum;
1003   si->markstaffnum = temp;
1004 
1005   temp = si->currentmeasurenum;
1006   si->currentmeasurenum = si->markmeasurenum;
1007   si->markmeasurenum = temp;
1008 
1009   temp = si->cursor_x;
1010   si->cursor_x = si->markcursor_x;
1011   si->markcursor_x = temp;
1012   setcurrentobject (si, si->cursor_x);
1013   calcmarkboundaries (si);
1014   displayhelper (Denemo.project);
1015 }
1016 
1017 /**
1018  * undowrapper
1019  * Wrapper function for the undo command
1020  *
1021  * Inputs
1022  * data - pointer to the score
1023  * callback_action - unused
1024  * widget - unused
1025  */
1026 void
undowrapper(GtkAction * action,DenemoScriptParam * param)1027 undowrapper (GtkAction * action, DenemoScriptParam * param)
1028 {
1029   DenemoProject *gui = Denemo.project;
1030   undo (gui);
1031   displayhelper (gui);
1032 }
1033 
1034 /**
1035  * redowrapper
1036  * Wrapper function for the redo command
1037  *
1038  * Inputs
1039  * data - pointer to the score
1040  * callback_action - unused
1041  * widget - unused
1042  */
1043 void
redowrapper(GtkAction * action,DenemoScriptParam * param)1044 redowrapper (GtkAction * action, DenemoScriptParam * param)
1045 {
1046   DenemoProject *gui = Denemo.project;
1047   redo (gui);
1048   displayhelper (gui);
1049 }
1050 
1051 /* store the passed object as ACTION_CHANGE undo information */
1052 /* potentially we could optimize the storage of undo information by telescoping changes to the same object when the undo is staged, it would mean keeping a global note of whether the undo is currently staged. We would peek at the head of the queue and if it was an ACTION_CHANGE at the same position we could free the stored object and replace it with the clone created here */
1053 void
store_for_undo_change(DenemoMovement * si,DenemoObject * curobj)1054 store_for_undo_change (DenemoMovement * si, DenemoObject * curobj)
1055 {
1056   if (!si->undo_guard)
1057     {
1058       DenemoUndoData *data = (DenemoUndoData *) g_malloc (sizeof (DenemoUndoData));
1059       data->object = dnm_clone_object (curobj);
1060       get_position (si, &data->position);
1061       data->action = ACTION_CHANGE;
1062       update_undo_info (si, data);
1063     }
1064 }
1065 
1066 void
store_for_undo_measure_insert(DenemoMovement * si,gint staffnum,gint measurenum)1067 store_for_undo_measure_insert (DenemoMovement * si, gint staffnum, gint measurenum)
1068 {
1069   if (!si->undo_guard)
1070     {
1071       DenemoUndoData *data = (DenemoUndoData *) g_malloc (sizeof (DenemoUndoData));
1072       data->position.staff = staffnum;
1073       data->position.measure = measurenum + 1;
1074       data->action = ACTION_MEASURE_CREATE;
1075       update_undo_info (si, data);
1076     }
1077 }
1078 
1079 static void
free_chunk(DenemoUndoData * chunk)1080 free_chunk (DenemoUndoData * chunk)
1081 {
1082   g_debug ("free %d\n", chunk->action);
1083   switch (chunk->action)
1084     {
1085     case ACTION_STAGE_START:
1086     case ACTION_STAGE_END:
1087     case ACTION_SCRIPT_ERROR:
1088       return;                   //statically allocated
1089 
1090     case ACTION_INSERT:
1091     case ACTION_DELETE:
1092     case ACTION_CHANGE:
1093       freeobject (chunk->object);
1094       g_free (chunk);
1095       break;
1096 
1097     case ACTION_MEASURE_CREATE:
1098     case ACTION_MEASURE_REMOVE:
1099       g_free (chunk);
1100       break;
1101     case ACTION_SNAPSHOT:
1102       g_warning ("Snapshot free is not implemented");
1103       g_free (chunk);
1104       break;
1105     default:
1106       g_warning ("Unknown type of undo data %d", chunk->action);
1107     }
1108 }
1109 
1110 
1111 
1112 static DenemoUndoData ActionStageStart = { ACTION_STAGE_START };
1113 static DenemoUndoData ActionStageEnd = { ACTION_STAGE_END };
1114 static DenemoUndoData ActionScriptError = { ACTION_SCRIPT_ERROR };
1115 
1116 void
stage_undo(DenemoMovement * si,action_type type)1117 stage_undo (DenemoMovement * si, action_type type)
1118 {
1119   switch (type)
1120     {
1121     case ACTION_STAGE_START:
1122       {
1123         if (g_queue_is_empty (si->undodata))
1124           return;
1125         DenemoUndoData *chunk = g_queue_peek_head (si->undodata);
1126         if (chunk->action == ACTION_STAGE_END)
1127           {
1128             chunk = g_queue_pop_head (si->undodata);
1129             // free_chunk(chunk); not needed, is static anyway
1130             //g_debug("Script did not need undoing");
1131           }
1132         else
1133           update_undo_info (si, &ActionStageStart);
1134       }
1135       break;
1136     case ACTION_STAGE_END:
1137       update_undo_info (si, &ActionStageEnd);
1138       break;
1139     case ACTION_SCRIPT_ERROR:
1140       update_undo_info (si, &ActionScriptError);
1141       break;
1142     default:
1143       g_warning ("Unknown undo action %d will not be stored", type);
1144     }
1145 }
1146 
1147 //return a string describing the top of the undo stack, or one below if stage start.
1148 // caller must g_free
1149 gchar *
get_last_change(DenemoMovement * si)1150 get_last_change (DenemoMovement * si)
1151 {
1152   DenemoUndoData *last = g_queue_peek_head (si->undodata);
1153   gint n = 0;
1154   while (last && ((last->action == ACTION_STAGE_START) || (last->action == ACTION_STAGE_END)))
1155     last = g_queue_peek_nth (si->undodata, ++n);
1156   if (last == NULL)
1157     return NULL;
1158 
1159   switch (last->action)
1160     {
1161     case ACTION_SNAPSHOT:
1162       return g_strdup_printf ("Snapshot (e.g. measure delete, cut, paste and sadly many other things ... ");
1163       break;
1164     case ACTION_INSERT:
1165       return g_strdup_printf ("Insert the object at staff %d measure %d position %d; ", last->position.staff, last->position.measure, last->position.object + 1);
1166     case ACTION_DELETE:
1167       return g_strdup_printf ("Deleted a %s at staff %d measure %d position %d; ", DenemoObjTypeNames[((DenemoObject *) last->object)->type], last->position.staff, last->position.measure, last->position.object);
1168       break;
1169     case ACTION_CHANGE:
1170       return g_strdup_printf ("Change %s at staff %d measure %d position %d; ", DenemoObjTypeNames[((DenemoObject *) last->object)->type], last->position.staff, last->position.measure, last->position.object);
1171       break;
1172     case ACTION_MEASURE_CREATE:
1173       return g_strdup_printf ("Create %s; at staff %d measure %d position %d; ", DenemoObjTypeNames[((DenemoObject *) last->object)->type], last->position.staff, last->position.measure, last->position.object);
1174       break;
1175     case ACTION_MEASURE_REMOVE:
1176       return g_strdup_printf ("Remove %s; at staff %d measure %d position %d; ", DenemoObjTypeNames[((DenemoObject *) last->object)->type], last->position.staff, last->position.measure, last->position.object);
1177     case ACTION_NOOP:
1178       return g_strdup_printf ("No-op; ");
1179       break;
1180     default:
1181       return g_strdup_printf ("Unknown action %d\n", last->action);
1182     }
1183 
1184 }
1185 
1186 // snapshot the current movement for undo
1187 gboolean
take_snapshot(void)1188 take_snapshot (void)
1189 {
1190   if (!Denemo.project->movement->undo_guard)
1191     {
1192       DenemoUndoData *chunk;
1193       chunk = (DenemoUndoData *) g_malloc (sizeof (DenemoUndoData));
1194       chunk->object = (DenemoObject *) clone_movement (Denemo.project->movement);
1195       //fix up somethings...
1196       get_position (Denemo.project->movement, &chunk->position);
1197       chunk->position.appending = 0;
1198       chunk->action = ACTION_SNAPSHOT;
1199       update_undo_info (Denemo.project->movement, chunk);
1200 
1201       return TRUE;
1202     }
1203   else
1204     return FALSE;
1205 }
1206 
1207 static void
print_queue(gchar * msg,GQueue * q)1208 print_queue (gchar * msg, GQueue * q)
1209 {
1210   GList *g;
1211   g_debug ("%s", msg);
1212   for (g = q->head; g; g = g->next)
1213     {
1214       DenemoUndoData *chunk = g->data;
1215       switch (chunk->action)
1216         {
1217         case ACTION_STAGE_START:
1218           g_debug ("[");
1219           break;
1220         case ACTION_STAGE_END:
1221           g_debug ("]\n");
1222           break;
1223         case ACTION_SNAPSHOT:
1224           g_debug ("Snapshot; ");
1225           break;
1226         case ACTION_INSERT:
1227           g_debug ("Ins; ");
1228           break;
1229         case ACTION_DELETE:
1230           g_debug ("Del %s; ", DenemoObjTypeNames[((DenemoObject *) chunk->object)->type]);
1231           break;
1232         case ACTION_CHANGE:
1233           g_debug ("Chn %s; ", DenemoObjTypeNames[((DenemoObject *) chunk->object)->type]);
1234           break;
1235         case ACTION_MEASURE_CREATE:
1236           g_debug ("Create; ");
1237           break;
1238         case ACTION_MEASURE_REMOVE:
1239           g_debug ("Remove; ");
1240           break;
1241         case ACTION_NOOP:
1242           g_debug ("No-op; ");
1243           break;
1244         default:
1245           g_debug ("Unknown action %d\n", chunk->action);
1246 
1247         }
1248     }
1249   g_debug ("End queue");
1250 }
1251 
1252 
1253 static gboolean
position_for_chunk(DenemoProject * gui,DenemoUndoData * chunk)1254 position_for_chunk (DenemoProject * gui, DenemoUndoData * chunk)
1255 {
1256   DenemoScriptParam param;
1257   param.status = TRUE;
1258   //g_debug("undo guard before %d level is %d\n undo action is %d\n",  gui->movement->undo_guard, gui->undo_level, chunk->action);
1259 
1260   switch (chunk->action)
1261     {
1262     case ACTION_CHANGE:
1263       if (chunk->position.object == 0)
1264         return FALSE;           //Cannot undo a change in an empty measure=>undo queue is corrupt
1265       //FALL THRU
1266     case ACTION_INSERT:
1267     case ACTION_DELETE:
1268     case ACTION_MEASURE_CREATE:        //this creates an (blank)measure
1269     case ACTION_MEASURE_REMOVE:        //this is the action that removes a blank measure at pos
1270       {
1271         push_given_position (&chunk->position);
1272         PopPosition (NULL, &param);
1273       }
1274       break;
1275     case ACTION_NOOP:
1276       break;
1277     default:
1278       break;
1279     }
1280   return param.status;
1281 }
1282 
1283 //Takes the action needed for one chunk of undo/redo data
1284 
1285 static void
action_chunk(DenemoProject * gui,DenemoUndoData ** pchunk)1286 action_chunk (DenemoProject * gui, DenemoUndoData ** pchunk)
1287 {
1288   DenemoUndoData *chunk = *pchunk;
1289   switch (chunk->action)
1290     {
1291     case ACTION_MEASURE_CREATE:
1292       {
1293         //delete the empty measure in the chunk->position.staff at measure number chunk->position->object
1294         dnm_deletemeasure (gui->movement);
1295         chunk->action = ACTION_MEASURE_REMOVE;
1296         if (chunk->position.measure > 1)
1297           chunk->position.measure--;
1298         else
1299           chunk->action = ACTION_NOOP;
1300 
1301         if (!gui->movement->currentmeasure)
1302           {
1303             g_warning ("position after undo insert Bug in select.c");
1304             position_for_chunk (gui, chunk);
1305             //movetoend(NULL, NULL);
1306           }
1307       }
1308       break;
1309 
1310     case ACTION_MEASURE_REMOVE:
1311       {
1312         //create empty measure in the chunk->position.staff at measure number chunk->position->object
1313         insertmeasureafter (NULL, NULL);
1314 
1315         cache_measure (Denemo.project->movement->currentmeasure);//rather than cache_staff (Denemo.project->movement->currentstaff); //rather than cach_all ();
1316 
1317         chunk->action = ACTION_MEASURE_CREATE;
1318         chunk->position.measure++;
1319         if (!gui->movement->currentmeasure)
1320           {
1321             g_warning ("position after undo insert Bug in select.c");
1322             position_for_chunk (gui, chunk);    //????
1323             //movetoend(NULL, NULL);
1324           }
1325       }
1326       break;
1327 
1328 
1329 
1330     case ACTION_STAGE_START:
1331       gui->undo_level++;
1332 
1333       *pchunk = &ActionStageEnd;
1334       break;
1335     case ACTION_STAGE_END:
1336       gui->undo_level--;
1337       *pchunk = &ActionStageStart;
1338 
1339       break;
1340     case ACTION_SCRIPT_ERROR:
1341       //chunk = &ActionScriptError;
1342       gui->undo_level = 0;
1343       break;
1344 
1345     case ACTION_INSERT:
1346       {
1347         chunk->object = dnm_clone_object (gui->movement->currentobject->data);
1348         dnm_deleteobject (gui->movement);
1349         chunk->action = ACTION_DELETE;
1350       }
1351       break;
1352     case ACTION_DELETE:
1353       {
1354         object_insert (gui, chunk->object);cache_all ();
1355         chunk->action = ACTION_INSERT;
1356         chunk->object = NULL;
1357       }
1358       break;
1359     case ACTION_CHANGE:
1360       {
1361         //FIXME guard against a corrupt undo queue here by checking  if(gui->movement->currentobject) {
1362         DenemoObject *temp = gui->movement->currentobject->data;
1363         gui->movement->currentobject->data = chunk->object;
1364         chunk->object = temp;cache_all ();
1365       }
1366       break;
1367     case ACTION_SNAPSHOT:
1368       {
1369 
1370         DenemoMovement *si = (DenemoMovement *) chunk->object;
1371         gint initial_guard = gui->movement->undo_guard;
1372         gint initial_changecount = gui->movement->changecount;
1373         gboolean initial_redo_invalid = gui->movement->redo_invalid;
1374         gpointer initial_smf = gui->movement->smf;
1375         // replace gui->movement in gui->movements with si
1376         GList *find = g_list_find (gui->movements, gui->movement);
1377         if (find)
1378           {
1379             find->data = si;
1380             GList *g, *gorig = NULL, *curstaff;
1381             for (curstaff = gui->movement->thescore; curstaff; curstaff = curstaff->next)
1382               {
1383                 DenemoStaff *thestaff = curstaff->data;
1384                 gorig = g = thestaff->verse_views;
1385                 thestaff->verse_views = NULL;
1386                 for (; g; g = g->next)
1387                   {
1388                     gchar *text = get_text_from_view (g->data);
1389                     gtk_widget_destroy (g->data);       //what about its parent??? FIXME
1390                     thestaff->verse_views = g_list_append (thestaff->verse_views, text);
1391                     if (thestaff->current_verse_view == g)
1392                       thestaff->current_verse_view = g_list_last (thestaff->verse_views);
1393                   }
1394 
1395                 {
1396                   GList *direc;
1397                   for (direc = thestaff->staff_directives; direc; direc = direc->next)
1398                     {
1399                       DenemoDirective *directive = direc->data;
1400                       if (directive->widget)
1401                         {
1402                           gtk_widget_destroy (directive->widget);
1403                           directive->widget = NULL;
1404                         }
1405                       //widget_for_staff_directive(directive);
1406                     }
1407                 }
1408                 {
1409                   GList *direc;
1410                   for (direc = thestaff->voice_directives; direc; direc = direc->next)
1411                     {
1412                       DenemoDirective *directive = direc->data;
1413                       if (directive->widget)
1414                         {
1415                           gtk_widget_destroy (directive->widget);
1416                           directive->widget = NULL;
1417                         }
1418                       //widget_for_voice_directive(directive);
1419                     }
1420                 }
1421               }
1422 
1423 
1424             {
1425               GList *direc;
1426               for (direc = gui->movement->movementcontrol.directives; direc; direc = direc->next)
1427                 {
1428                   DenemoDirective *directive = direc->data;
1429                   gtk_widget_destroy (directive->widget);
1430                   directive->widget = NULL;
1431 
1432                 }
1433             }
1434             {
1435               GList *direc;
1436               for (direc = gui->movement->header.directives; direc; direc = direc->next)
1437                 {
1438                   DenemoDirective *directive = direc->data;
1439                   gtk_widget_destroy (directive->widget);
1440                   directive->widget = NULL;
1441                 }
1442             }
1443             {
1444               GList *direc;
1445               for (direc = gui->movement->layout.directives; direc; direc = direc->next)
1446                 {
1447                   DenemoDirective *directive = direc->data;
1448                   gtk_widget_destroy (directive->widget);
1449                   directive->widget = NULL;
1450                 }
1451             }
1452 
1453             g_list_free (gorig);
1454             chunk->object = (DenemoObject *) gui->movement;
1455             //FIXME fix up other values in stored object si?????? voice/staff directive widgets
1456             gui->movement = si;
1457             for (curstaff = si->thescore; curstaff; curstaff = curstaff->next)
1458               {
1459                 DenemoStaff *thestaff = curstaff->data;
1460                 gorig = g = thestaff->verse_views;
1461                 gint curversenum = g_list_position (g, thestaff->current_verse_view);
1462                 thestaff->verse_views = NULL;
1463 
1464                 for (; g; g = g->next)
1465                   {
1466                     add_verse_to_staff (si, thestaff);
1467                     gtk_text_buffer_set_text (gtk_text_view_get_buffer ((GtkTextView *) thestaff->current_verse_view->data), g->data, -1);
1468                     gtk_widget_show (thestaff->current_verse_view->data);
1469                     g_signal_connect (G_OBJECT (gtk_text_view_get_buffer (thestaff->current_verse_view->data)), "changed", G_CALLBACK (lyric_changed_cb), NULL);
1470                   }
1471                 thestaff->current_verse_view = g_list_nth (thestaff->verse_views, curversenum);
1472 
1473 
1474                 {
1475                   GList *direc;
1476                   for (direc = thestaff->staff_directives; direc; direc = direc->next)
1477                     {
1478                       DenemoDirective *directive = direc->data;
1479                       directive->widget = NULL;
1480                       widget_for_staff_directive (directive, thestaff->staffmenu);
1481                     }
1482                 }
1483                 {
1484                   GList *direc;
1485                   for (direc = thestaff->voice_directives; direc; direc = direc->next)
1486                     {
1487                       DenemoDirective *directive = direc->data;
1488                       directive->widget = NULL;
1489                       widget_for_voice_directive (directive, thestaff->voicemenu);
1490                     }
1491                 }
1492                 g_list_free (gorig);
1493               }
1494 
1495 
1496             {
1497               GList *direc;
1498               for (direc = gui->movement->movementcontrol.directives; direc; direc = direc->next)
1499                 {
1500                   DenemoDirective *directive = direc->data;
1501                   directive->widget = NULL;
1502                   widget_for_movementcontrol_directive (directive);
1503                 }
1504             }
1505             {
1506               GList *direc;
1507               for (direc = gui->movement->header.directives; direc; direc = direc->next)
1508                 {
1509                   DenemoDirective *directive = direc->data;
1510                   directive->widget = NULL;
1511                   widget_for_header_directive (directive);
1512                 }
1513             }
1514             {
1515               GList *direc;
1516               for (direc = gui->movement->layout.directives; direc; direc = direc->next)
1517                 {
1518                   DenemoDirective *directive = direc->data;
1519                   directive->widget = NULL;
1520                   widget_for_layout_directive (directive);
1521                 }
1522             }
1523 
1524             cache_all ();
1525 
1526 
1527             gui->movement->smf = initial_smf;
1528             gui->movement->smfsync = -1;      //force recalculation of midi
1529             gui->movement->redo_invalid = initial_redo_invalid;
1530             gui->movement->undo_guard = initial_guard;        //we keep all the guards we had on entry which will be removed when
1531             gui->movement->changecount = initial_changecount;
1532             position_for_chunk (gui, chunk);    //FIXME check return val
1533             if (!gui->movement->currentmeasure)
1534               {
1535                 g_warning ("positioning after snapshot Bug in select.c");
1536                 movetoend (NULL, NULL);
1537               }
1538             gui->movement->currentstaffnum = 1 + g_list_position (gui->movement->thescore, gui->movement->currentstaff);
1539 
1540           }
1541         else
1542           {
1543             g_critical ("Movement does not exist in list of movements");
1544           }
1545       }
1546       break;
1547     case ACTION_NOOP:
1548       break;
1549 
1550     default:
1551       g_warning ("Unexpected undo case ");
1552     }
1553 }
1554 
1555 
1556 
1557 
1558 static void
position_warning(DenemoUndoData * chunk)1559 position_warning (DenemoUndoData * chunk)
1560 {
1561   g_warning ("Could not find position for undotype %d  movement %d staff %d measure %d object %d appending %d offend %d", chunk->action, chunk->position.movement, chunk->position.staff, chunk->position.measure, chunk->position.object, chunk->position.appending, chunk->position.offend);
1562   print_queue ("The undo queue was:", Denemo.project->movement->undodata);
1563   print_queue ("The redo queue was:", Denemo.project->movement->redodata);
1564 }
1565 
1566 static void
warn_no_more_undo(DenemoProject * gui)1567 warn_no_more_undo (DenemoProject * gui)
1568 {
1569   g_warning ("No more undo information at level %d guard %d ... resetting", gui->undo_level, gui->movement->undo_guard);
1570   gui->undo_level = 0;
1571   gui->movement->undo_guard = Denemo.prefs.disable_undo;
1572 }
1573 
1574 
1575 
1576 static void
free_queue(GQueue * queue)1577 free_queue (GQueue * queue)
1578 {
1579   DenemoUndoData *chunk;
1580   //g_debug("before redo queue %p is %d empty\n", queue, g_queue_is_empty(queue));
1581   while ((chunk = (DenemoUndoData *) g_queue_pop_head (queue)))
1582     free_chunk (chunk);
1583   //g_debug("after redo queue %p is %d empty\n", queue, g_queue_is_empty(queue));
1584 }
1585 
1586 /**
1587  * undo
1588  * Undoes an insert, delete change of a DenemoObject, transferring the undo object to the redo queue and switching it between delete/insert
1589  * Undoes other changes to movement by returning to a snapshot.
1590  *
1591  * PARAM gui  the score (why??? this is per movement undo FIXME)
1592  */
1593 static void
undo(DenemoProject * gui)1594 undo (DenemoProject * gui)
1595 {
1596 
1597   DenemoUndoData *chunk = (DenemoUndoData *) g_queue_pop_head (gui->movement->undodata);
1598   if (chunk)
1599     {
1600       gui->movement->undo_guard++;
1601       //g_debug("undo %d\n", chunk->action);
1602       if (position_for_chunk (gui, chunk))
1603         {
1604           action_chunk (gui, &chunk);
1605         }
1606       else
1607         {
1608           position_warning (chunk);
1609           free_queue (gui->movement->redodata);
1610           free_queue (gui->movement->undodata);
1611           warn_no_more_undo (gui);      //returns guard to user preference and sets level 0
1612           return;
1613         }
1614       //g_debug("actioned undo now pushing %d\n", chunk->action);
1615       update_redo_info (gui->movement, chunk);
1616       gui->movement->undo_guard--;
1617       //g_debug("***undo guard after undo %d\n",  gui->movement->undo_guard);
1618       if (gui->undo_level > 0)
1619         undo (gui);
1620       score_status (gui, TRUE);
1621       if (gui->movement->currentmeasurenum > g_list_length (gui->movement->measurewidths))
1622         {
1623           g_warning ("Undo failed to set current measurenum %d out of %d", gui->movement->currentmeasurenum, g_list_length (gui->movement->measurewidths));
1624           gui->movement->currentmeasurenum = g_list_length (gui->movement->measurewidths);
1625         }
1626       //print_queue("Undo, queue: ", gui->movement->undodata);
1627     }
1628   else
1629     warn_no_more_undo (gui);
1630 
1631 }
1632 
1633 
1634 
1635 /**
1636  * redo
1637  * Takes objects from the redo queue and actions them, staged by ACTION_STAGE_START/END
1638  * Once actioned they are transferred back to the undo queue, with inverse transformation
1639  *
1640  * Input
1641  * scoreinfo - score data
1642  */
1643 void
redo(DenemoProject * gui)1644 redo (DenemoProject * gui)
1645 {
1646   DenemoUndoData *chunk = (DenemoUndoData *) g_queue_pop_head (gui->movement->redodata);
1647   if (chunk)
1648     {
1649       //g_debug("Before %s and %d\n", gui->movement->currentobject?"Obj":"noObj", gui->movement->cursor_x);
1650       gui->movement->undo_guard++;
1651       if (position_for_chunk (gui, chunk))
1652         {
1653           action_chunk (gui, &chunk);
1654         }
1655       else
1656         {
1657           position_warning (chunk);
1658 
1659         }
1660       update_undo_info (gui->movement, chunk);
1661       gui->movement->undo_guard--;
1662       //g_debug("After %s and %d\n", gui->movement->currentobject?"Obj":"noObj!!", gui->movement->cursor_x);
1663       if (gui->undo_level > 0)
1664         redo (gui);
1665       score_status (gui, TRUE);
1666     }
1667   else
1668     warn_no_more_undo (gui);
1669   cache_all ();
1670 }
1671 
1672 
1673 
1674 
1675 /**
1676  *  update_undo_info
1677  *
1678  *  Updates the undo list with current operation.
1679  *  Is passed score structure and undo_data structure
1680  *
1681  */
1682 void
update_undo_info(DenemoMovement * si,DenemoUndoData * undo)1683 update_undo_info (DenemoMovement * si, DenemoUndoData * undo)
1684 {
1685 
1686 
1687   //g_debug ("Adding: Action %d at pos %d appending %d\n",  undo->action, undo->position.object, undo->position.appending);
1688 
1689   //  if (g_queue_get_length (si->undodata) == MAX_UNDOS)
1690   //    {
1691   //      tmp = g_queue_pop_tail (si->undodata);//FIXME freeing undo info, especially the object
1692   //      g_warning("Lost undo of %p %p", tmp, tmp->object);
1693   //   }
1694 
1695   g_queue_push_head (si->undodata, undo);
1696   si->redo_invalid = TRUE;
1697   // print_queue("\nUpdate Undo, queue:", si->undodata);
1698 }
1699 
1700 
1701 /**
1702  * update_redo_info
1703  *
1704  *  Updates the redo list with last undo operation.
1705  *  Is passed score structure and redo_data structure
1706  *  @param si pointer to the DenemoMovement structure
1707  *  @param redo redo data structure to prepend to the queue
1708 g */
1709 
1710 void
update_redo_info(DenemoMovement * si,DenemoUndoData * redo)1711 update_redo_info (DenemoMovement * si, DenemoUndoData * redo)
1712 {
1713   //print_queue("Update redo ******************\nUndo queue:\n", si->undodata);
1714   //print_queue("Update redo ******************\nredo queue:\n", si->redodata);
1715 
1716 
1717   if (si->redo_invalid)
1718     {
1719       free_queue (si->redodata);
1720       si->redo_invalid = FALSE;
1721       //g_debug("queue = %p\n", si->redodata);
1722     }
1723   g_queue_push_head (si->redodata, redo);
1724 }
1725