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