1 /*
2  * gnote
3  *
4  * Copyright (C) 2010-2017,2019-2021 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 #include <algorithm>
24 #include <array>
25 
26 #include <glibmm/i18n.h>
27 #include <glibmm/main.h>
28 
29 #include "config.h"
30 #include "debug.hpp"
31 #include "notebuffer.hpp"
32 #include "notetag.hpp"
33 #include "note.hpp"
34 #include "preferences.hpp"
35 #include "undo.hpp"
36 
37 #include "sharp/xmlreader.hpp"
38 #include "sharp/xmlwriter.hpp"
39 
40 
41 namespace gnote {
42 
43 
44 #define NUM_INDENT_BULLETS 3
45   const gunichar NoteBuffer::s_indent_bullets[NUM_INDENT_BULLETS] = { 0x2022, 0x2218, 0x2023 };
46 
is_bullet(gunichar c)47   bool NoteBuffer::is_bullet(gunichar c)
48   {
49     for (int i = 0; i < NUM_INDENT_BULLETS; ++i)
50       if (c == s_indent_bullets[i])
51         return true;
52 
53     return false;
54   }
55 
get_enable_auto_bulleted_lists() const56   bool NoteBuffer::get_enable_auto_bulleted_lists() const
57   {
58     return m_preferences.enable_auto_bulleted_lists();
59   }
60 
61 
NoteBuffer(const NoteTagTable::Ptr & tags,Note & note_,Preferences & preferences)62   NoteBuffer::NoteBuffer(const NoteTagTable::Ptr & tags, Note & note_, Preferences & preferences)
63     : Gtk::TextBuffer(tags)
64     , m_undomanager(NULL)
65     , m_note(note_)
66     , m_preferences(preferences)
67   {
68     m_undomanager = new UndoManager(this);
69     signal_insert().connect(sigc::mem_fun(*this, &NoteBuffer::text_insert_event));
70     signal_mark_set().connect(sigc::mem_fun(*this, &NoteBuffer::mark_set_event));
71 
72     signal_apply_tag().connect(sigc::mem_fun(*this, &NoteBuffer::on_tag_applied));
73 
74 
75     tags->signal_tag_changed().connect(sigc::mem_fun(*this, &NoteBuffer::on_tag_changed));
76   }
77 
78 
~NoteBuffer()79   NoteBuffer::~NoteBuffer()
80   {
81     delete m_undomanager;
82   }
83 
toggle_active_tag(const Glib::ustring & tag_name)84   void NoteBuffer::toggle_active_tag(const Glib::ustring & tag_name)
85   {
86     DBG_OUT("ToggleTag called for '%s'", tag_name.c_str());
87 
88     Glib::RefPtr<Gtk::TextTag> tag = get_tag_table()->lookup(tag_name);
89     Gtk::TextIter select_start, select_end;
90 
91     if (get_selection_bounds(select_start, select_end)) {
92       // Ignore the bullet character
93       if (find_depth_tag(select_start))
94         select_start.set_line_offset(2);
95 
96       if(is_active_tag(tag)) {
97         remove_tag(tag, select_start, select_end);
98       }
99       else {
100         apply_tag(tag, select_start, select_end);
101       }
102     }
103     else {
104       if(!utils::remove_swap_back(m_active_tags, tag)) {
105         m_active_tags.push_back(tag);
106       }
107     }
108   }
109 
set_active_tag(const Glib::ustring & tag_name)110   void NoteBuffer::set_active_tag (const Glib::ustring & tag_name)
111   {
112     DBG_OUT("SetTag called for '%s'", tag_name.c_str());
113 
114     Glib::RefPtr<Gtk::TextTag> tag = get_tag_table()->lookup(tag_name);
115     Gtk::TextIter select_start, select_end;
116 
117     if (get_selection_bounds(select_start, select_end)) {
118       apply_tag(tag, select_start, select_end);
119     }
120     else {
121       m_active_tags.push_back(tag);
122     }
123   }
124 
remove_active_tag(const Glib::ustring & tag_name)125   void NoteBuffer::remove_active_tag (const Glib::ustring & tag_name)
126   {
127     DBG_OUT("remove_tagcalled for '%s'", tag_name.c_str());
128 
129     Glib::RefPtr<Gtk::TextTag> tag = get_tag_table()->lookup(tag_name);
130     Gtk::TextIter select_start, select_end;
131 
132     if (get_selection_bounds(select_start, select_end)) {
133       remove_tag(tag, select_start, select_end);
134     }
135     else {
136       utils::remove_swap_back(m_active_tags, tag);
137     }
138   }
139 
140 
141   /// <summary>
142   /// Returns the specified DynamicNoteTag if one exists on the TextIter
143   /// or null if none was found.
144   /// </summary>
get_dynamic_tag(const Glib::ustring & tag_name,const Gtk::TextIter & iter)145   DynamicNoteTag::ConstPtr NoteBuffer::get_dynamic_tag (const Glib::ustring  & tag_name,
146                                                         const Gtk::TextIter & iter)
147   {
148     // TODO: Is this variables used, or do we just need to
149     // access iter.Tags to work around a bug?
150     Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> > tag_list = iter.get_tags();
151     for(Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
152         tag_iter != tag_list.end(); ++tag_iter) {
153       const Glib::RefPtr<const Gtk::TextTag> & tag(*tag_iter);
154       DynamicNoteTag::ConstPtr dynamic_tag =  DynamicNoteTag::ConstPtr::cast_dynamic(tag);
155       if (dynamic_tag &&
156           (dynamic_tag->get_element_name() == tag_name)) {
157         return dynamic_tag;
158       }
159     }
160 
161     return DynamicNoteTag::ConstPtr();
162   }
163 
164 
on_tag_applied(const Glib::RefPtr<Gtk::TextTag> & tag1,const Gtk::TextIter & start_char,const Gtk::TextIter & end_char)165   void NoteBuffer::on_tag_applied(const Glib::RefPtr<Gtk::TextTag> & tag1,
166                                   const Gtk::TextIter & start_char, const Gtk::TextIter &end_char)
167   {
168     DepthNoteTag::Ptr dn_tag = DepthNoteTag::Ptr::cast_dynamic(tag1);
169     if (!dn_tag) {
170       // Remove the tag from any bullets in the selection
171       m_undomanager->freeze_undo();
172       Gtk::TextIter iter;
173       for (int i = start_char.get_line(); i <= end_char.get_line(); i++) {
174         iter = get_iter_at_line(i);
175 
176         if (find_depth_tag(iter)) {
177           Gtk::TextIter next = iter;
178           next.forward_chars(2);
179           remove_tag(tag1, iter, next);
180         }
181       }
182       m_undomanager->thaw_undo();
183     }
184     else {
185       // Remove any existing tags when a depth tag is applied
186       m_undomanager->freeze_undo();
187       Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> > tag_list = start_char.get_tags();
188       for(Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
189           tag_iter != tag_list.end(); ++tag_iter) {
190         const Glib::RefPtr<const Gtk::TextTag> & tag(*tag_iter);
191         DepthNoteTag::ConstPtr dn_tag2 = DepthNoteTag::ConstPtr::cast_dynamic(tag);
192         if (!dn_tag2) {
193           // here it gets hairy. Gtkmm does not implement remove_tag() on a const.
194           // given that Gtk does not have const, I assume I can work that out.
195           remove_tag(Glib::RefPtr<Gtk::TextTag>::cast_const(tag), start_char, end_char);
196         }
197       }
198       m_undomanager->thaw_undo();
199     }
200   }
201 
202 
is_active_tag(const Glib::ustring & tag_name)203   bool NoteBuffer::is_active_tag(const Glib::ustring & tag_name)
204   {
205     Glib::RefPtr<Gtk::TextTag> tag = get_tag_table()->lookup(tag_name);
206     return is_active_tag(tag);
207   }
208 
209 
is_active_tag(const Glib::RefPtr<Gtk::TextTag> & tag)210   bool NoteBuffer::is_active_tag(const Glib::RefPtr<Gtk::TextTag> & tag)
211   {
212     Gtk::TextIter iter, select_end;
213 
214     if (get_selection_bounds (iter, select_end)) {
215       // Ignore the bullet character and look at the
216       // first character of the list item
217       if (find_depth_tag(iter)) {
218         iter.forward_chars(2);
219       }
220       if(iter.starts_tag(tag) || iter.has_tag(tag)) {
221         // consider tag active only if it applies to the entire selection
222         if (iter.forward_to_tag_toggle(tag)) {
223           return select_end <= iter;
224         }
225         else {
226           // probably reached the end of note
227           return true;
228         }
229       }
230 
231       return false;
232     }
233     else {
234       return (find(m_active_tags.begin(), m_active_tags.end(), tag) != m_active_tags.end());
235     }
236   }
237 
238     // Returns true if the cursor is inside of a bulleted list
is_bulleted_list_active()239   bool NoteBuffer::is_bulleted_list_active()
240   {
241     Glib::RefPtr<Gtk::TextMark> insert_mark = get_insert();
242     Gtk::TextIter iter = get_iter_at_mark(insert_mark);
243     return is_bulleted_list_active(iter);
244   }
245 
is_bulleted_list_active(Gtk::TextIter iter)246   bool NoteBuffer::is_bulleted_list_active(Gtk::TextIter iter)
247   {
248     iter.set_line_offset(0);
249 
250     Glib::RefPtr<Gtk::TextTag> depth = find_depth_tag(iter);
251 
252     return (bool)depth;
253   }
254 
255 
256   // Returns true if the cursor is at a position that can
257     // be made into a bulleted list
can_make_bulleted_list()258   bool NoteBuffer::can_make_bulleted_list()
259   {
260     Glib::RefPtr<Gtk::TextMark> insert_mark = get_insert();
261     Gtk::TextIter iter = get_iter_at_mark(insert_mark);
262 
263     return iter.get_line();
264   }
265 
266   // Apply active_tags to inserted text
text_insert_event(const Gtk::TextIter & pos,const Glib::ustring & text,int bytes)267   void NoteBuffer::text_insert_event(const Gtk::TextIter & pos, const Glib::ustring & text, int bytes)
268   {
269     // Check for bullet paste
270     if(text.size() == 2 && is_bullet(text[0])) {
271       signal_change_text_depth(pos.get_line(), true);
272     }
273     else {
274       // Only apply active tags when typing, not on paste.
275       if (text.size() == 1) {
276         Gtk::TextIter insert_start(pos);
277         insert_start.backward_chars (text.size());
278 
279         m_undomanager->freeze_undo();
280         Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list = insert_start.get_tags();
281         for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
282             tag_iter != tag_list.end(); ++tag_iter) {
283           remove_tag(*tag_iter, insert_start, pos);
284         }
285 
286         for(auto & tag : m_active_tags) {
287           apply_tag(tag, insert_start, pos);
288         }
289         m_undomanager->thaw_undo();
290       }
291       else {
292         DepthNoteTag::Ptr depth_tag;
293         Gtk::TextIter line_start(pos);
294         line_start.backward_chars(text.size());
295         if(line_start.get_line_offset() == 2) {
296           line_start.set_line_offset(0);
297           depth_tag = find_depth_tag(line_start);
298         }
299 
300         if(depth_tag) {
301           for(int i = 0; i < depth_tag->get_depth(); ++i) {
302             signal_change_text_depth(line_start.get_line(), true);
303           }
304         }
305       }
306 
307       signal_insert_text_with_tags(pos, text, bytes);
308     }
309   }
310 
311 
add_new_line(bool soft_break)312   bool NoteBuffer::add_new_line(bool soft_break)
313   {
314     if (!can_make_bulleted_list() || !get_enable_auto_bulleted_lists())
315       return false;
316 
317     Glib::RefPtr<Gtk::TextMark> insert_mark = get_insert();
318     Gtk::TextIter iter = get_iter_at_mark(insert_mark);
319     iter.set_line_offset(0);
320 
321     DepthNoteTag::Ptr prev_depth = find_depth_tag(iter);
322 
323     Gtk::TextIter insert_iter = get_iter_at_mark(insert_mark);
324 
325     // Insert a LINE SEPARATOR character which allows us
326     // to have multiple lines in a single bullet point
327     if (prev_depth && soft_break) {
328       bool at_end_of_line = insert_iter.ends_line();
329       insert_iter = insert(insert_iter, Glib::ustring(1, (gunichar)0x2028));
330 
331       // Hack so that the user sees that what they type
332       // next will appear on a new line, otherwise the
333       // cursor stays at the end of the previous line.
334       if (at_end_of_line) {
335         insert_iter = insert(insert_iter, " ");
336         Gtk::TextIter bound = insert_iter;
337         bound.backward_char();
338         move_mark(get_selection_bound(), bound);
339       }
340 
341       return true;
342 
343       // If the previous line has a bullet point on it we add a bullet
344       // to the new line, unless the previous line was blank (apart from
345       // the bullet), in which case we clear the bullet/indent from the
346       // previous line.
347     }
348     else if (prev_depth) {
349       if(!iter.ends_line()) {
350         iter.forward_to_line_end();
351       }
352 
353       // See if the line was left contentless and remove the bullet
354       // if so.
355       if(iter.get_line_offset() < 3) {
356         Gtk::TextIter start = get_iter_at_line(iter.get_line());
357         Gtk::TextIter end_iter = start;
358         end_iter.forward_to_line_end();
359 
360         if (end_iter.get_line_offset() < 2) {
361           end_iter = start;
362         }
363         else {
364           end_iter = get_iter_at_line_offset(iter.get_line(), 2);
365         }
366 
367         erase(start, end_iter);
368 
369         iter = get_iter_at_mark(insert_mark);
370         insert(iter, "\n");
371       }
372       else {
373         iter = get_iter_at_mark(insert_mark);
374         Gtk::TextIter prev = iter;
375         prev.backward_char();
376 
377         // Remove soft breaks
378         if (prev.get_char() == 0x2028) {
379           iter = erase(prev, iter);
380         }
381 
382         m_undomanager->freeze_undo();
383         int offset = iter.get_offset();
384         insert(iter, "\n");
385 
386         iter = get_iter_at_mark(insert_mark);
387         Gtk::TextIter start = get_iter_at_line(iter.get_line());
388 
389         insert_bullet(start, prev_depth->get_depth());
390         m_undomanager->thaw_undo();
391 
392         signal_new_bullet_inserted(offset, prev_depth->get_depth());
393       }
394 
395       return true;
396     }
397     // Replace lines starting with any numbers of leading spaces
398     // followed by '*' or '-' and then by a space with bullets
399     else if (line_needs_bullet(iter)) {
400       Gtk::TextIter start = get_iter_at_line_offset (iter.get_line(), 0);
401       Gtk::TextIter end_iter = get_iter_at_line_offset (iter.get_line(), 0);
402 
403       // Remove any leading white space
404       while (end_iter.get_char() == ' ') {
405         end_iter.forward_char();
406       }
407       // Remove the '*' or '-' character and the space after
408       end_iter.forward_chars(2);
409 
410       end_iter = erase(start, end_iter);
411       start = end_iter;
412       if (end_iter.ends_line()) {
413         increase_depth(start);
414       }
415       else {
416         increase_depth(start);
417 
418         iter = get_iter_at_mark(insert_mark);
419         int offset = iter.get_offset();
420         insert(iter, "\n");
421 
422         iter = get_iter_at_mark(insert_mark);
423         iter.set_line_offset(0);
424 
425         m_undomanager->freeze_undo();
426         insert_bullet (iter, 0);
427         m_undomanager->thaw_undo();
428 
429         signal_new_bullet_inserted(offset, 0);
430       }
431 
432       return true;
433     }
434 
435     return false;
436   }
437 
438 
439   // Returns true if line starts with any numbers of leading spaces
440   // followed by '*' or '-' and then by a space
line_needs_bullet(Gtk::TextIter iter)441   bool NoteBuffer::line_needs_bullet(Gtk::TextIter iter)
442   {
443     while (!iter.ends_line()) {
444       switch (iter.get_char()) {
445       case ' ':
446         iter.forward_char();
447         break;
448       case '*':
449       case '-':
450         return (get_iter_at_line_offset(iter.get_line(), iter.get_line_offset() + 1).get_char() == ' ');
451       default:
452         return false;
453       }
454     }
455     return false;
456   }
457 
handle_tab(DepthAction depth_action)458   bool NoteBuffer::handle_tab(DepthAction depth_action)
459   {
460     // if we have something selected, then tab increases ident for selected lines
461     Gtk::TextIter start, end;
462     if(get_selection_bounds(start, end)) {
463       start.set_line_offset(0);
464       for(int end_line = end.get_line(); start.get_line() <= end_line;) {
465         (*this.*depth_action)(start);
466         if(!start.forward_line()) {
467           break;
468         }
469       }
470       return true;
471     }
472     else {
473       Glib::RefPtr<Gtk::TextMark> insert_mark = get_insert();
474       Gtk::TextIter iter = get_iter_at_mark(insert_mark);
475       iter.set_line_offset(0);
476 
477       DepthNoteTag::Ptr depth = find_depth_tag(iter);
478 
479       // If the cursor is at a line with a depth and a tab has been
480       // inserted then we increase the indent depth of that line.
481       if (depth) {
482         (*this.*depth_action)(iter);
483         return true;
484       }
485     }
486     return false;
487   }
488 
489   // Returns true if the depth of the line was increased
add_tab()490   bool NoteBuffer::add_tab()
491   {
492     return handle_tab(&NoteBuffer::increase_depth);
493   }
494 
495 
496   // Returns true if the depth of the line was decreased
remove_tab()497   bool NoteBuffer::remove_tab()
498   {
499     return handle_tab(&NoteBuffer::decrease_depth);
500   }
501 
502 
503   // Returns true if a bullet had to be removed
504     // This is for the Delete key not Backspace
delete_key_handler()505   bool NoteBuffer::delete_key_handler()
506   {
507     // See if there is a selection
508     Gtk::TextIter start;
509     Gtk::TextIter end_iter;
510 
511     bool selection = get_selection_bounds(start, end_iter);
512 
513     if (selection) {
514       augment_selection(start, end_iter);
515       erase(start, end_iter);
516       m_note.data().set_cursor_position(get_insert()->get_iter().get_offset());
517       m_note.data().set_selection_bound_position(NoteData::s_noPosition);
518       return true;
519     }
520     else if (start.ends_line() && start.get_line() < get_line_count()) {
521       Gtk::TextIter next = get_iter_at_line (start.get_line() + 1);
522       end_iter = start;
523       if(is_bulleted_list_active() || is_bulleted_list_active(next)) {
524         end_iter.forward_chars(3);
525       }
526       else {
527         end_iter.forward_chars(1);
528       }
529 
530       DepthNoteTag::Ptr depth = find_depth_tag(next);
531 
532       if (depth) {
533         erase(start, end_iter);
534         return true;
535       }
536     }
537     else {
538       Gtk::TextIter next = start;
539 
540       if (next.get_line_offset() != 0)
541         next.forward_char();
542 
543       DepthNoteTag::Ptr depth = find_depth_tag(start);
544       DepthNoteTag::Ptr nextDepth = find_depth_tag(next);
545       if (depth || nextDepth) {
546         decrease_depth (start);
547         return true;
548       }
549     }
550 
551     return false;
552   }
553 
554 
backspace_key_handler()555   bool NoteBuffer::backspace_key_handler()
556   {
557     Gtk::TextIter start;
558     Gtk::TextIter end_iter;
559 
560     bool selection = get_selection_bounds(start, end_iter);
561 
562     DepthNoteTag::Ptr depth = find_depth_tag(start);
563 
564     if (selection) {
565       augment_selection(start, end_iter);
566       erase(start, end_iter);
567       m_note.data().set_cursor_position(get_insert()->get_iter().get_offset());
568       m_note.data().set_selection_bound_position(NoteData::s_noPosition);
569       return true;
570     }
571     else {
572       // See if the cursor is inside or just after a bullet region
573       // ie.
574       // |* lorum ipsum
575       //  ^^^
576       // and decrease the depth if it is.
577 
578       Gtk::TextIter prev = start;
579 
580       if (prev.get_line_offset())
581         prev.backward_chars (1);
582 
583       DepthNoteTag::Ptr prev_depth = find_depth_tag(prev);
584       if (depth || prev_depth) {
585         decrease_depth(start);
586         return true;
587       }
588       else {
589         // See if the cursor is before a soft line break
590         // and remove it if it is. Otherwise you have to
591         // press backspace twice before  it will delete
592         // the previous visible character.
593         prev = start;
594         prev.backward_chars (2);
595         if (prev.get_char() == 0x2028) {
596           Gtk::TextIter end_break = prev;
597           end_break.forward_char();
598           erase(prev, end_break);
599         }
600       }
601     }
602 
603     return false;
604   }
605 
606 
607   // On an InsertEvent we change the selection (if there is one)
608     // so that it doesn't slice through bullets.
check_selection()609   void NoteBuffer::check_selection()
610   {
611     Gtk::TextIter start;
612     Gtk::TextIter end_iter;
613 
614     bool selection = get_selection_bounds(start, end_iter);
615 
616     if (selection) {
617       augment_selection(start, end_iter);
618     }
619     else {
620       // If the cursor is at the start of a bulleted line
621       // move it so it is after the bullet.
622       if ((start.get_line_offset() == 0 || start.get_line_offset() == 1) &&
623           find_depth_tag(start))
624       {
625         start.set_line_offset(2);
626         select_range (start, start);
627       }
628     }
629   }
630 
631 
632   // Change the selection on the buffer taking into account any
633   // bullets that are in or near the seletion
augment_selection(Gtk::TextIter & start,Gtk::TextIter & end_iter)634   void NoteBuffer::augment_selection(Gtk::TextIter & start, Gtk::TextIter & end_iter)
635   {
636     DepthNoteTag::Ptr start_depth = find_depth_tag(start);
637     DepthNoteTag::Ptr end_depth = find_depth_tag(end_iter);
638 
639     Gtk::TextIter inside_end = end_iter;
640     inside_end.backward_char();
641 
642     DepthNoteTag::Ptr inside_end_depth = find_depth_tag(inside_end);
643 
644     // Start inside bullet region
645     if (start_depth) {
646       start.set_line_offset(2);
647       select_range(start, end_iter);
648     }
649 
650     // End inside another bullet
651     if (inside_end_depth) {
652       end_iter.set_line_offset(2);
653       select_range (start, end_iter);
654     }
655 
656     // Check if the End is right before start of bullet
657     if (end_depth) {
658       end_iter.set_line_offset(2);
659       select_range(start, end_iter);
660     }
661   }
662 
663   // Clear active tags, and add any tags which should be applied:
664   // - Avoid the having tags grow frontwords by not adding tags
665   //   which start on the next character.
666   // - Add tags ending on the prior character, to avoid needing to
667   //   constantly toggle tags.
mark_set_event(const Gtk::TextIter &,const Glib::RefPtr<Gtk::TextBuffer::Mark> & mark)668   void NoteBuffer::mark_set_event(const Gtk::TextIter &,const Glib::RefPtr<Gtk::TextBuffer::Mark> & mark)
669   {
670     if (mark != get_insert()) {
671       return;
672     }
673 
674     m_active_tags.clear();
675 
676     Gtk::TextIter iter = get_iter_at_mark(mark);
677 
678     // Add any growable tags not starting on the next character...
679     const Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list(iter.get_tags());
680     for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
681         tag_iter != tag_list.end(); ++tag_iter) {
682       const Glib::RefPtr<Gtk::TextTag> & tag(*tag_iter);
683       if(!iter.starts_tag(tag) && NoteTagTable::tag_is_growable(tag)) {
684         m_active_tags.push_back(tag);
685       }
686     }
687 
688     // Add any growable tags not ending on the prior character..
689     const Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list2(iter.get_toggled_tags(false));
690     for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list2.begin();
691         tag_iter != tag_list2.end(); ++tag_iter) {
692       const Glib::RefPtr<Gtk::TextTag> & tag(*tag_iter);
693       if (!iter.ends_tag(tag) && NoteTagTable::tag_is_growable(tag)) {
694         m_active_tags.push_back(tag);
695       }
696     }
697   }
698 
699 
widget_swap(const NoteTag::Ptr & tag,const Gtk::TextIter & start,const Gtk::TextIter &,bool adding)700   void NoteBuffer::widget_swap (const NoteTag::Ptr & tag, const Gtk::TextIter & start,
701                                 const Gtk::TextIter & /*end*/, bool adding)
702   {
703     if (tag->get_widget() == NULL)
704       return;
705 
706     Gtk::TextIter prev = start;
707     prev.backward_char();
708 
709     WidgetInsertData data;
710     data.buffer = start.get_buffer();
711     data.tag = tag;
712     data.widget = tag->get_widget();
713     data.adding = adding;
714 
715     if (adding) {
716       data.position = start.get_buffer()->create_mark (start, true);
717     }
718     else {
719       data.position = tag->get_widget_location();
720     }
721 
722     m_widget_queue.push(data);
723 
724     if (!m_widget_queue_timeout) {
725       m_widget_queue_timeout = Glib::signal_idle()
726         .connect(sigc::mem_fun(*this, &NoteBuffer::run_widget_queue));
727     }
728   }
729 
730 
run_widget_queue()731   bool NoteBuffer::run_widget_queue()
732   {
733     while(!m_widget_queue.empty()) {
734       const WidgetInsertData & data(m_widget_queue.front());
735       // HACK: This is a quick fix for bug #486551
736       if (data.position) {
737         NoteBuffer::Ptr buffer = NoteBuffer::Ptr::cast_static(data.buffer);
738         Gtk::TextIter iter = buffer->get_iter_at_mark(data.position);
739         Glib::RefPtr<Gtk::TextMark> location = data.position;
740 
741         // Prevent the widget from being inserted before a bullet
742         if (find_depth_tag(iter)) {
743           iter.set_line_offset(2);
744           location = create_mark(data.position->get_name(), iter, data.position->get_left_gravity());
745         }
746 
747         buffer->undoer().freeze_undo();
748 
749         if (data.adding && !data.tag->get_widget_location()) {
750           Glib::RefPtr<Gtk::TextChildAnchor> childAnchor = buffer->create_child_anchor(iter);
751           data.tag->set_widget_location(location);
752           m_note.add_child_widget(childAnchor, data.widget);
753         }
754         else if (!data.adding && data.tag->get_widget_location()) {
755           Gtk::TextIter end_iter = iter;
756           end_iter.forward_char();
757           buffer->erase(iter, end_iter);
758           buffer->delete_mark(location);
759           data.tag->set_widget_location(Glib::RefPtr<Gtk::TextMark>());
760         }
761         buffer->undoer().thaw_undo();
762       }
763       m_widget_queue.pop();
764     }
765 
766 //      m_widget_queue_timeout = 0;
767     return false;
768   }
769 
on_tag_changed(const Glib::RefPtr<Gtk::TextTag> & tag,bool)770   void NoteBuffer::on_tag_changed(const Glib::RefPtr<Gtk::TextTag> & tag, bool)
771   {
772     NoteTag::Ptr note_tag = NoteTag::Ptr::cast_dynamic(tag);
773     if (note_tag) {
774       utils::TextTagEnumerator enumerator(Glib::RefPtr<Gtk::TextBuffer>(this), note_tag);
775       while(enumerator.move_next()) {
776         const utils::TextRange & range(enumerator.current());
777         widget_swap(note_tag, range.start(), range.end(), true);
778       }
779     }
780   }
781 
on_apply_tag(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start,const Gtk::TextIter & end_iter)782   void NoteBuffer::on_apply_tag(const Glib::RefPtr<Gtk::TextTag> & tag,
783                        const Gtk::TextIter & start,  const Gtk::TextIter &end_iter)
784   {
785     Gtk::TextBuffer::on_apply_tag(tag, start, end_iter);
786 
787     NoteTag::Ptr note_tag = NoteTag::Ptr::cast_dynamic(tag);
788     if (note_tag) {
789       widget_swap(note_tag, start, end_iter, true);
790     }
791   }
792 
on_remove_tag(const Glib::RefPtr<Gtk::TextTag> & tag,const Gtk::TextIter & start,const Gtk::TextIter & end_iter)793   void NoteBuffer::on_remove_tag(const Glib::RefPtr<Gtk::TextTag> & tag,
794                                  const Gtk::TextIter & start,  const Gtk::TextIter & end_iter)
795   {
796     NoteTag::Ptr note_tag = NoteTag::Ptr::cast_dynamic(tag);
797     if (note_tag) {
798       widget_swap(note_tag, start, end_iter, false);
799     }
800 
801     Gtk::TextBuffer::on_remove_tag(tag, start, end_iter);
802   }
803 
get_selection() const804   Glib::ustring NoteBuffer::get_selection() const
805   {
806     Gtk::TextIter select_start, select_end;
807     Glib::ustring text;
808 
809     if (get_selection_bounds(select_start, select_end)) {
810       text = get_text(select_start, select_end, false);
811     }
812 
813     return text;
814   }
815 
816 
get_block_extents(Gtk::TextIter & start,Gtk::TextIter & end_iter,int threshold,const Glib::RefPtr<Gtk::TextTag> & avoid_tag)817   void NoteBuffer::get_block_extents(Gtk::TextIter & start, Gtk::TextIter & end_iter,
818                                      int threshold, const Glib::RefPtr<Gtk::TextTag> & avoid_tag)
819   {
820     // Move start and end to the beginning or end of their
821     // respective paragraphs, bounded by some threshold.
822 
823     start.set_line_offset(std::max(0, start.get_line_offset() - threshold));
824 
825     // FIXME: Sometimes I need to access this before it
826     // returns real values.
827     (void)end_iter.get_chars_in_line();
828 
829     if (end_iter.get_chars_in_line() - end_iter.get_line_offset() > (threshold + 1 /* newline */)) {
830       end_iter.set_line_offset(end_iter.get_line_offset() + threshold);
831     }
832     else {
833       end_iter.forward_to_line_end();
834     }
835 
836     if (avoid_tag) {
837       if (start.has_tag(avoid_tag)) {
838         start.backward_to_tag_toggle(avoid_tag);
839       }
840 
841       if (end_iter.has_tag(avoid_tag)) {
842         end_iter.forward_to_tag_toggle(avoid_tag);
843       }
844     }
845   }
846 
toggle_selection_bullets()847   void NoteBuffer::toggle_selection_bullets()
848   {
849     Gtk::TextIter start;
850     Gtk::TextIter end_iter;
851 
852     get_selection_bounds (start, end_iter);
853 
854     start = get_iter_at_line_offset(start.get_line(), 0);
855 
856     bool toggle_on = true;
857     if (find_depth_tag(start)) {
858       toggle_on = false;
859     }
860 
861     int start_line = start.get_line();
862     int end_line = end_iter.get_line();
863 
864     for (int i = start_line; i <= end_line; i++) {
865       Gtk::TextIter curr_line = get_iter_at_line(i);
866       if (toggle_on && !find_depth_tag(curr_line)) {
867         increase_depth(curr_line);
868       }
869       else if (!toggle_on && find_depth_tag(curr_line)) {
870         Gtk::TextIter bullet_end = get_iter_at_line_offset(curr_line.get_line(), 2);
871         erase(curr_line, bullet_end);
872       }
873     }
874   }
875 
876 
change_cursor_depth_directional(bool right)877   void NoteBuffer::change_cursor_depth_directional(bool right)
878   {
879     Gtk::TextIter start;
880     Gtk::TextIter end_iter;
881 
882     get_selection_bounds (start, end_iter);
883 
884     // If we are moving right then:
885     //   RTL => decrease depth
886     //   LTR => increase depth
887     // We choose to increase or decrease the depth
888     // based on the fist line in the selection.
889     bool increase = right;
890     start.set_line_offset(0);
891     DepthNoteTag::Ptr start_depth = find_depth_tag (start);
892 
893     Gtk::TextIter next = start;
894 
895     if (start_depth) {
896       next.forward_chars (2);
897     }
898     else {
899       // Look for the first non-space character on the line
900       // and use that to determine what direction we should go
901       next.forward_sentence_end ();
902       next.backward_sentence_start ();
903     }
904 
905     change_cursor_depth(increase);
906   }
907 
change_cursor_depth(bool increase)908   void NoteBuffer::change_cursor_depth(bool increase)
909   {
910     Gtk::TextIter start;
911     Gtk::TextIter end_iter;
912 
913     get_selection_bounds (start, end_iter);
914 
915     Gtk::TextIter curr_line;
916 
917     int start_line = start.get_line();
918     int end_line = end_iter.get_line();
919 
920     for (int i = start_line; i <= end_line; i++) {
921       curr_line = get_iter_at_line(i);
922       if (increase)
923         increase_depth (curr_line);
924       else
925         decrease_depth (curr_line);
926     }
927   }
928 
929 
insert_bullet(Gtk::TextIter & iter,int depth)930   void NoteBuffer::insert_bullet(Gtk::TextIter & iter, int depth)
931   {
932     NoteTagTable::Ptr note_table = NoteTagTable::Ptr::cast_dynamic(get_tag_table());
933 
934     DepthNoteTag::Ptr tag = note_table->get_depth_tag(depth);
935 
936     Glib::ustring bullet =
937       Glib::ustring(1, s_indent_bullets [depth % NUM_INDENT_BULLETS]) + " ";
938 
939     iter = insert_with_tag (iter, bullet, tag);
940   }
941 
remove_bullet(Gtk::TextIter & iter)942   void NoteBuffer::remove_bullet(Gtk::TextIter & iter)
943   {
944     Gtk::TextIter end_iter;
945     Gtk::TextIter line_end = iter;
946 
947     line_end.forward_to_line_end ();
948 
949     if (line_end.get_line_offset() < 2) {
950       end_iter = get_iter_at_line_offset (iter.get_line(), 1);
951     }
952     else {
953       end_iter = get_iter_at_line_offset (iter.get_line(), 2);
954     }
955 
956     // Go back one more character to delete the \n as well
957     iter = get_iter_at_line (iter.get_line() - 1);
958     iter.forward_to_line_end ();
959 
960     iter = erase(iter, end_iter);
961   }
962 
963 
increase_depth(Gtk::TextIter & start)964   void NoteBuffer::increase_depth(Gtk::TextIter & start)
965   {
966     if (!can_make_bulleted_list())
967       return;
968 
969     Gtk::TextIter end_iter;
970 
971     start = get_iter_at_line_offset (start.get_line(), 0);
972 
973     Gtk::TextIter line_end = get_iter_at_line (start.get_line());
974     line_end.forward_to_line_end ();
975 
976     end_iter = start;
977     end_iter.forward_chars(2);
978 
979     DepthNoteTag::Ptr curr_depth = find_depth_tag (start);
980 
981     undoer().freeze_undo ();
982     if (!curr_depth) {
983       // Insert a brand new bullet
984       Gtk::TextIter next = start;
985       next.forward_sentence_end ();
986       next.backward_sentence_start ();
987 
988       insert_bullet (start, 0);
989     }
990     else {
991       // Remove the previous indent
992       start = erase (start, end_iter);
993 
994       // Insert the indent at the new depth
995       int nextDepth = curr_depth->get_depth() + 1;
996       insert_bullet(start, nextDepth);
997     }
998     undoer().thaw_undo ();
999 
1000     signal_change_text_depth (start.get_line(), true);
1001   }
1002 
decrease_depth(Gtk::TextIter & start)1003   void NoteBuffer::decrease_depth(Gtk::TextIter & start)
1004   {
1005     if (!can_make_bulleted_list())
1006       return;
1007 
1008     Gtk::TextIter end_iter;
1009 
1010     start = get_iter_at_line_offset (start.get_line(), 0);
1011 
1012     Gtk::TextIter line_end = start;
1013     line_end.forward_to_line_end ();
1014 
1015     if ((line_end.get_line_offset() < 2) || start.ends_line()) {
1016       end_iter = start;
1017     }
1018     else {
1019       end_iter = get_iter_at_line_offset (start.get_line(), 2);
1020     }
1021 
1022     DepthNoteTag::Ptr curr_depth = find_depth_tag (start);
1023 
1024     undoer().freeze_undo ();
1025     if (curr_depth) {
1026       // Remove the previous indent
1027       start = erase(start, end_iter);
1028 
1029       // Insert the indent at the new depth
1030       int nextDepth = curr_depth->get_depth() - 1;
1031 
1032       if (nextDepth != -1) {
1033         insert_bullet(start, nextDepth);
1034       }
1035     }
1036     undoer().thaw_undo ();
1037 
1038     signal_change_text_depth (start.get_line(), false);
1039   }
1040 
1041 
find_depth_tag(Gtk::TextIter & iter)1042   DepthNoteTag::Ptr NoteBuffer::find_depth_tag(Gtk::TextIter & iter)
1043   {
1044     DepthNoteTag::Ptr depth_tag;
1045 
1046     Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list = iter.get_tags();
1047     for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
1048         tag_iter != tag_list.end(); ++tag_iter) {
1049       const Glib::RefPtr<Gtk::TextTag> & tag(*tag_iter);
1050       if (NoteTagTable::tag_has_depth (tag)) {
1051         depth_tag = DepthNoteTag::Ptr::cast_dynamic(tag);
1052         break;
1053       }
1054     }
1055 
1056     return depth_tag;
1057   }
1058 
select_note_body()1059   void NoteBuffer::select_note_body()
1060   {
1061     Glib::ustring title = m_note.get_title();
1062     Gtk::TextIter iter = get_iter_at_offset(title.length());
1063     while(isspace(*iter))
1064       iter.forward_char();
1065     move_mark(get_selection_bound(), iter);
1066     move_mark(get_insert(), end());
1067   }
1068 
serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer)1069   Glib::ustring NoteBufferArchiver::serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer)
1070   {
1071     return serialize(buffer, buffer->begin(), buffer->end());
1072   }
1073 
1074 
serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,const Gtk::TextIter & start,const Gtk::TextIter & end)1075   Glib::ustring NoteBufferArchiver::serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,
1076                                             const Gtk::TextIter & start,
1077                                             const Gtk::TextIter & end)
1078   {
1079     sharp::XmlWriter xml;
1080 
1081     serialize(buffer, start, end, xml);
1082     xml.close();
1083     Glib::ustring serializedBuffer = xml.to_string();
1084     // FIXME: there is some sort of attempt to ensure the endline are the
1085     // same on all platforms.
1086     return serializedBuffer;
1087   }
1088 
1089 
write_tag(const Glib::RefPtr<const Gtk::TextTag> & tag,sharp::XmlWriter & xml,bool start)1090   void NoteBufferArchiver::write_tag(const Glib::RefPtr<const Gtk::TextTag> & tag,
1091                                      sharp::XmlWriter & xml, bool start)
1092   {
1093     NoteTag::ConstPtr note_tag = NoteTag::ConstPtr::cast_dynamic(tag);
1094     if (note_tag) {
1095       note_tag->write (xml, start);
1096     }
1097     else if (NoteTagTable::tag_is_serializable (tag)) {
1098       if (start) {
1099         xml.write_start_element ("", tag->property_name().get_value(), "");
1100       }
1101       else {
1102         xml.write_end_element ();
1103       }
1104     }
1105   }
1106 
tag_ends_here(const Glib::RefPtr<const Gtk::TextTag> & tag,const Gtk::TextIter & iter,const Gtk::TextIter & next_iter)1107   bool NoteBufferArchiver::tag_ends_here (const Glib::RefPtr<const Gtk::TextTag> & tag,
1108                                           const Gtk::TextIter & iter,
1109                                           const Gtk::TextIter & next_iter)
1110   {
1111     return (iter.has_tag (tag) && !next_iter.has_tag (tag)) || next_iter.is_end();
1112   }
1113 
1114 
1115   // This is taken almost directly from GAIM.  There must be a
1116   // better way to do this...
serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,const Gtk::TextIter & start,const Gtk::TextIter & end,sharp::XmlWriter & xml)1117   void NoteBufferArchiver::serialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,
1118                                      const Gtk::TextIter & start,
1119                                      const Gtk::TextIter & end, sharp::XmlWriter & xml)
1120   {
1121     std::stack<Glib::RefPtr<const Gtk::TextTag> > tag_stack;
1122     std::stack<Glib::RefPtr<const Gtk::TextTag> > replay_stack;
1123     std::stack<Glib::RefPtr<const Gtk::TextTag> > continue_stack;
1124 
1125     Gtk::TextIter iter = start;
1126     Gtk::TextIter next_iter = start;
1127     next_iter.forward_char();
1128 
1129     bool line_has_depth = false;
1130     int prev_depth_line = -1;
1131     int prev_depth = -1;
1132 
1133     xml.write_start_element ("", "note-content", "");
1134     xml.write_attribute_string ("", "version", "", "0.1");
1135     xml.write_attribute_string("xmlns",
1136                                "link",
1137                                "",
1138                                "http://beatniksoftware.com/tomboy/link");
1139     xml.write_attribute_string("xmlns",
1140                                "size",
1141                                "",
1142                                "http://beatniksoftware.com/tomboy/size");
1143 
1144     // Insert any active tags at start into tag_stack...
1145     Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> > tag_list = start.get_tags();
1146     for(Glib::SListHandle<Glib::RefPtr<const Gtk::TextTag> >::const_iterator tag_iter = tag_list.begin();
1147         tag_iter != tag_list.end(); ++tag_iter) {
1148       const Glib::RefPtr<const Gtk::TextTag> & start_tag(*tag_iter);
1149       if (!start.toggles_tag (start_tag)) {
1150         tag_stack.push (start_tag);
1151         write_tag (start_tag, xml, true);
1152       }
1153     }
1154 
1155     while ((iter != end) && iter.get_char()) {
1156       DepthNoteTag::Ptr depth_tag = NoteBuffer::Ptr::cast_static(buffer)->find_depth_tag (iter);
1157 
1158       // If we are at a character with a depth tag we are at the
1159       // start of a bulleted line
1160       if (depth_tag && iter.starts_line()) {
1161         line_has_depth = true;
1162 
1163         if (iter.get_line() == prev_depth_line + 1) {
1164           // Line part of existing list
1165 
1166           if (depth_tag->get_depth() == prev_depth) {
1167             // Line same depth as previous
1168             // Close previous <list-item>
1169             xml.write_end_element ();
1170 
1171           }
1172           else if (depth_tag->get_depth() > prev_depth) {
1173             // Line of greater depth
1174             xml.write_start_element ("", "list", "");
1175 
1176             for (int i = prev_depth + 2; i <= depth_tag->get_depth(); i++) {
1177               // Start a new nested list
1178               xml.write_start_element ("", "list-item", "");
1179               xml.write_start_element ("", "list", "");
1180             }
1181           }
1182           else {
1183             // Line of lesser depth
1184             // Close previous <list-item>
1185             // and nested <list>s
1186             xml.write_end_element ();
1187 
1188             for (int i = prev_depth; i > depth_tag->get_depth(); i--) {
1189               // Close nested <list>
1190               xml.write_end_element ();
1191               // Close <list-item>
1192               xml.write_end_element ();
1193             }
1194           }
1195         }
1196         else {
1197           // Start of new list
1198           xml.write_start_element ("", "list", "");
1199           for (int i = 1; i <= depth_tag->get_depth(); i++) {
1200             xml.write_start_element ("", "list-item", "");
1201             xml.write_start_element ("", "list", "");
1202           }
1203         }
1204 
1205         prev_depth = depth_tag->get_depth();
1206 
1207         // Start a new <list-item>
1208         write_tag (depth_tag, xml, true);
1209       }
1210 
1211       // Output any tags that begin at the current position
1212       Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list2 = iter.get_tags();
1213       for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list2.begin();
1214           tag_iter != tag_list2.end(); ++tag_iter) {
1215         const Glib::RefPtr<Gtk::TextTag> & tag(*tag_iter);
1216         if(iter.starts_tag(tag)) {
1217           if (!(DepthNoteTag::Ptr::cast_dynamic(tag)) && NoteTagTable::tag_is_serializable(tag)) {
1218             write_tag (tag, xml, true);
1219             tag_stack.push (tag);
1220           }
1221         }
1222       }
1223 
1224       // Reopen tags that continued across indented lines
1225       // or into or out of lines with a depth
1226       while (!continue_stack.empty() &&
1227              ((!depth_tag && iter.starts_line ()) || (iter.get_line_offset() == 1)))
1228       {
1229         Glib::RefPtr<const Gtk::TextTag> continue_tag = continue_stack.top();
1230         continue_stack.pop();
1231 
1232         if (!tag_ends_here (continue_tag, iter, next_iter)
1233             && iter.has_tag (continue_tag))
1234         {
1235           write_tag (continue_tag, xml, true);
1236           tag_stack.push (continue_tag);
1237         }
1238       }
1239 
1240       // Hidden character representing an anchor
1241       if (iter.get_char() == 0xFFFC) {
1242         DBG_OUT("Got child anchor!!!");
1243         if (iter.get_child_anchor()) {
1244           const char * serialize = (const char*)(iter.get_child_anchor()->get_data(Glib::Quark("serialize")));
1245           if (serialize)
1246             xml.write_raw (serialize);
1247         }
1248         // Line Separator character
1249       }
1250       else if (iter.get_char() == 0x2028) {
1251         xml.write_char_entity (0x2028);
1252       }
1253       else if (!depth_tag) {
1254         xml.write_string (Glib::ustring(1, (gunichar)iter.get_char()));
1255       }
1256 
1257       bool end_of_depth_line = line_has_depth && next_iter.ends_line ();
1258 
1259       bool next_line_has_depth = false;
1260       if (iter.get_line() < buffer->get_line_count() - 1) {
1261         Gtk::TextIter next_line = buffer->get_iter_at_line(iter.get_line()+1);
1262         next_line_has_depth =
1263           (bool)NoteBuffer::Ptr::cast_static(buffer)->find_depth_tag (next_line);
1264       }
1265 
1266       bool at_empty_line = iter.ends_line () && iter.starts_line ();
1267 
1268       if (end_of_depth_line ||
1269           (next_line_has_depth && (next_iter.ends_line () || at_empty_line)))
1270       {
1271         // Close all tags in the tag_stack
1272         while (!tag_stack.empty()) {
1273           Glib::RefPtr<const Gtk::TextTag> existing_tag;
1274           existing_tag = tag_stack.top();
1275           tag_stack.pop ();
1276 
1277           // Any tags which continue across the indented
1278           // line are added to the continue_stack to be
1279           // reopened at the start of the next <list-item>
1280           if (!tag_ends_here (existing_tag, iter, next_iter)) {
1281             continue_stack.push (existing_tag);
1282           }
1283 
1284           write_tag (existing_tag, xml, false);
1285         }
1286       }
1287       else {
1288         Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> > tag_list3 = iter.get_tags();
1289         for(Glib::SListHandle<Glib::RefPtr<Gtk::TextTag> >::const_iterator tag_iter = tag_list3.begin();
1290             tag_iter != tag_list3.end(); ++tag_iter) {
1291           const Glib::RefPtr<Gtk::TextTag> & tag(*tag_iter);
1292           if (tag_ends_here (tag, iter, next_iter) &&
1293               NoteTagTable::tag_is_serializable(tag) && !(DepthNoteTag::Ptr::cast_dynamic(tag)))
1294           {
1295             while (!tag_stack.empty()) {
1296               Glib::RefPtr<const Gtk::TextTag> existing_tag = tag_stack.top();
1297               tag_stack.pop();
1298 
1299               if (!tag_ends_here (existing_tag, iter, next_iter)) {
1300                 replay_stack.push (existing_tag);
1301               }
1302 
1303               write_tag (existing_tag, xml, false);
1304             }
1305 
1306             // Replay the replay queue.
1307             // Restart any tags that
1308             // overlapped with the ended
1309             // tag...
1310             while (!replay_stack.empty()) {
1311               Glib::RefPtr<const Gtk::TextTag> replay_tag = replay_stack.top();
1312               replay_stack.pop();
1313               tag_stack.push (replay_tag);
1314 
1315               write_tag (replay_tag, xml, true);
1316             }
1317           }
1318         }
1319       }
1320 
1321       // At the end of the line record that it
1322       // was the last line encountered with a depth
1323       if (end_of_depth_line) {
1324         line_has_depth = false;
1325         prev_depth_line = iter.get_line();
1326       }
1327 
1328       // If we are at the end of a line with a depth and the
1329       // next line does not have a depth line close all <list>
1330       // and <list-item> tags that remain open
1331       if (end_of_depth_line && !next_line_has_depth) {
1332         for (int i = prev_depth; i > -1; i--) {
1333           // Close <list>
1334           xml.write_full_end_element ();
1335           // Close <list-item>
1336           xml.write_full_end_element ();
1337         }
1338 
1339         prev_depth = -1;
1340       }
1341 
1342       iter.forward_char();
1343       next_iter.forward_char();
1344     }
1345 
1346     // Empty any trailing tags left in tag_stack..
1347     while (!tag_stack.empty()) {
1348       Glib::RefPtr<const Gtk::TextTag> tail_tag = tag_stack.top ();
1349       tag_stack.pop();
1350       write_tag (tail_tag, xml, false);
1351     }
1352 
1353     xml.write_end_element (); // </note-content>
1354   }
1355 
1356 
deserialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,const Gtk::TextIter & iter,const Glib::ustring & content)1357   void NoteBufferArchiver::deserialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,
1358                                        const Gtk::TextIter & iter,
1359                                        const Glib::ustring & content)
1360   {
1361     if(!content.empty()) {
1362       // it looks like an empty string does not really make the cut
1363       sharp::XmlReader xml;
1364       xml.load_buffer(content);
1365       deserialize(buffer, iter, xml);
1366     }
1367   }
1368 
1369   struct TagStart
1370   {
TagStartgnote::TagStart1371     TagStart()
1372       : start(0)
1373       {}
1374     int start;
1375     Glib::RefPtr<Gtk::TextTag> tag;
1376   };
1377 
1378 
deserialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,const Gtk::TextIter & start,sharp::XmlReader & xml)1379   void NoteBufferArchiver::deserialize(const Glib::RefPtr<Gtk::TextBuffer> & buffer,
1380                                        const Gtk::TextIter & start,
1381                                        sharp::XmlReader & xml)
1382   {
1383     int offset = start.get_offset();
1384     std::stack<TagStart> tag_stack;
1385     TagStart tag_start;
1386     Glib::ustring value;
1387 
1388     NoteTagTable::Ptr note_table = NoteTagTable::Ptr::cast_dynamic(buffer->get_tag_table());
1389 
1390     int curr_depth = -1;
1391 
1392     // A stack of boolean values which mark if a
1393     // list-item contains content other than another list
1394     // For some reason, std::stack<bool> cause crashes.
1395     std::deque<bool> list_stack;
1396 
1397     try {
1398       while (xml.read ()) {
1399         Gtk::TextIter insert_at;
1400         switch (xml.get_node_type()) {
1401         case XML_READER_TYPE_ELEMENT:
1402           if (xml.get_name() == "note-content")
1403             break;
1404 
1405           tag_start = TagStart();
1406           tag_start.start = offset;
1407 
1408           if (note_table &&
1409               note_table->is_dynamic_tag_registered (xml.get_name())) {
1410             tag_start.tag =
1411               note_table->create_dynamic_tag (xml.get_name());
1412           }
1413           else if (xml.get_name() == "list") {
1414             curr_depth++;
1415             // If we are inside a <list-item> mark off
1416             // that we have encountered some content
1417             if (!list_stack.empty()) {
1418               list_stack.pop_front();
1419               list_stack.push_front(true);
1420             }
1421             break;
1422           }
1423           else if (xml.get_name() == "list-item") {
1424             if (curr_depth >= 0) {
1425               tag_start.tag = note_table->get_depth_tag(curr_depth);
1426               list_stack.push_front (false);
1427             }
1428             else {
1429               ERR_OUT(_("</list> tag mismatch"));
1430             }
1431           }
1432           else {
1433             tag_start.tag = buffer->get_tag_table()->lookup (xml.get_name());
1434           }
1435 
1436           if (NoteTag::Ptr::cast_dynamic(tag_start.tag)) {
1437             NoteTag::Ptr::cast_dynamic(tag_start.tag)->read (xml, true);
1438           }
1439 
1440           if(!xml.is_empty_element()) {
1441             tag_stack.push (tag_start);
1442           }
1443           break;
1444         case XML_READER_TYPE_TEXT:
1445         case XML_READER_TYPE_WHITESPACE:
1446         case XML_READER_TYPE_SIGNIFICANT_WHITESPACE:
1447           insert_at = buffer->get_iter_at_offset (offset);
1448           value = xml.get_value();
1449           buffer->insert (insert_at, value);
1450 
1451           // we need the # of chars *Unicode) and not bytes (ASCII)
1452           // see bug #587070
1453           offset += value.length();
1454 
1455           // If we are inside a <list-item> mark off
1456           // that we have encountered some content
1457           if (!list_stack.empty()) {
1458             list_stack.pop_front ();
1459             list_stack.push_front (true);
1460           }
1461 
1462           break;
1463         case XML_READER_TYPE_END_ELEMENT:
1464           if (xml.get_name() == "note-content")
1465             break;
1466 
1467           if (xml.get_name() == "list") {
1468             curr_depth--;
1469             break;
1470           }
1471 
1472           tag_start = tag_stack.top();
1473           tag_stack.pop();
1474           if (tag_start.tag) {
1475 
1476             Gtk::TextIter apply_start, apply_end;
1477             apply_start = buffer->get_iter_at_offset (tag_start.start);
1478             apply_end = buffer->get_iter_at_offset (offset);
1479 
1480             if (NoteTag::Ptr::cast_dynamic(tag_start.tag)) {
1481               NoteTag::Ptr::cast_dynamic(tag_start.tag)->read (xml, false);
1482             }
1483 
1484             // Insert a bullet if we have reached a closing
1485             // <list-item> tag, but only if the <list-item>
1486             // had content.
1487             DepthNoteTag::Ptr depth_tag = DepthNoteTag::Ptr::cast_dynamic(tag_start.tag);
1488 
1489             if (depth_tag && list_stack.front ()) {
1490               NoteBuffer::Ptr note_buffer = NoteBuffer::Ptr::cast_dynamic(buffer);
1491               // Do not insert bullet if it's already there
1492               // this happens when using double identation in bullet list
1493               if(!note_buffer->find_depth_tag(apply_start)) {
1494                 note_buffer->insert_bullet(apply_start, depth_tag->get_depth());
1495                 buffer->remove_all_tags (apply_start, apply_start);
1496                 offset += 2;
1497               }
1498               list_stack.pop_front();
1499             }
1500             else if (!depth_tag) {
1501               buffer->apply_tag (tag_start.tag, apply_start, apply_end);
1502             }
1503           }
1504           break;
1505         default:
1506           DBG_OUT("Unhandled element %d. Value: '%s'",
1507                   xml.get_node_type(), xml.get_value().c_str());
1508           break;
1509         }
1510       }
1511     }
1512     catch(const std::exception & e) {
1513       ERR_OUT(_("Exception: %s"), e.what());
1514     }
1515   }
1516 
1517 }
1518