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