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 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: action[" << cmd.action
175 <<"] arg[" << cmd.argument << "]" << endl;
177 BufferView * bv = cmd.view();
179 switch (cmd.action) {
181 case LFUN_APPENDIX: {
182 Paragraph * par = cursor.par();
183 bool start = !par->params().startOfAppendix();
185 // ensure that we have only one start_of_appendix in this document
186 Paragraph * tmp = ownerParagraph();
187 for (; tmp; tmp = tmp->next())
188 tmp->params().startOfAppendix(false);
190 par->params().startOfAppendix(start);
192 // we can set the refreshing parameters now
193 status(cmd.view(), LyXText::NEED_MORE_REFRESH);
195 refresh_row = 0; // not needed for full update
196 updateCounters(cmd.view());
197 setCursor(cmd.view(), cursor.par(), cursor.pos());
202 case LFUN_DELETE_WORD_FORWARD:
203 bv->beforeChange(this);
205 deleteWordForward(bv);
210 case LFUN_DELETE_WORD_BACKWARD:
211 bv->beforeChange(this);
213 deleteWordBackward(bv);
218 case LFUN_DELETE_LINE_FORWARD:
219 bv->beforeChange(this);
221 deleteLineForward(bv);
228 if (!selection.mark())
229 bv->beforeChange(this);
236 if (!selection.mark())
237 bv->beforeChange(this);
239 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
240 cursorLeftOneWord(bv);
242 cursorRightOneWord(bv);
247 if (!selection.mark())
248 bv->beforeChange(this);
250 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
251 cursorRightOneWord(bv);
253 cursorLeftOneWord(bv);
257 case LFUN_BEGINNINGBUF:
258 if (!selection.mark())
259 bv->beforeChange(this);
266 if (selection.mark())
267 bv->beforeChange(this);
275 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
279 bv->finishChange(true);
284 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
288 bv->finishChange(true);
294 bv->finishChange(true);
299 cursorDown(bv, true);
300 bv->finishChange(true);
303 case LFUN_UP_PARAGRAPHSEL:
305 cursorUpParagraph(bv);
306 bv->finishChange(true);
309 case LFUN_DOWN_PARAGRAPHSEL:
311 cursorDownParagraph(bv);
312 bv->finishChange(true);
317 cursorPrevious(this, bv);
318 bv->finishChange(true);
323 cursorNext(this, bv);
330 bv->finishChange(true);
336 bv->finishChange(true);
339 case LFUN_WORDRIGHTSEL:
341 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
342 cursorLeftOneWord(bv);
344 cursorRightOneWord(bv);
345 bv->finishChange(true);
348 case LFUN_WORDLEFTSEL:
350 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
351 cursorRightOneWord(bv);
353 cursorLeftOneWord(bv);
354 bv->finishChange(true);
358 bool is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
359 if (!selection.mark())
360 bv->beforeChange(this);
363 cursorLeft(bv, false);
364 if (cursor.pos() < cursor.par()->size()
365 && cursor.par()->isInset(cursor.pos())
366 && isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
367 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
368 cmd.message(tmpinset->editMessage());
369 tmpinset->edit(bv, !is_rtl);
373 cursorRight(bv, false);
374 bv->finishChange(false);
379 // This is soooo ugly. Isn`t it possible to make
381 bool const is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
382 if (!selection.mark())
383 bv->beforeChange(this);
385 LyXCursor const cur = cursor;
387 cursorLeft(bv, false);
388 if ((is_rtl || cur != cursor) && // only if really moved!
389 cursor.pos() < cursor.par()->size() &&
390 cursor.par()->isInset(cursor.pos()) &&
391 isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
392 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
393 cmd.message(tmpinset->editMessage());
394 tmpinset->edit(bv, is_rtl);
398 cursorRight(bv, false);
399 bv->finishChange(false);
404 if (!selection.mark())
405 bv->beforeChange(this);
406 bv->update(this, BufferView::UPDATE);
408 bv->finishChange(false);
412 if (!selection.mark())
413 bv->beforeChange(this);
414 bv->update(this, BufferView::UPDATE);
419 case LFUN_UP_PARAGRAPH:
420 if (!selection.mark())
421 bv->beforeChange(this);
422 bv->update(this, BufferView::UPDATE);
423 cursorUpParagraph(bv);
427 case LFUN_DOWN_PARAGRAPH:
428 if (!selection.mark())
429 bv->beforeChange(this);
430 bv->update(this, BufferView::UPDATE);
431 cursorDownParagraph(bv);
432 bv->finishChange(false);
436 if (!selection.mark())
437 bv->beforeChange(this);
438 bv->update(this, BufferView::UPDATE);
439 cursorPrevious(this, bv);
440 bv->finishChange(false);
443 // moveCursorUpdate(false, false);
444 // owner_->view_state_changed();
448 if (!selection.mark())
449 bv->beforeChange(this);
450 bv->update(this, BufferView::UPDATE);
451 cursorNext(this, bv);
452 bv->finishChange(false);
456 if (!selection.mark())
457 bv->beforeChange(this);
460 bv->finishChange(false);
464 if (!selection.mark())
465 bv->beforeChange(this);
468 bv->finishChange(false);
472 bv->beforeChange(this);
473 insertChar(bv, Paragraph::META_NEWLINE);
475 setCursor(bv, cursor.par(), cursor.pos());
476 bv->moveCursorUpdate(false);
480 if (!selection.set()) {
482 selection.cursor = cursor;
484 // It is possible to make it a lot faster still
485 // just comment out the line below...
490 bv->moveCursorUpdate(false);
491 bv->owner()->view_state_changed();
495 case LFUN_DELETE_SKIP:
496 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
497 if (!selection.set()) {
498 LyXCursor cur = cursor;
499 if (cur.pos() == cur.par()->size()) {
503 && !(cur.par()->params().spaceTop()
504 == VSpace (VSpace::NONE))) {
506 cur.par()->params().lineTop(),
507 cur.par()->params().lineBottom(),
508 cur.par()->params().pagebreakTop(),
509 cur.par()->params().pagebreakBottom(),
510 VSpace(VSpace::NONE),
511 cur.par()->params().spaceBottom(),
512 cur.par()->params().spacing(),
513 cur.par()->params().align(),
514 cur.par()->params().labelWidthString(), 0);
520 selection.cursor = cursor;
525 selection.cursor = cursor;
534 if (!selection.set()) {
535 if (bv->owner()->getIntl().getTransManager().backspace()) {
537 selection.cursor = cursor;
539 // It is possible to make it a lot faster still
540 // just comment out the line below...
545 bv->owner()->view_state_changed();
549 case LFUN_BACKSPACE_SKIP:
550 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
551 if (!selection.set()) {
552 LyXCursor cur = cursor;
554 && !(cur.par()->params().spaceTop()
555 == VSpace (VSpace::NONE))) {
557 cur.par()->params().lineTop(),
558 cur.par()->params().lineBottom(),
559 cur.par()->params().pagebreakTop(),
560 cur.par()->params().pagebreakBottom(),
561 VSpace(VSpace::NONE), cur.par()->params().spaceBottom(),
562 cur.par()->params().spacing(),
563 cur.par()->params().align(),
564 cur.par()->params().labelWidthString(), 0);
568 selection.cursor = cur;
575 case LFUN_BREAKPARAGRAPH:
576 bv->beforeChange(this);
577 breakParagraph(bv, 0);
579 selection.cursor = cursor;
581 bv->owner()->view_state_changed();
584 case LFUN_BREAKPARAGRAPHKEEPLAYOUT:
585 bv->beforeChange(this);
586 breakParagraph(bv, 1);
588 selection.cursor = cursor;
590 bv->owner()->view_state_changed();
593 case LFUN_BREAKPARAGRAPH_SKIP: {
594 // When at the beginning of a paragraph, remove
595 // indentation and add a "defskip" at the top.
596 // Otherwise, do the same as LFUN_BREAKPARAGRAPH.
597 LyXCursor cur = cursor;
598 bv->beforeChange(this);
599 if (cur.pos() == 0) {
600 if (cur.par()->params().spaceTop() == VSpace(VSpace::NONE)) {
602 cur.par()->params().lineTop(),
603 cur.par()->params().lineBottom(),
604 cur.par()->params().pagebreakTop(),
605 cur.par()->params().pagebreakBottom(),
606 VSpace(VSpace::DEFSKIP), cur.par()->params().spaceBottom(),
607 cur.par()->params().spacing(),
608 cur.par()->params().align(),
609 cur.par()->params().labelWidthString(), 1);
614 breakParagraph(bv, 0);
618 selection.cursor = cur;
620 bv->owner()->view_state_changed();
624 case LFUN_PARAGRAPH_SPACING: {
625 Paragraph * par = cursor.par();
626 Spacing::Space cur_spacing = par->params().spacing().getSpace();
627 float cur_value = 1.0;
628 if (cur_spacing == Spacing::Other)
629 cur_value = par->params().spacing().getValue();
631 istringstream is(cmd.argument.c_str());
634 Spacing::Space new_spacing = cur_spacing;
635 float new_value = cur_value;
637 lyxerr << "Missing argument to `paragraph-spacing'"
639 } else if (tmp == "single") {
640 new_spacing = Spacing::Single;
641 } else if (tmp == "onehalf") {
642 new_spacing = Spacing::Onehalf;
643 } else if (tmp == "double") {
644 new_spacing = Spacing::Double;
645 } else if (tmp == "other") {
646 new_spacing = Spacing::Other;
649 lyxerr << "new_value = " << tmpval << endl;
652 } else if (tmp == "default") {
653 new_spacing = Spacing::Default;
655 lyxerr << _("Unknown spacing argument: ")
656 << cmd.argument << endl;
658 if (cur_spacing != new_spacing || cur_value != new_value) {
659 par->params().spacing(Spacing(new_spacing, new_value));
666 case LFUN_INSET_TOGGLE:
668 bv->beforeChange(this);
675 case LFUN_PROTECTEDSPACE:
676 if (cursor.par()->layout()->free_spacing) {
680 specialChar(this, bv, InsetSpecialChar::PROTECTED_SEPARATOR);
682 bv->moveCursorUpdate(false);
685 case LFUN_HYPHENATION:
686 specialChar(this, bv, InsetSpecialChar::HYPHENATION);
689 case LFUN_LIGATURE_BREAK:
690 specialChar(this, bv, InsetSpecialChar::LIGATURE_BREAK);
694 specialChar(this, bv, InsetSpecialChar::LDOTS);
700 insertChar(bv, Paragraph::META_HFILL);
704 case LFUN_END_OF_SENTENCE:
705 specialChar(this, bv, InsetSpecialChar::END_OF_SENTENCE);
708 case LFUN_MENU_SEPARATOR:
709 specialChar(this, bv, InsetSpecialChar::MENU_SEPARATOR);
713 bv->beforeChange(this);
715 selection.cursor = cursor;
716 cmd.message(N_("Mark off"));
720 bv->beforeChange(this);
721 selection.mark(true);
723 selection.cursor = cursor;
724 cmd.message(N_("Mark on"));
728 bv->beforeChange(this);
729 if (selection.mark()) {
731 cmd.message(N_("Mark removed"));
733 selection.mark(true);
735 cmd.message(N_("Mark set"));
737 selection.cursor = cursor;
740 case LFUN_UPCASE_WORD:
742 changeCase(bv, LyXText::text_uppercase);
744 bv->updateInset(inset_owner, true);
748 case LFUN_LOWCASE_WORD:
750 changeCase(bv, LyXText::text_lowercase);
752 bv->updateInset(inset_owner, true);
756 case LFUN_CAPITALIZE_WORD:
758 changeCase(bv, LyXText::text_capitalization);
760 bv->updateInset(inset_owner, true);
764 case LFUN_TRANSPOSE_CHARS:
768 bv->updateInset(inset_owner, true);
772 case LFUN_BEGINNINGBUFSEL:
774 return Inset::UNDISPATCHED;
777 bv->finishChange(true);
782 return Inset::UNDISPATCHED;
785 bv->finishChange(true);
789 cmd.message(tostr(cursor.x()) + ' ' + tostr(cursor.y()));
795 istringstream is(cmd.argument.c_str());
798 lyxerr << "SETXY: Could not parse coordinates in '"
799 << cmd.argument << std::endl;
801 setCursorFromCoordinates(bv, x, y);
806 if (current_font.shape() == LyXFont::ITALIC_SHAPE)
808 else if (current_font.shape() == LyXFont::SMALLCAPS_SHAPE)
815 cmd.message(tostr(cursor.par()->layout()));
819 lyxerr[Debug::INFO] << "LFUN_LAYOUT: (arg) "
820 << cmd.argument << endl;
822 // This is not the good solution to the empty argument
823 // problem, but it will hopefully suffice for 1.2.0.
824 // The correct solution would be to augument the
825 // function list/array with information about what
826 // functions needs arguments and their type.
827 if (cmd.argument.empty()) {
828 cmd.errorMessage(_("LyX function 'layout' needs an argument."));
832 // Derive layout number from given argument (string)
833 // and current buffer's textclass (number)
834 LyXTextClass const & tclass = bv->buffer()->params.getLyXTextClass();
835 bool hasLayout = tclass.hasLayout(cmd.argument);
836 string layout = cmd.argument;
838 // If the entry is obsolete, use the new one instead.
840 string const & obs = tclass[layout]->obsoleted_by();
846 cmd.errorMessage(string(N_("Layout ")) + cmd.argument +
851 bool change_layout = (current_layout != layout);
852 if (!change_layout && selection.set() &&
853 selection.start.par() != selection.end.par())
855 Paragraph * spar = selection.start.par();
856 Paragraph * epar = selection.end.par()->next();
857 while (spar != epar) {
858 if (spar->layout()->name() != current_layout) {
859 change_layout = true;
866 current_layout = layout;
868 setLayout(bv, layout);
869 bv->owner()->setLayout(layout);
876 case LFUN_PASTESELECTION: {
880 // this was originally a beforeChange(bv->text), i.e
881 // the outermost LyXText!
882 bv->beforeChange(this);
883 string const clip(bv->workarea().getClipboard());
885 if (cmd.argument == "paragraph")
886 insertStringAsParagraphs(bv, clip);
888 insertStringAsLines(bv, clip);
895 case LFUN_SELFINSERT: {
896 if (cmd.argument.empty())
899 // Automatically delete the currently selected
900 // text and replace it with what is being
901 // typed in now. Depends on lyxrc settings
902 // "auto_region_delete", which defaults to
905 if (lyxrc.auto_region_delete) {
906 if (selection.set()) {
907 cutSelection(bv, false, false);
910 bv->workarea().haveSelection(false);
913 bv->beforeChange(this);
914 LyXFont const old_font(real_current_font);
916 string::const_iterator cit = cmd.argument.begin();
917 string::const_iterator end = cmd.argument.end();
918 for (; cit != end; ++cit)
919 bv->owner()->getIntl().getTransManager().
920 TranslateAndInsert(*cit, this);
923 selection.cursor = cursor;
924 bv->moveCursorUpdate(false);
926 // real_current_font.number can change so we need to
927 // update the minibuffer
928 if (old_font != real_current_font)
929 bv->owner()->view_state_changed();
934 return Inset::UNDISPATCHED;
937 return Inset::DISPATCHED;