1 /*
2  * gnote
3  *
4  * Copyright (C) 2010,2016-2017,2019 Aurimas Cernius
5  * Copyright (C) 2009 Hubert Figuiere
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 
22 
23 
24 #include "sharp/exception.hpp"
25 #include "debug.hpp"
26 #include "notetag.hpp"
27 #include "undo.hpp"
28 
29 namespace gnote {
30 
EditActionGroup(bool start)31   EditActionGroup::EditActionGroup(bool start)
32     : m_start(start)
33   {
34   }
35 
undo(Gtk::TextBuffer *)36   void EditActionGroup::undo(Gtk::TextBuffer*)
37   {
38   }
39 
redo(Gtk::TextBuffer *)40   void EditActionGroup::redo(Gtk::TextBuffer*)
41   {
42   }
43 
merge(EditAction *)44   void EditActionGroup::merge(EditAction*)
45   {
46   }
47 
can_merge(const EditAction *) const48   bool EditActionGroup::can_merge(const EditAction*) const
49   {
50     return false;
51   }
52 
destroy()53   void EditActionGroup::destroy()
54   {
55   }
56 
ChopBuffer(const Glib::RefPtr<Gtk::TextTagTable> & table)57   ChopBuffer::ChopBuffer(const Glib::RefPtr<Gtk::TextTagTable> & table)
58     : Gtk::TextBuffer(table)
59   {
60   }
61 
62 
add_chop(const Gtk::TextIter & start_iter,const Gtk::TextIter & end_iter)63   utils::TextRange ChopBuffer::add_chop(const Gtk::TextIter & start_iter,
64                                         const Gtk::TextIter & end_iter)
65   {
66     int chop_start, chop_end;
67     Gtk::TextIter current_end = end();
68 
69     chop_start = end().get_offset();
70     insert (current_end, start_iter, end_iter);
71     chop_end = end().get_offset();
72 
73     return utils::TextRange (get_iter_at_offset (chop_start),
74                              get_iter_at_offset (chop_end));
75   }
76 
SplitterAction()77   SplitterAction::SplitterAction()
78   {
79   }
80 
split(Gtk::TextIter iter,Gtk::TextBuffer * buffer)81   void SplitterAction::split(Gtk::TextIter iter,
82                              Gtk::TextBuffer * buffer)
83   {
84     Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list = iter.get_tags();
85     for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
86         tag_iter != tag_list.end(); ++tag_iter) {
87       const Glib::RefPtr<Gtk::TextTag>& tag(*tag_iter);
88       NoteTag::ConstPtr noteTag = NoteTag::ConstPtr::cast_dynamic(tag);
89       if (noteTag && !noteTag->can_split()) {
90         Gtk::TextIter start = iter;
91         Gtk::TextIter end = iter;
92 
93         // We only care about enclosing tags
94         if (start.toggles_tag (tag) || end.toggles_tag (tag)) {
95           continue;
96         }
97 
98         start.backward_to_tag_toggle (tag);
99         end.forward_to_tag_toggle (tag);
100         add_split_tag (start, end, tag);
101         buffer->remove_tag(tag, start, end);
102       }
103     }
104   }
105 
106 
add_split_tag(const Gtk::TextIter & start,const Gtk::TextIter & end,const Glib::RefPtr<Gtk::TextTag> tag)107   void SplitterAction::add_split_tag(const Gtk::TextIter & start,
108                                      const Gtk::TextIter & end,
109                                      const Glib::RefPtr<Gtk::TextTag> tag)
110   {
111     TagData data;
112     data.start = start.get_offset();
113     data.end = end.get_offset();
114     data.tag = tag;
115     m_splitTags.push_back(data);
116 
117     /*
118      * The text chop will contain these tags, which means that when
119      * the text is inserted again during redo, it will have the tag.
120      */
121     m_chop.remove_tag(tag);
122   }
123 
124 
get_split_offset() const125   int SplitterAction::get_split_offset() const
126   {
127     int offset = 0;
128     for(auto & iter : m_splitTags) {
129       NoteTag::Ptr noteTag = NoteTag::Ptr::cast_dynamic(iter.tag);
130       if (noteTag->get_image()) {
131         offset++;
132       }
133     }
134     return offset;
135   }
136 
137 
apply_split_tag(Gtk::TextBuffer * buffer)138   void SplitterAction::apply_split_tag(Gtk::TextBuffer * buffer)
139   {
140     for(const auto & tag : m_splitTags) {
141       int offset = get_split_offset ();
142 
143       Gtk::TextIter start = buffer->get_iter_at_offset (tag.start - offset);
144       Gtk::TextIter end = buffer->get_iter_at_offset (tag.end - offset);
145       buffer->apply_tag(tag.tag, start, end);
146     }
147   }
148 
149 
remove_split_tags(Gtk::TextBuffer * buffer)150   void SplitterAction::remove_split_tags(Gtk::TextBuffer *buffer)
151   {
152     for(const auto & tag : m_splitTags) {
153       Gtk::TextIter start = buffer->get_iter_at_offset (tag.start);
154       Gtk::TextIter end = buffer->get_iter_at_offset (tag.end);
155       buffer->remove_tag(tag.tag, start, end);
156     }
157   }
158 
159 
InsertAction(const Gtk::TextIter & start,const Glib::ustring &,int length,const ChopBuffer::Ptr & chop_buf)160   InsertAction::InsertAction(const Gtk::TextIter & start,
161                              const Glib::ustring & , int length,
162                              const ChopBuffer::Ptr & chop_buf)
163     : m_index(start.get_offset() - length)
164     , m_is_paste(length > 1)
165 
166   {
167     Gtk::TextIter index_iter = start.get_buffer()->get_iter_at_offset(m_index);
168     m_chop = chop_buf->add_chop(index_iter, start);
169   }
170 
171 
undo(Gtk::TextBuffer * buffer)172   void InsertAction::undo (Gtk::TextBuffer * buffer)
173   {
174     int tag_images = get_split_offset ();
175 
176     Gtk::TextIter start_iter = buffer->get_iter_at_offset (m_index - tag_images);
177     Gtk::TextIter end_iter = buffer->get_iter_at_offset (m_index - tag_images
178                                                          + m_chop.length());
179     buffer->erase (start_iter, end_iter);
180     buffer->move_mark (buffer->get_insert(),
181                        buffer->get_iter_at_offset (m_index - tag_images));
182     buffer->move_mark (buffer->get_selection_bound (),
183                        buffer->get_iter_at_offset (m_index - tag_images));
184 
185     apply_split_tag (buffer);
186   }
187 
188 
redo(Gtk::TextBuffer * buffer)189   void InsertAction::redo (Gtk::TextBuffer * buffer)
190   {
191     remove_split_tags (buffer);
192 
193     Gtk::TextIter idx_iter = buffer->get_iter_at_offset (m_index);
194     buffer->insert (idx_iter, m_chop.start(), m_chop.end());
195 
196     buffer->move_mark (buffer->get_selection_bound(),
197                        buffer->get_iter_at_offset (m_index));
198     buffer->move_mark (buffer->get_insert(),
199                        buffer->get_iter_at_offset (m_index + m_chop.length()));
200   }
201 
202 
merge(EditAction * action)203   void InsertAction::merge (EditAction * action)
204   {
205     InsertAction * insert = dynamic_cast<InsertAction*>(action);
206     if(insert) {
207       m_chop.set_end(insert->m_chop.end());
208 
209       insert->m_chop.destroy ();
210     }
211   }
212 
213 
can_merge(const EditAction * action) const214   bool InsertAction::can_merge (const EditAction * action) const
215   {
216     const InsertAction * insert = dynamic_cast<const InsertAction*>(action);
217     if (insert == NULL) {
218       return false;
219     }
220 
221     // Don't group text pastes
222     if (m_is_paste || insert->m_is_paste) {
223       return false;
224     }
225 
226     // Must meet eachother
227     if (insert->m_index != (m_index + m_chop.length())) {
228       return false;
229     }
230 
231     // Don't group more than one line (inclusive)
232     if (m_chop.text()[0] == '\n') {
233       return false;
234     }
235 
236     // Don't group more than one word (exclusive)
237     if ((insert->m_chop.text()[0] == ' ')
238         || (insert->m_chop.text()[0] == '\t')) {
239       return false;
240     }
241 
242     return true;
243   }
244 
245 
destroy()246   void InsertAction::destroy ()
247   {
248     m_chop.erase ();
249     m_chop.destroy ();
250   }
251 
252 
253 
EraseAction(const Gtk::TextIter & start_iter,const Gtk::TextIter & end_iter,const ChopBuffer::Ptr & chop_buf)254   EraseAction::EraseAction(const Gtk::TextIter & start_iter,
255                            const Gtk::TextIter & end_iter,
256                            const ChopBuffer::Ptr & chop_buf)
257     : m_start(start_iter.get_offset())
258     , m_end(end_iter.get_offset())
259     , m_is_cut(m_end - m_start > 1)
260   {
261     Gtk::TextIter insert =
262       start_iter.get_buffer()->get_iter_at_mark (start_iter.get_buffer()->get_insert());
263     m_is_forward = (insert.get_offset() <= m_start);
264 
265     m_chop = chop_buf->add_chop (start_iter, end_iter);
266   }
267 
268 
undo(Gtk::TextBuffer * buffer)269   void EraseAction::undo (Gtk::TextBuffer * buffer)
270   {
271     int tag_images = get_split_offset ();
272 
273     Gtk::TextIter start_iter = buffer->get_iter_at_offset (m_start - tag_images);
274     buffer->insert (start_iter, m_chop.start(), m_chop.end());
275 
276     buffer->move_mark (buffer->get_insert(),
277                      buffer->get_iter_at_offset (m_is_forward ? m_start - tag_images
278                                                  : m_end - tag_images));
279     buffer->move_mark (buffer->get_selection_bound(),
280                        buffer->get_iter_at_offset (m_is_forward ? m_end - tag_images
281                                                    : m_start - tag_images));
282 
283     apply_split_tag (buffer);
284   }
285 
286 
redo(Gtk::TextBuffer * buffer)287   void EraseAction::redo (Gtk::TextBuffer * buffer)
288   {
289     remove_split_tags (buffer);
290 
291     Gtk::TextIter start_iter = buffer->get_iter_at_offset (m_start);
292     Gtk::TextIter end_iter = buffer->get_iter_at_offset (m_end);
293     buffer->erase (start_iter, end_iter);
294     buffer->move_mark (buffer->get_insert(),
295                        buffer->get_iter_at_offset (m_start));
296     buffer->move_mark (buffer->get_selection_bound(),
297                        buffer->get_iter_at_offset (m_start));
298   }
299 
300 
merge(EditAction * action)301   void EraseAction::merge (EditAction * action)
302   {
303     EraseAction * erase = dynamic_cast<EraseAction*>(action);
304     if (m_start == erase->m_start) {
305       m_end += erase->m_end - erase->m_start;
306       m_chop.set_end(erase->m_chop.end());
307 
308       // Delete the marks, leave the text
309       erase->m_chop.destroy ();
310     }
311     else {
312       m_start = erase->m_start;
313 
314       Gtk::TextIter chop_start = m_chop.start();
315       m_chop.buffer()->insert(chop_start,
316                                   erase->m_chop.start(),
317                                   erase->m_chop.end());
318 
319       // Delete the marks and text
320       erase->destroy ();
321     }
322   }
323 
324 
can_merge(const EditAction * action) const325   bool EraseAction::can_merge (const EditAction * action) const
326   {
327     const EraseAction * erase = dynamic_cast<const EraseAction *>(action);
328     if (erase == NULL) {
329       return false;
330     }
331 
332     // Don't group separate text cuts
333     if (m_is_cut || erase->m_is_cut) {
334       return false;
335     }
336 
337     // Must meet eachother
338     if (m_start != (m_is_forward ? erase->m_start : erase->m_end)) {
339       return false;
340     }
341 
342     // Don't group deletes with backspaces
343     if (m_is_forward != erase->m_is_forward) {
344       return false;
345   }
346 
347     // Group if something other than text was deleted
348     // (e.g. an email image)
349     if (m_chop.text().empty() || erase->m_chop.text().empty()) {
350       return true;
351     }
352 
353     // Don't group more than one line (inclusive)
354     if (m_chop.text()[0] == '\n') {
355       return false;
356     }
357 
358     // Don't group more than one word (exclusive)
359     if ((erase->m_chop.text()[0] == ' ') || (erase->m_chop.text()[0] == '\t')) {
360       return false;
361     }
362 
363     return true;
364   }
365 
366 
destroy()367   void EraseAction::destroy ()
368   {
369     m_chop.erase ();
370     m_chop.destroy ();
371   }
372 
373 
374 
TagApplyAction(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start,const Gtk::TextIter & end)375   TagApplyAction::TagApplyAction(const Glib::RefPtr<Gtk::TextTag> & tag,
376                                  const Gtk::TextIter & start,
377                                  const Gtk::TextIter & end)
378     : m_tag(tag)
379     , m_start(start.get_offset())
380     , m_end(end.get_offset())
381   {
382   }
383 
384 
undo(Gtk::TextBuffer * buffer)385   void TagApplyAction::undo (Gtk::TextBuffer * buffer)
386   {
387     Gtk::TextIter start_iter, end_iter;
388     start_iter = buffer->get_iter_at_offset (m_start);
389     end_iter = buffer->get_iter_at_offset (m_end);
390 
391     buffer->move_mark (buffer->get_selection_bound(), start_iter);
392     buffer->remove_tag (m_tag, start_iter, end_iter);
393     buffer->move_mark (buffer->get_insert(), end_iter);
394   }
395 
396 
redo(Gtk::TextBuffer * buffer)397   void TagApplyAction::redo (Gtk::TextBuffer * buffer)
398   {
399     Gtk::TextIter start_iter, end_iter;
400     start_iter = buffer->get_iter_at_offset (m_start);
401     end_iter = buffer->get_iter_at_offset (m_end);
402 
403     buffer->move_mark (buffer->get_selection_bound(), start_iter);
404     buffer->apply_tag (m_tag, start_iter, end_iter);
405     buffer->move_mark (buffer->get_insert(), end_iter);
406   }
407 
408 
merge(EditAction *)409   void TagApplyAction::merge (EditAction * )
410   {
411     throw sharp::Exception ("TagApplyActions cannot be merged");
412   }
413 
414 
can_merge(const EditAction *) const415   bool TagApplyAction::can_merge (const EditAction * ) const
416   {
417     return false;
418   }
419 
420 
destroy()421   void TagApplyAction::destroy ()
422   {
423   }
424 
425 
TagRemoveAction(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start,const Gtk::TextIter & end)426   TagRemoveAction::TagRemoveAction(const Glib::RefPtr<Gtk::TextTag> & tag,
427                                    const Gtk::TextIter & start,
428                                    const Gtk::TextIter & end)
429     : m_tag(tag)
430     , m_start(start.get_offset())
431     , m_end(end.get_offset())
432   {
433   }
434 
435 
undo(Gtk::TextBuffer * buffer)436   void TagRemoveAction::undo (Gtk::TextBuffer * buffer)
437   {
438     Gtk::TextIter start_iter, end_iter;
439     start_iter = buffer->get_iter_at_offset (m_start);
440     end_iter = buffer->get_iter_at_offset (m_end);
441 
442     buffer->move_mark (buffer->get_selection_bound(), start_iter);
443     buffer->apply_tag (m_tag, start_iter, end_iter);
444     buffer->move_mark (buffer->get_insert(), end_iter);
445   }
446 
447 
redo(Gtk::TextBuffer * buffer)448   void TagRemoveAction::redo (Gtk::TextBuffer * buffer)
449   {
450     Gtk::TextIter start_iter, end_iter;
451     start_iter = buffer->get_iter_at_offset (m_start);
452     end_iter = buffer->get_iter_at_offset (m_end);
453 
454     buffer->move_mark (buffer->get_selection_bound(), start_iter);
455     buffer->remove_tag (m_tag, start_iter, end_iter);
456     buffer->move_mark (buffer->get_insert(), end_iter);
457   }
458 
459 
merge(EditAction *)460   void TagRemoveAction::merge (EditAction * )
461   {
462     throw sharp::Exception ("TagRemoveActions cannot be merged");
463   }
464 
465 
can_merge(const EditAction *) const466   bool TagRemoveAction::can_merge (const EditAction * ) const
467   {
468     return false;
469   }
470 
471 
destroy()472   void TagRemoveAction::destroy ()
473   {
474   }
475 
476 
ChangeDepthAction(int line,bool direction)477   ChangeDepthAction::ChangeDepthAction(int line, bool direction)
478     : m_line(line)
479     , m_direction(direction)
480   {
481   }
482 
483 
undo(Gtk::TextBuffer * buffer)484   void ChangeDepthAction::undo (Gtk::TextBuffer * buffer)
485   {
486     Gtk::TextIter iter = buffer->get_iter_at_line (m_line);
487 
488     NoteBuffer* note_buffer = dynamic_cast<NoteBuffer*>(buffer);
489     if(note_buffer) {
490       if (m_direction) {
491         note_buffer->decrease_depth (iter);
492       }
493       else {
494         note_buffer->increase_depth (iter);
495       }
496 
497       buffer->move_mark (buffer->get_insert(), iter);
498       buffer->move_mark (buffer->get_selection_bound(), iter);
499     }
500   }
501 
502 
redo(Gtk::TextBuffer * buffer)503   void ChangeDepthAction::redo (Gtk::TextBuffer * buffer)
504   {
505     Gtk::TextIter iter = buffer->get_iter_at_line (m_line);
506 
507     NoteBuffer* note_buffer = dynamic_cast<NoteBuffer*>(buffer);
508     if(note_buffer) {
509       if (m_direction) {
510         note_buffer->increase_depth (iter);
511       }
512       else {
513         note_buffer->decrease_depth (iter);
514       }
515 
516       buffer->move_mark (buffer->get_insert(), iter);
517       buffer->move_mark (buffer->get_selection_bound(), iter);
518     }
519   }
520 
521 
merge(EditAction *)522   void ChangeDepthAction::merge (EditAction * )
523   {
524     throw sharp::Exception ("ChangeDepthActions cannot be merged");
525   }
526 
527 
can_merge(const EditAction *) const528   bool ChangeDepthAction::can_merge (const EditAction * ) const
529   {
530     return false;
531   }
532 
533 
destroy()534   void ChangeDepthAction::destroy ()
535   {
536   }
537 
538 
539 
InsertBulletAction(int offset,int depth)540   InsertBulletAction::InsertBulletAction(int offset, int depth)
541     : m_offset(offset)
542     , m_depth(depth)
543   {
544   }
545 
546 
undo(Gtk::TextBuffer * buffer)547   void InsertBulletAction::undo (Gtk::TextBuffer * buffer)
548   {
549     Gtk::TextIter iter = buffer->get_iter_at_offset (m_offset);
550     iter.forward_line ();
551     iter = buffer->get_iter_at_line (iter.get_line());
552 
553     dynamic_cast<NoteBuffer*>(buffer)->remove_bullet (iter);
554 
555     iter.forward_to_line_end ();
556 
557     buffer->move_mark (buffer->get_insert(), iter);
558     buffer->move_mark (buffer->get_selection_bound(), iter);
559   }
560 
561 
redo(Gtk::TextBuffer * buffer)562   void InsertBulletAction::redo (Gtk::TextBuffer * buffer)
563   {
564     Gtk::TextIter iter = buffer->get_iter_at_offset (m_offset);
565     iter = buffer->insert (iter, "\n");
566 
567     dynamic_cast<NoteBuffer*>(buffer)->insert_bullet(iter, m_depth);
568 
569     buffer->move_mark (buffer->get_insert(), iter);
570     buffer->move_mark (buffer->get_selection_bound(), iter);
571   }
572 
573 
merge(EditAction *)574   void InsertBulletAction::merge (EditAction * )
575   {
576     throw sharp::Exception ("InsertBulletActions cannot be merged");
577   }
578 
579 
can_merge(const EditAction *) const580   bool InsertBulletAction::can_merge (const EditAction * ) const
581   {
582     return false;
583   }
584 
585 
destroy()586   void InsertBulletAction::destroy ()
587   {
588   }
589 
590 
UndoManager(NoteBuffer * buffer)591   UndoManager::UndoManager(NoteBuffer * buffer)
592     : m_frozen_cnt(0)
593     , m_try_merge(false)
594     , m_buffer(buffer)
595     , m_chop_buffer(new ChopBuffer(buffer->get_tag_table()))
596   {
597 
598     buffer->signal_insert_text_with_tags
599       .connect(sigc::mem_fun(*this, &UndoManager::on_insert_text)); // supposedly before
600     buffer->signal_new_bullet_inserted
601       .connect(sigc::mem_fun(*this, &UndoManager::on_bullet_inserted));
602     buffer->signal_change_text_depth
603       .connect(sigc::mem_fun(*this, &UndoManager::on_change_depth));
604     buffer->signal_erase()
605       .connect(sigc::mem_fun(*this, &UndoManager::on_delete_range), false);
606     buffer->signal_apply_tag()
607       .connect(sigc::mem_fun(*this, &UndoManager::on_tag_applied));
608     buffer->signal_remove_tag()
609       .connect(sigc::mem_fun(*this, &UndoManager::on_tag_removed));
610   }
611 
612 
~UndoManager()613   UndoManager::~UndoManager()
614   {
615     clear_action_stack(m_undo_stack);
616     clear_action_stack(m_redo_stack);
617   }
618 
undo_redo(std::stack<EditAction * > & pop_from,std::stack<EditAction * > & push_to,bool is_undo)619   void UndoManager::undo_redo(std::stack<EditAction *> & pop_from,
620                               std::stack<EditAction *> & push_to, bool is_undo)
621   {
622     if (!pop_from.empty()) {
623       bool loop = false;
624       freeze_undo();
625       do {
626         EditAction *action = pop_from.top();
627         pop_from.pop();
628         EditActionGroup *group = dynamic_cast<EditActionGroup*>(action);
629         if(group) {
630           // in case of undo group-end is at the top, for redo it's the opposite
631           loop = is_undo ? !group->is_start() : group->is_start();
632         }
633 
634         undo_redo_action(*action, is_undo);
635 
636         push_to.push(action);
637 
638       } while(loop);
639       thaw_undo();
640 
641       // Lock merges until a new undoable event comes in...
642       m_try_merge = false;
643 
644       if (pop_from.empty() || push_to.size() == 1) {
645         m_undo_changed();
646       }
647     }
648   }
649 
650 
undo_redo_action(EditAction & action,bool is_undo)651   void UndoManager::undo_redo_action(EditAction & action, bool is_undo)
652   {
653     if(is_undo) {
654       action.undo(m_buffer);
655     }
656     else {
657       action.redo(m_buffer);
658     }
659   }
660 
661 
clear_action_stack(std::stack<EditAction * > & stack)662   void UndoManager::clear_action_stack(std::stack<EditAction *> & stack)
663   {
664     while(!stack.empty()) {
665       delete stack.top();
666       stack.pop();
667     }
668   }
669 
clear_undo_history()670   void UndoManager::clear_undo_history()
671   {
672     clear_action_stack(m_undo_stack);
673     clear_action_stack(m_redo_stack);
674     m_undo_changed();
675   }
676 
677 
add_undo_action(EditAction * action)678   void UndoManager::add_undo_action(EditAction * action)
679   {
680     DBG_ASSERT(action, "action is NULL");
681     if (m_try_merge && !m_undo_stack.empty()) {
682       EditAction *top = m_undo_stack.top();
683 
684       if (top->can_merge (action)) {
685         // Merging object should handle freeing
686         // action's resources, if needed.
687         top->merge (action);
688         delete action;
689         return;
690       }
691     }
692 
693     m_undo_stack.push (action);
694 
695     // Clear the redo stack
696     clear_action_stack (m_redo_stack);
697 
698     // Try to merge new incoming actions...
699     m_try_merge = true;
700 
701     // Have undoable actions now
702     if (m_undo_stack.size() == 1) {
703       m_undo_changed();
704     }
705   }
706 
707 
on_insert_text(const Gtk::TextIter & pos,const Glib::ustring & text,int)708   void UndoManager::on_insert_text(const Gtk::TextIter & pos,
709                                    const Glib::ustring & text, int)
710   {
711     if (m_frozen_cnt) {
712       return;
713     }
714 
715     InsertAction *action = new InsertAction (pos,
716                                              text, text.length(),
717                                              m_chop_buffer);
718 
719     /*
720      * If this insert occurs in the middle of any
721      * non-splittable tags, remove them first and
722      * add them to the InsertAction.
723      */
724     m_frozen_cnt++;
725     action->split(pos, m_buffer);
726     m_frozen_cnt--;
727 
728     add_undo_action (action);
729   }
730 
731 
on_delete_range(const Gtk::TextIter & start,const Gtk::TextIter & end)732   void UndoManager::on_delete_range(const Gtk::TextIter & start,
733                                     const Gtk::TextIter & end)
734   {
735     if (m_frozen_cnt) {
736       return;
737     }
738     EraseAction *action = new EraseAction (start, end,
739                                            m_chop_buffer);
740     /*
741      * Delete works a lot like insert here, except
742      * there are two positions in the buffer that
743      * may need to have their tags removed.
744      */
745     m_frozen_cnt++;
746     action->split (start, m_buffer);
747     action->split (end, m_buffer);
748     m_frozen_cnt--;
749 
750     add_undo_action (action);
751   }
752 
753 
on_tag_applied(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start_char,const Gtk::TextIter & end_char)754   void UndoManager::on_tag_applied(const Glib::RefPtr<Gtk::TextTag> & tag,
755                                    const Gtk::TextIter & start_char,
756                                    const Gtk::TextIter & end_char)
757   {
758     if(m_frozen_cnt) {
759       return;
760     }
761     if (NoteTagTable::tag_is_undoable (tag)) {
762       add_undo_action (new TagApplyAction (tag, start_char,
763                                            end_char));
764     }
765   }
766 
767 
on_tag_removed(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start_char,const Gtk::TextIter & end_char)768   void UndoManager::on_tag_removed(const Glib::RefPtr<Gtk::TextTag> & tag,
769                                    const Gtk::TextIter & start_char,
770                                    const Gtk::TextIter & end_char)
771   {
772     if(m_frozen_cnt) {
773       return;
774     }
775     if (NoteTagTable::tag_is_undoable (tag)) {
776       add_undo_action (new TagRemoveAction (tag, start_char,
777                                             end_char));
778     }
779   }
780 
781 
on_change_depth(int line,bool direction)782   void UndoManager::on_change_depth(int line, bool direction)
783   {
784     if(m_frozen_cnt) {
785       return;
786     }
787     add_undo_action(new ChangeDepthAction(line, direction));
788   }
789 
on_bullet_inserted(int offset,int depth)790   void UndoManager::on_bullet_inserted(int offset, int depth)
791   {
792     if(m_frozen_cnt) {
793       return;
794     }
795     add_undo_action(new InsertBulletAction(offset, depth));
796   }
797 
798 
799 }
800 
801