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, ¶m);
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