2 /******************************************************************************
3 * MODULE : archiver.cpp
4 * DESCRIPTION: manage undo/redo history
5 * COPYRIGHT : (C) 2009 Joris van der Hoeven
6 *******************************************************************************
7 * This software falls under the GNU general public license version 3 or later.
8 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
9 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
10 ******************************************************************************/
12 #include "archiver.hpp"
13 #include "hashset.hpp"
14 #include "iterator.hpp"
17 array
<patch
> singleton (patch p
);
18 static patch
make_compound (array
<patch
> a
);
19 static patch
make_branches (array
<patch
> a
);
20 static hashset
<double> genuine_authors
;
21 static hashset
<pointer
> archs
;
22 static hashset
<pointer
> pending_archs
;
24 /******************************************************************************
25 * Constructors, destructors, printing and announcements
26 ******************************************************************************/
28 archiver_rep::archiver_rep (double author
, path rp2
):
29 archive (make_branches (0)),
30 current (make_compound (0)),
37 undo_obs (undo_observer (this)),
40 archs
->insert ((pointer
) this);
41 attach_observer (subtree (the_et
, rp
), undo_obs
);
42 genuine_authors
->insert (the_author
);
45 archiver_rep::~archiver_rep () {
46 genuine_authors
->remove (the_author
);
47 detach_observer (subtree (the_et
, rp
), undo_obs
);
48 archs
->remove ((pointer
) this);
49 pending_archs
->remove ((pointer
) this);
52 archiver::archiver (double author
, path rp
):
53 rep (tm_new
<archiver_rep
> (author
, rp
)) {}
56 archiver_rep::clear () {
57 archive
= make_branches (0);
58 current
= make_compound (0);
66 archiver_rep::show_all () {
67 cout
<< HRULE
<< archive
<< LF
<< HRULE
<< LF
;
71 archive_announce (archiver_rep
* arch
, modification mod
) {
72 //cout << "Archive " << mod << "\n";
73 ASSERT (arch
->rp
<= mod
->p
, "invalid modification");
74 if (!arch
->versioning
) {
76 pending_archs
->insert ((pointer
) arch
);
81 global_clear_history () {
82 iterator
<pointer
> it
= iterate (archs
);
84 archiver_rep
* arch
= (archiver_rep
*) it
->next();
91 iterator
<pointer
> it
= iterate (pending_archs
);
93 archiver_rep
* arch
= (archiver_rep
*) it
->next();
97 pending_archs
= hashset
<pointer
> ();
100 /******************************************************************************
102 ******************************************************************************/
105 make_compound (array
<patch
> a
) {
106 if (N(a
) == 1) return a
[0];
107 else return patch (false, a
);
111 make_branches (array
<patch
> a
) {
112 if (N(a
) == 1) return a
[0];
113 else return patch (true, a
);
117 append_branches (patch p1
, patch p2
) {
118 return make_branches (append (branches (p1
), branches (p2
)));
122 make_history (patch undo
, patch redo
) {
124 a
<< undo
<< branches (redo
);
125 return make_branches (a
);
130 if (nr_branches (p
) == 0 || nr_children (branch (p
, 0)) != 2) return 0;
136 return max (0, nr_branches (p
) - 1);
141 ASSERT (nr_branches (p
) > 0, "undo part unavailable");
142 return branch (p
, 0);
147 if (nr_branches (p
) == 0) return make_branches (array
<patch
> ());
148 return make_branches (branches (p
, 1, nr_branches (p
)));
153 ASSERT (nr_children (branch (p
, 0)) == 2, "car unavailable")
159 ASSERT (nr_children (branch (p
, 0)) == 2, "cdr unavailable")
163 /******************************************************************************
164 * Internal subroutines
165 ******************************************************************************/
168 archiver_rep::apply (patch p
) {
169 // apply a patch, while disabling versioning during the modifications
170 // cout << "Apply " << p << "\n";
171 ASSERT (is_applicable (p
, the_et
), "invalid history");
172 bool old
= versioning
;
179 archiver_rep::split (patch p1
, patch p2
, patch
& re1
, patch
& re2
) {
180 //cout << "p1= " << p1 << "\n";
181 //cout << "p2= " << p2 << "\n";
182 array
<patch
> a
= branches (p2
);
185 for (int i
=0; i
<N(a
); i
++) {
187 patch q2
= car (a
[i
]);
188 if (get_author (q2
) != the_author
|| !swap (q1
, q2
)) a1
<< a
[i
];
189 else a2
<< patch (q1
, make_future (q2
, cdr (a
[i
])));
191 re1
= make_branches (a1
);
192 re2
= make_branches (a2
);
193 //cout << "re1= " << re1 << "\n";
194 //cout << "re2= " << re2 << "\n";
198 archiver_rep::make_future (patch p1
, patch p2
) {
199 if (nr_branches (p2
) == 0 || get_author (p1
) == the_author
)
200 return patch (p1
, p2
);
202 split (p1
, p2
, re1
, re2
);
203 if (nr_branches (re1
) != 0) re1
= patch (p1
, re1
);
204 return append_branches (re1
, re2
);
208 archiver_rep::expose (patch archive
) {
209 if (nr_undo (archive
) != 0 &&
210 get_author (car (get_undo (archive
))) != the_author
&&
211 nr_undo (cdr (get_undo (archive
))) != 0)
213 patch nx1
= expose (cdr (get_undo (archive
)));
214 if (get_author (car (get_undo (nx1
))) != the_author
) return archive
;
215 patch un1
= car (get_undo (archive
));
216 patch un2
= car (get_undo (nx1
));
217 patch re1
= get_redo (archive
);
218 patch re2
= get_redo (nx1
);
219 patch nx2
= cdr (get_undo (nx1
));
220 patch fut
= make_branches (0);
221 if (nr_branches (re2
) != 0) fut
= make_future (un1
, re2
);
222 if (!swap (un1
, un2
)) return archive
;
223 patch nx
= make_history (patch (un2
, nx2
), make_branches (0));
224 patch un
= patch (un1
, nx
);
225 patch re
= append_branches (re1
, fut
);
226 last_save
= last_autosave
= -1;
227 return make_history (un
, re
);
233 archiver_rep::expose () {
234 archive
= expose (archive
);
238 archiver_rep::normalize () {
239 if (nr_undo (archive
) != 0 && nr_redo (cdr (get_undo (archive
))) != 0) {
240 patch un1
= get_undo (archive
);
241 patch re1
= get_redo (archive
);
242 patch p1
= car (un1
);
243 patch nx1
= cdr (un1
);
244 patch un2
= get_undo (nx1
);
245 patch re2
= get_redo (nx1
);
247 if (get_author (p1
) == the_author
) return;
248 split (p1
, re2
, Re1
, Re2
);
249 patch ar2
= make_history (un2
, Re1
);
250 archive
= make_history (patch (p1
, ar2
), append_branches (re1
, Re2
));
251 last_save
= last_autosave
= -1;
255 /******************************************************************************
256 * Routines concerning the current modifications
257 ******************************************************************************/
260 archiver_rep::add (modification m
) {
261 if (the_owner
!= 0 && the_owner
!= get_author ()) {
262 //cout << "Change " << the_owner << " -> " << get_author () << "\n";
265 else the_owner
= get_author ();
266 modification i
= invert (m
, the_et
);
268 //cout << "Add [" << the_owner << "] " << q << "\n";
269 current
= patch (q
, current
);
273 archiver_rep::start_slave (double a
) {
274 if (the_owner
!= 0 && the_owner
!= get_author ()) {
275 //cout << "Change " << the_owner << " -> " << get_author () << "\n";
278 else the_owner
= get_author ();
280 //cout << "Add [" << the_owner << "] " << q << "\n";
281 current
= patch (q
, current
);
285 archiver_rep::active () {
286 return nr_children (current
) != 0;
290 archiver_rep::has_history () {
291 return nr_undo (archive
) == 1;
295 archiver_rep::cancel () {
297 // cout << "Cancel " << current << "\n";
299 current
= make_compound (0);
305 archiver_rep::confirm () {
307 current
= patch (the_owner
, compactify (current
));
308 //cout << "Confirm " << current << "\n";
309 archive
= patch (current
, archive
);
310 current
= make_compound (0);
313 if (depth
<= last_save
) last_save
= -1;
314 if (depth
<= last_autosave
) last_autosave
= -1;
321 archiver_rep::retract () {
322 if (!has_history ()) return false;
323 if (the_owner
!= 0 && the_owner
!= the_author
) return false;
325 patch un
= car (get_undo (archive
));
326 if (get_author (un
) != the_author
) return false;
327 patch re
= get_redo (archive
);
328 patch nx
= cdr (get_undo (archive
));
329 //cout << "Retract " << un << "\n";
330 if (active ()) current
= compactify (patch (current
, un
));
332 the_owner
= the_author
;
333 if (nr_branches (re
) != 0) {
334 patch q
= invert (current
, the_et
);
337 if (nr_branches (nx
) != 0) nx
= get_undo (nx
);
338 archive
= make_history (nx
, append_branches (re
, get_redo (nx
)));
345 archiver_rep::forget () {
352 /******************************************************************************
353 * Simplification of the history
354 ******************************************************************************/
357 archiver_rep::simplify () {
358 if (has_history () &&
359 nr_undo (cdr (get_undo (archive
))) == 1 &&
360 nr_redo (cdr (get_undo (archive
))) == 0 &&
361 depth
!= last_save
+ 1)
363 patch p1
= car (get_undo (archive
));
364 patch p2
= car (get_undo (cdr (get_undo (archive
))));
365 //cout << "p1= " << p1 << "\n";
366 //cout << "p2= " << p2 << "\n";
367 bool r
= join (p1
, p2
, the_et
);
368 //cout << "pr= " << p1 << "\n";
370 //cout << "\n\nSimplify\n";
372 patch un
= patch (p1
, cdr (get_undo (cdr (get_undo (archive
)))));
373 patch re
= get_redo (archive
);
374 archive
= make_history (un
, re
);
377 if (depth
== last_autosave
+ 1) last_autosave
= -1;
384 /******************************************************************************
386 ******************************************************************************/
389 archiver_rep::undo_possibilities () {
390 return nr_undo (archive
);
394 archiver_rep::redo_possibilities () {
395 return nr_redo (archive
);
399 archiver_rep::undo_one (int i
) {
400 if (active ()) return path ();
401 if (undo_possibilities () != 0) {
402 ASSERT (i
== 0, "index out of range");
403 patch p
= car (get_undo (archive
));
404 //cout << "p= " << p << "\n";
405 ASSERT (is_applicable (p
, the_et
), "history corrupted");
406 patch q
= invert (p
, the_et
);
407 //cout << "q= " << q << "\n";
409 patch re1
= patch (q
, get_redo (archive
));
410 patch nx
= cdr (get_undo (archive
));
411 patch re2
= get_redo (nx
);
412 patch re
= append_branches (re1
, re2
);
413 patch un
= (nr_branches (nx
) == 0? nx
: get_undo (nx
));
414 archive
= make_history (un
, re
);
417 return cursor_hint (q
, the_et
);
423 archiver_rep::redo_one (int i
) {
424 if (active ()) return path ();
425 int n
= redo_possibilities ();
427 ASSERT (i
>= 0 && i
< n
, "index out of range");
428 patch un
= get_undo (archive
);
429 patch re
= get_redo (archive
);
430 patch p
= car (branch (re
, i
));
431 //cout << "p= " << p << "\n";
432 ASSERT (is_applicable (p
, the_et
), "future corrupted");
433 patch q
= invert (p
, the_et
);
434 //cout << "q= " << q << "\n";
436 patch other
= make_branches (append (branches (re
, 0, i
),
437 branches (re
, i
+1, n
)));
438 //cout << "other= " << other << "\n";
439 patch nx
= make_history (un
, other
);
440 archive
= make_history (patch (q
, nx
), cdr (branch (re
, i
)));
441 if (depth
<= last_save
&& i
!= 0) last_save
= -1;
442 if (depth
<= last_autosave
&& i
!= 0) last_autosave
= -1;
446 return cursor_hint (q
, the_et
);
452 archiver_rep::undo (int i
) {
453 if (active ()) return path ();
455 while (undo_possibilities () != 0) {
456 ASSERT (i
== 0, "index out of range");
458 if (get_author (car (get_undo (archive
))) == the_author
)
469 archiver_rep::redo (int i
) {
470 if (active ()) return path ();
473 while (redo_possibilities () != 0) {
474 ASSERT (i
>= 0 && i
< redo_possibilities (), "index out of range");
475 patch re
= branch (get_redo (archive
), i
);
476 bool done
= (get_author (car (re
)) == the_author
);
478 if (done
&& !first
) break;
479 if (nr_redo (archive
) != 1) break;
482 re
= branch (get_redo (archive
), i
);
483 if (get_author (car (re
)) == the_author
) break;
484 if (done
&& genuine_authors
->contains (get_author (car (re
)))) break;
489 /******************************************************************************
490 * Marking blocks for grouped modifications or canceling
491 ******************************************************************************/
494 is_marker (patch p
, double m
, bool birth
) {
495 if (get_type (p
) == PATCH_AUTHOR
)
496 return is_marker (p
[0], m
, birth
);
497 else if (get_type (p
) == PATCH_BIRTH
)
498 return get_author (p
) == m
&& get_birth (p
) == birth
;
503 remove_marker (patch archive
, double m
) {
504 ASSERT (nr_undo (archive
) != 0, "marker not found");
505 if (is_marker (car (get_undo (archive
)), m
, false)) {
506 ASSERT (nr_redo (archive
) == 0, "cannot remove marker");
507 return cdr (get_undo (archive
));
510 patch un
= get_undo (archive
);
511 patch re
= get_redo (archive
);
512 return make_history (patch (car (un
), remove_marker (cdr (un
), m
)), re
);
517 archiver_rep::mark_start (double m
) {
518 //cout << "Mark start " << m << "\n";
526 archiver_rep::mark_end (double m
) {
527 //cout << "Mark end " << m << "\n";
528 archive
= remove_marker (archive
, m
);
535 archiver_rep::mark_cancel (double m
) {
536 //cout << "Mark cancel " << m << "\n";
538 while (nr_undo (archive
) != 0) {
540 if (is_marker (car (get_undo (archive
)), m
, false)) {
541 archive
= remove_marker (archive
, m
);
546 if (get_author (car (get_undo (archive
))) != the_author
) {
547 archive
= remove_marker (archive
, m
);
557 /******************************************************************************
558 * Check changes since last save/autosave
559 ******************************************************************************/
562 archiver_rep::corrected_depth () {
563 // NOTE : fix depth due to presence of marker
564 // FIXME: implement a more robust check for conformity with saved state
565 if (nr_undo (archive
) == 0) return depth
;
566 patch p
= car (get_undo (archive
));
567 if (get_type (p
) == PATCH_AUTHOR
) p
= p
[0];
568 if (get_type (p
) == PATCH_BIRTH
&& get_birth (p
) == false) return depth
- 1;
573 archiver_rep::require_save () {
578 archiver_rep::notify_save () {
579 last_save
= corrected_depth ();
583 archiver_rep::conform_save () {
584 return last_save
== corrected_depth ();
588 archiver_rep::require_autosave () {
593 archiver_rep::notify_autosave () {
594 last_autosave
= depth
;
598 archiver_rep::conform_autosave () {
599 return last_autosave
== depth
;