1 /* This file is part of
2 * ======================================================
4 * LyX, The Document Processor
6 * Copyright 1995 Matthias Ettrich
7 * Copyright 1995-2002 The LyX Team.
9 * ====================================================== */
15 #include "paragraph.h"
16 #include "BufferView.h"
17 #include "funcrequest.h"
20 #include "bufferparams.h"
22 #include "ParagraphParameters.h"
25 #include "support/lstrings.h"
26 #include "frontends/LyXView.h"
27 #include "frontends/screen.h"
28 #include "frontends/WorkArea.h"
29 #include "insets/insetspecialchar.h"
30 #include "insets/insettext.h"
31 #include "undo_funcs.h"
35 extern string current_layout;
40 void cursorPrevious(LyXText * text, BufferView * bv)
42 if (!text->cursor.row()->previous()) {
43 if (text->first_y > 0) {
44 int new_y = bv->text->first_y - bv->workarea().workHeight();
45 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
46 bv->updateScrollbar();
51 int y = text->first_y;
52 Row * cursorrow = text->cursor.row();
54 text->setCursorFromCoordinates(bv, text->cursor.x_fix(), y);
58 if (cursorrow == bv->text->cursor.row()) {
59 // we have a row which is higher than the workarea so we leave the
60 // cursor on the start of the row and move only the draw up as soon
61 // as we move the cursor or do something while inside the row (it may
62 // span several workarea-heights) we'll move to the top again, but this
63 // is better than just jump down and only display part of the row.
64 new_y = bv->text->first_y - bv->workarea().workHeight();
66 if (text->inset_owner) {
67 new_y = bv->text->cursor.iy()
68 + bv->theLockingInset()->insetInInsetY() + y
69 + text->cursor.row()->height()
70 - bv->workarea().workHeight() + 1;
72 new_y = text->cursor.y()
73 - text->cursor.row()->baseline()
74 + text->cursor.row()->height()
75 - bv->workarea().workHeight() + 1;
78 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
79 if (text->cursor.row()->previous()) {
81 text->setCursor(bv, cur, text->cursor.row()->previous()->par(),
82 text->cursor.row()->previous()->pos(), false);
83 if (cur.y() > text->first_y) {
84 text->cursorUp(bv, true);
87 bv->updateScrollbar();
91 void cursorNext(LyXText * text, BufferView * bv)
93 if (!text->cursor.row()->next()) {
94 int y = text->cursor.y() - text->cursor.row()->baseline() +
95 text->cursor.row()->height();
96 if (y > int(text->first_y + bv->workarea().workHeight())) {
97 bv->screen().draw(bv->text, bv,
98 bv->text->first_y + bv->workarea().workHeight());
99 bv->updateScrollbar();
104 int y = text->first_y + bv->workarea().workHeight();
105 if (text->inset_owner && !text->first_y) {
106 y -= (bv->text->cursor.iy()
108 + bv->theLockingInset()->insetInInsetY());
111 text->getRowNearY(y);
113 Row * cursorrow = text->cursor.row();
114 text->setCursorFromCoordinates(bv, text->cursor.x_fix(), y);
115 // + workarea().workHeight());
119 if (cursorrow == bv->text->cursor.row()) {
120 // we have a row which is higher than the workarea so we leave the
121 // cursor on the start of the row and move only the draw down as soon
122 // as we move the cursor or do something while inside the row (it may
123 // span several workarea-heights) we'll move to the top again, but this
124 // is better than just jump down and only display part of the row.
125 new_y = bv->text->first_y + bv->workarea().workHeight();
127 if (text->inset_owner) {
128 new_y = bv->text->cursor.iy()
129 + bv->theLockingInset()->insetInInsetY()
130 + y - text->cursor.row()->baseline();
132 new_y = text->cursor.y() - text->cursor.row()->baseline();
135 bv->screen().draw(bv->text, bv, new_y);
136 if (text->cursor.row()->next()) {
138 text->setCursor(bv, cur, text->cursor.row()->next()->par(),
139 text->cursor.row()->next()->pos(), false);
140 if (cur.y() < int(text->first_y + bv->workarea().workHeight())) {
141 text->cursorDown(bv, true);
144 bv->updateScrollbar();
150 void LyXText::update(BufferView * bv, bool changed)
152 BufferView::UpdateCodes c = BufferView::SELECT | BufferView::FITCUR;
154 bv->update(this, c | BufferView::CHANGE);
160 void specialChar(LyXText * lt, BufferView * bv, InsetSpecialChar::Kind kind)
164 InsetSpecialChar * new_inset = new InsetSpecialChar(kind);
165 if (!bv->insertInset(new_inset))
168 bv->updateInset(new_inset, true);
172 Inset::RESULT LyXText::dispatch(FuncRequest const & cmd)
174 BufferView * bv = cmd.view();
176 switch (cmd.action) {
178 case LFUN_APPENDIX: {
179 Paragraph * par = cursor.par();
180 bool start = !par->params().startOfAppendix();
182 // ensure that we have only one start_of_appendix in this document
183 Paragraph * tmp = ownerParagraph();
184 for (; tmp; tmp = tmp->next())
185 tmp->params().startOfAppendix(false);
187 par->params().startOfAppendix(start);
189 // we can set the refreshing parameters now
190 status(cmd.view(), LyXText::NEED_MORE_REFRESH);
192 refresh_row = 0; // not needed for full update
193 updateCounters(cmd.view());
194 setCursor(cmd.view(), cursor.par(), cursor.pos());
199 case LFUN_DELETE_WORD_FORWARD:
200 bv->beforeChange(this);
202 deleteWordForward(bv);
207 case LFUN_DELETE_WORD_BACKWARD:
208 bv->beforeChange(this);
210 deleteWordBackward(bv);
215 case LFUN_DELETE_LINE_FORWARD:
216 bv->beforeChange(this);
218 deleteLineForward(bv);
225 if (!selection.mark())
226 bv->beforeChange(this);
233 if (!selection.mark())
234 bv->beforeChange(this);
236 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
237 cursorLeftOneWord(bv);
239 cursorRightOneWord(bv);
244 if (!selection.mark())
245 bv->beforeChange(this);
247 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
248 cursorRightOneWord(bv);
250 cursorLeftOneWord(bv);
254 case LFUN_BEGINNINGBUF:
255 if (!selection.mark())
256 bv->beforeChange(this);
263 if (selection.mark())
264 bv->beforeChange(this);
272 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
276 bv->finishChange(true);
281 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
285 bv->finishChange(true);
291 bv->finishChange(true);
296 cursorDown(bv, true);
297 bv->finishChange(true);
300 case LFUN_UP_PARAGRAPHSEL:
302 cursorUpParagraph(bv);
303 bv->finishChange(true);
306 case LFUN_DOWN_PARAGRAPHSEL:
308 cursorDownParagraph(bv);
309 bv->finishChange(true);
314 cursorPrevious(this, bv);
315 bv->finishChange(true);
320 cursorNext(this, bv);
327 bv->finishChange(true);
333 bv->finishChange(true);
336 case LFUN_WORDRIGHTSEL:
338 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
339 cursorLeftOneWord(bv);
341 cursorRightOneWord(bv);
342 bv->finishChange(true);
345 case LFUN_WORDLEFTSEL:
347 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
348 cursorRightOneWord(bv);
350 cursorLeftOneWord(bv);
351 bv->finishChange(true);
355 bool is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
356 if (!selection.mark())
357 bv->beforeChange(this);
360 cursorLeft(bv, false);
361 if (cursor.pos() < cursor.par()->size()
362 && cursor.par()->isInset(cursor.pos())
363 && isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
364 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
365 cmd.message(tmpinset->editMessage());
366 tmpinset->edit(bv, !is_rtl);
370 cursorRight(bv, false);
371 bv->finishChange(false);
376 // This is soooo ugly. Isn`t it possible to make
378 bool const is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
379 if (!selection.mark())
380 bv->beforeChange(this);
382 LyXCursor const cur = cursor;
384 cursorLeft(bv, false);
385 if ((is_rtl || cur != cursor) && // only if really moved!
386 cursor.pos() < cursor.par()->size() &&
387 cursor.par()->isInset(cursor.pos()) &&
388 isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
389 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
390 cmd.message(tmpinset->editMessage());
391 tmpinset->edit(bv, is_rtl);
395 cursorRight(bv, false);
396 bv->finishChange(false);
401 if (!selection.mark())
402 bv->beforeChange(this);
403 bv->update(this, BufferView::UPDATE);
405 bv->finishChange(false);
409 if (!selection.mark())
410 bv->beforeChange(this);
411 bv->update(this, BufferView::UPDATE);
416 case LFUN_UP_PARAGRAPH:
417 if (!selection.mark())
418 bv->beforeChange(this);
419 bv->update(this, BufferView::UPDATE);
420 cursorUpParagraph(bv);
424 case LFUN_DOWN_PARAGRAPH:
425 if (!selection.mark())
426 bv->beforeChange(this);
427 bv->update(this, BufferView::UPDATE);
428 cursorDownParagraph(bv);
429 bv->finishChange(false);
433 if (!selection.mark())
434 bv->beforeChange(this);
435 bv->update(this, BufferView::UPDATE);
436 cursorPrevious(this, bv);
437 bv->finishChange(false);
440 // moveCursorUpdate(false, false);
441 // owner_->view_state_changed();
445 if (!selection.mark())
446 bv->beforeChange(this);
447 bv->update(this, BufferView::UPDATE);
448 cursorNext(this, bv);
449 bv->finishChange(false);
453 if (!selection.mark())
454 bv->beforeChange(this);
457 bv->finishChange(false);
461 if (!selection.mark())
462 bv->beforeChange(this);
465 bv->finishChange(false);
469 bv->beforeChange(this);
470 insertChar(bv, Paragraph::META_NEWLINE);
472 setCursor(bv, cursor.par(), cursor.pos());
473 bv->moveCursorUpdate(false);
477 if (!selection.set()) {
479 selection.cursor = cursor;
481 // It is possible to make it a lot faster still
482 // just comment out the line below...
487 bv->moveCursorUpdate(false);
488 bv->owner()->view_state_changed();
492 case LFUN_DELETE_SKIP:
493 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
494 if (!selection.set()) {
495 LyXCursor cur = cursor;
496 if (cur.pos() == cur.par()->size()) {
500 && !(cur.par()->params().spaceTop()
501 == VSpace (VSpace::NONE))) {
503 cur.par()->params().lineTop(),
504 cur.par()->params().lineBottom(),
505 cur.par()->params().pagebreakTop(),
506 cur.par()->params().pagebreakBottom(),
507 VSpace(VSpace::NONE),
508 cur.par()->params().spaceBottom(),
509 cur.par()->params().spacing(),
510 cur.par()->params().align(),
511 cur.par()->params().labelWidthString(), 0);
517 selection.cursor = cursor;
522 selection.cursor = cursor;
531 if (!selection.set()) {
532 if (bv->owner()->getIntl().getTransManager().backspace()) {
534 selection.cursor = cursor;
536 // It is possible to make it a lot faster still
537 // just comment out the line below...
542 bv->owner()->view_state_changed();
546 case LFUN_BACKSPACE_SKIP:
547 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
548 if (!selection.set()) {
549 LyXCursor cur = cursor;
551 && !(cur.par()->params().spaceTop()
552 == VSpace (VSpace::NONE))) {
554 cur.par()->params().lineTop(),
555 cur.par()->params().lineBottom(),
556 cur.par()->params().pagebreakTop(),
557 cur.par()->params().pagebreakBottom(),
558 VSpace(VSpace::NONE), cur.par()->params().spaceBottom(),
559 cur.par()->params().spacing(),
560 cur.par()->params().align(),
561 cur.par()->params().labelWidthString(), 0);
565 selection.cursor = cur;
572 case LFUN_BREAKPARAGRAPH:
573 bv->beforeChange(this);
574 breakParagraph(bv, 0);
576 selection.cursor = cursor;
578 bv->owner()->view_state_changed();
581 case LFUN_BREAKPARAGRAPHKEEPLAYOUT:
582 bv->beforeChange(this);
583 breakParagraph(bv, 1);
585 selection.cursor = cursor;
587 bv->owner()->view_state_changed();
590 case LFUN_BREAKPARAGRAPH_SKIP: {
591 // When at the beginning of a paragraph, remove
592 // indentation and add a "defskip" at the top.
593 // Otherwise, do the same as LFUN_BREAKPARAGRAPH.
594 LyXCursor cur = cursor;
595 bv->beforeChange(this);
596 if (cur.pos() == 0) {
597 if (cur.par()->params().spaceTop() == VSpace(VSpace::NONE)) {
599 cur.par()->params().lineTop(),
600 cur.par()->params().lineBottom(),
601 cur.par()->params().pagebreakTop(),
602 cur.par()->params().pagebreakBottom(),
603 VSpace(VSpace::DEFSKIP), cur.par()->params().spaceBottom(),
604 cur.par()->params().spacing(),
605 cur.par()->params().align(),
606 cur.par()->params().labelWidthString(), 1);
611 breakParagraph(bv, 0);
615 selection.cursor = cur;
617 bv->owner()->view_state_changed();
621 case LFUN_PARAGRAPH_SPACING: {
622 Paragraph * par = cursor.par();
623 Spacing::Space cur_spacing = par->params().spacing().getSpace();
624 float cur_value = 1.0;
625 if (cur_spacing == Spacing::Other)
626 cur_value = par->params().spacing().getValue();
628 istringstream is(cmd.argument.c_str());
631 Spacing::Space new_spacing = cur_spacing;
632 float new_value = cur_value;
634 lyxerr << "Missing argument to `paragraph-spacing'"
636 } else if (tmp == "single") {
637 new_spacing = Spacing::Single;
638 } else if (tmp == "onehalf") {
639 new_spacing = Spacing::Onehalf;
640 } else if (tmp == "double") {
641 new_spacing = Spacing::Double;
642 } else if (tmp == "other") {
643 new_spacing = Spacing::Other;
646 lyxerr << "new_value = " << tmpval << endl;
649 } else if (tmp == "default") {
650 new_spacing = Spacing::Default;
652 lyxerr << _("Unknown spacing argument: ")
653 << cmd.argument << endl;
655 if (cur_spacing != new_spacing || cur_value != new_value) {
656 par->params().spacing(Spacing(new_spacing, new_value));
663 case LFUN_INSET_TOGGLE:
665 bv->beforeChange(this);
672 case LFUN_PROTECTEDSPACE:
673 if (cursor.par()->layout()->free_spacing) {
677 specialChar(this, bv, InsetSpecialChar::PROTECTED_SEPARATOR);
679 bv->moveCursorUpdate(false);
682 case LFUN_HYPHENATION:
683 specialChar(this, bv, InsetSpecialChar::HYPHENATION);
686 case LFUN_LIGATURE_BREAK:
687 specialChar(this, bv, InsetSpecialChar::LIGATURE_BREAK);
691 specialChar(this, bv, InsetSpecialChar::LDOTS);
697 insertChar(bv, Paragraph::META_HFILL);
701 case LFUN_END_OF_SENTENCE:
702 specialChar(this, bv, InsetSpecialChar::END_OF_SENTENCE);
705 case LFUN_MENU_SEPARATOR:
706 specialChar(this, bv, InsetSpecialChar::MENU_SEPARATOR);
710 bv->beforeChange(this);
712 selection.cursor = cursor;
713 cmd.message(N_("Mark off"));
717 bv->beforeChange(this);
718 selection.mark(true);
720 selection.cursor = cursor;
721 cmd.message(N_("Mark on"));
725 bv->beforeChange(this);
726 if (selection.mark()) {
728 cmd.message(N_("Mark removed"));
730 selection.mark(true);
732 cmd.message(N_("Mark set"));
734 selection.cursor = cursor;
737 case LFUN_UPCASE_WORD:
739 changeCase(bv, LyXText::text_uppercase);
741 bv->updateInset(inset_owner, true);
745 case LFUN_LOWCASE_WORD:
747 changeCase(bv, LyXText::text_lowercase);
749 bv->updateInset(inset_owner, true);
753 case LFUN_CAPITALIZE_WORD:
755 changeCase(bv, LyXText::text_capitalization);
757 bv->updateInset(inset_owner, true);
761 case LFUN_TRANSPOSE_CHARS:
765 bv->updateInset(inset_owner, true);
769 case LFUN_BEGINNINGBUFSEL:
771 return Inset::UNDISPATCHED;
774 bv->finishChange(true);
779 return Inset::UNDISPATCHED;
782 bv->finishChange(true);
786 cmd.message(tostr(cursor.x()) + ' ' + tostr(cursor.y()));
792 istringstream is(cmd.argument.c_str());
795 lyxerr << "SETXY: Could not parse coordinates in '"
796 << cmd.argument << std::endl;
798 setCursorFromCoordinates(bv, x, y);
803 if (current_font.shape() == LyXFont::ITALIC_SHAPE)
805 else if (current_font.shape() == LyXFont::SMALLCAPS_SHAPE)
812 cmd.message(tostr(cursor.par()->layout()));
816 lyxerr[Debug::INFO] << "LFUN_LAYOUT: (arg) "
817 << cmd.argument << endl;
819 // This is not the good solution to the empty argument
820 // problem, but it will hopefully suffice for 1.2.0.
821 // The correct solution would be to augument the
822 // function list/array with information about what
823 // functions needs arguments and their type.
824 if (cmd.argument.empty()) {
825 cmd.errorMessage(_("LyX function 'layout' needs an argument."));
829 // Derive layout number from given argument (string)
830 // and current buffer's textclass (number)
831 LyXTextClass const & tclass = bv->buffer()->params.getLyXTextClass();
832 bool hasLayout = tclass.hasLayout(cmd.argument);
833 string layout = cmd.argument;
835 // If the entry is obsolete, use the new one instead.
837 string const & obs = tclass[layout]->obsoleted_by();
843 cmd.errorMessage(string(N_("Layout ")) + cmd.argument +
848 bool change_layout = (current_layout != layout);
849 if (!change_layout && selection.set() &&
850 selection.start.par() != selection.end.par())
852 Paragraph * spar = selection.start.par();
853 Paragraph * epar = selection.end.par()->next();
854 while (spar != epar) {
855 if (spar->layout()->name() != current_layout) {
856 change_layout = true;
863 current_layout = layout;
865 setLayout(bv, layout);
866 bv->owner()->setLayout(layout);
873 case LFUN_PASTESELECTION: {
877 // this was originally a beforeChange(bv->text), i.e
878 // the outermost LyXText!
879 bv->beforeChange(this);
880 string const clip(bv->workarea().getClipboard());
882 if (cmd.argument == "paragraph")
883 insertStringAsParagraphs(bv, clip);
885 insertStringAsLines(bv, clip);
892 case LFUN_SELFINSERT: {
893 if (cmd.argument.empty())
896 // Automatically delete the currently selected
897 // text and replace it with what is being
898 // typed in now. Depends on lyxrc settings
899 // "auto_region_delete", which defaults to
902 if (lyxrc.auto_region_delete) {
903 if (selection.set()) {
904 cutSelection(bv, false, false);
907 bv->workarea().haveSelection(false);
910 bv->beforeChange(this);
911 LyXFont const old_font(real_current_font);
913 string::const_iterator cit = cmd.argument.begin();
914 string::const_iterator end = cmd.argument.end();
915 for (; cit != end; ++cit)
916 bv->owner()->getIntl().getTransManager().
917 TranslateAndInsert(*cit, this);
920 selection.cursor = cursor;
921 bv->moveCursorUpdate(false);
923 // real_current_font.number can change so we need to
924 // update the minibuffer
925 if (old_font != real_current_font)
926 bv->owner()->view_state_changed();
931 return Inset::UNDISPATCHED;
934 return Inset::DISPATCHED;