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...
489 cutSelection(bv, true);
492 bv->moveCursorUpdate(false);
493 bv->owner()->view_state_changed();
497 case LFUN_DELETE_SKIP:
498 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
499 if (!selection.set()) {
500 LyXCursor cur = cursor;
501 if (cur.pos() == cur.par()->size()) {
505 && !(cur.par()->params().spaceTop()
506 == VSpace (VSpace::NONE))) {
508 cur.par()->params().lineTop(),
509 cur.par()->params().lineBottom(),
510 cur.par()->params().pagebreakTop(),
511 cur.par()->params().pagebreakBottom(),
512 VSpace(VSpace::NONE),
513 cur.par()->params().spaceBottom(),
514 cur.par()->params().spacing(),
515 cur.par()->params().align(),
516 cur.par()->params().labelWidthString(), 0);
522 selection.cursor = cursor;
526 selection.cursor = cursor;
530 cutSelection(bv, true);
537 if (!selection.set()) {
538 if (bv->owner()->getIntl().getTransManager().backspace()) {
540 selection.cursor = cursor;
542 // It is possible to make it a lot faster still
543 // just comment out the line below...
548 cutSelection(bv, true);
551 bv->owner()->view_state_changed();
555 case LFUN_BACKSPACE_SKIP:
556 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
557 if (!selection.set()) {
558 LyXCursor cur = cursor;
560 && !(cur.par()->params().spaceTop()
561 == VSpace (VSpace::NONE))) {
563 cur.par()->params().lineTop(),
564 cur.par()->params().lineBottom(),
565 cur.par()->params().pagebreakTop(),
566 cur.par()->params().pagebreakBottom(),
567 VSpace(VSpace::NONE), cur.par()->params().spaceBottom(),
568 cur.par()->params().spacing(),
569 cur.par()->params().align(),
570 cur.par()->params().labelWidthString(), 0);
573 selection.cursor = cur;
577 cutSelection(bv, true);
582 case LFUN_BREAKPARAGRAPH:
583 bv->beforeChange(this);
584 breakParagraph(bv, 0);
586 selection.cursor = cursor;
588 bv->owner()->view_state_changed();
591 case LFUN_BREAKPARAGRAPHKEEPLAYOUT:
592 bv->beforeChange(this);
593 breakParagraph(bv, 1);
595 selection.cursor = cursor;
597 bv->owner()->view_state_changed();
600 case LFUN_BREAKPARAGRAPH_SKIP: {
601 // When at the beginning of a paragraph, remove
602 // indentation and add a "defskip" at the top.
603 // Otherwise, do the same as LFUN_BREAKPARAGRAPH.
604 LyXCursor cur = cursor;
605 bv->beforeChange(this);
606 if (cur.pos() == 0) {
607 if (cur.par()->params().spaceTop() == VSpace(VSpace::NONE)) {
609 cur.par()->params().lineTop(),
610 cur.par()->params().lineBottom(),
611 cur.par()->params().pagebreakTop(),
612 cur.par()->params().pagebreakBottom(),
613 VSpace(VSpace::DEFSKIP), cur.par()->params().spaceBottom(),
614 cur.par()->params().spacing(),
615 cur.par()->params().align(),
616 cur.par()->params().labelWidthString(), 1);
621 breakParagraph(bv, 0);
625 selection.cursor = cur;
627 bv->owner()->view_state_changed();
631 case LFUN_PARAGRAPH_SPACING: {
632 Paragraph * par = cursor.par();
633 Spacing::Space cur_spacing = par->params().spacing().getSpace();
634 float cur_value = 1.0;
635 if (cur_spacing == Spacing::Other)
636 cur_value = par->params().spacing().getValue();
638 istringstream is(cmd.argument.c_str());
641 Spacing::Space new_spacing = cur_spacing;
642 float new_value = cur_value;
644 lyxerr << "Missing argument to `paragraph-spacing'"
646 } else if (tmp == "single") {
647 new_spacing = Spacing::Single;
648 } else if (tmp == "onehalf") {
649 new_spacing = Spacing::Onehalf;
650 } else if (tmp == "double") {
651 new_spacing = Spacing::Double;
652 } else if (tmp == "other") {
653 new_spacing = Spacing::Other;
656 lyxerr << "new_value = " << tmpval << endl;
659 } else if (tmp == "default") {
660 new_spacing = Spacing::Default;
662 lyxerr << _("Unknown spacing argument: ")
663 << cmd.argument << endl;
665 if (cur_spacing != new_spacing || cur_value != new_value) {
666 par->params().spacing(Spacing(new_spacing, new_value));
673 case LFUN_INSET_TOGGLE:
675 bv->beforeChange(this);
682 case LFUN_PROTECTEDSPACE:
683 if (cursor.par()->layout()->free_spacing) {
687 specialChar(this, bv, InsetSpecialChar::PROTECTED_SEPARATOR);
689 bv->moveCursorUpdate(false);
692 case LFUN_HYPHENATION:
693 specialChar(this, bv, InsetSpecialChar::HYPHENATION);
696 case LFUN_LIGATURE_BREAK:
697 specialChar(this, bv, InsetSpecialChar::LIGATURE_BREAK);
701 specialChar(this, bv, InsetSpecialChar::LDOTS);
707 insertChar(bv, Paragraph::META_HFILL);
711 case LFUN_END_OF_SENTENCE:
712 specialChar(this, bv, InsetSpecialChar::END_OF_SENTENCE);
715 case LFUN_MENU_SEPARATOR:
716 specialChar(this, bv, InsetSpecialChar::MENU_SEPARATOR);
720 bv->beforeChange(this);
722 selection.cursor = cursor;
723 cmd.message(N_("Mark off"));
727 bv->beforeChange(this);
728 selection.mark(true);
730 selection.cursor = cursor;
731 cmd.message(N_("Mark on"));
735 bv->beforeChange(this);
736 if (selection.mark()) {
738 cmd.message(N_("Mark removed"));
740 selection.mark(true);
742 cmd.message(N_("Mark set"));
744 selection.cursor = cursor;
747 case LFUN_UPCASE_WORD:
749 changeCase(bv, LyXText::text_uppercase);
751 bv->updateInset(inset_owner, true);
755 case LFUN_LOWCASE_WORD:
757 changeCase(bv, LyXText::text_lowercase);
759 bv->updateInset(inset_owner, true);
763 case LFUN_CAPITALIZE_WORD:
765 changeCase(bv, LyXText::text_capitalization);
767 bv->updateInset(inset_owner, true);
771 case LFUN_TRANSPOSE_CHARS:
775 bv->updateInset(inset_owner, true);
780 cmd.message(_("Paste"));
782 // clear the selection
783 bv->toggleSelection();
787 clearSelection(); // bug 393
796 cutSelection(bv, true);
798 cmd.message(_("Cut"));
803 cmd.message(_("Copy"));
806 case LFUN_BEGINNINGBUFSEL:
808 return Inset::UNDISPATCHED;
811 bv->finishChange(true);
816 return Inset::UNDISPATCHED;
819 bv->finishChange(true);
823 cmd.message(tostr(cursor.x()) + ' ' + tostr(cursor.y()));
829 istringstream is(cmd.argument.c_str());
832 lyxerr << "SETXY: Could not parse coordinates in '"
833 << cmd.argument << std::endl;
835 setCursorFromCoordinates(bv, x, y);
840 if (current_font.shape() == LyXFont::ITALIC_SHAPE)
842 else if (current_font.shape() == LyXFont::SMALLCAPS_SHAPE)
849 cmd.message(tostr(cursor.par()->layout()));
853 lyxerr[Debug::INFO] << "LFUN_LAYOUT: (arg) "
854 << cmd.argument << endl;
856 // This is not the good solution to the empty argument
857 // problem, but it will hopefully suffice for 1.2.0.
858 // The correct solution would be to augument the
859 // function list/array with information about what
860 // functions needs arguments and their type.
861 if (cmd.argument.empty()) {
862 cmd.errorMessage(_("LyX function 'layout' needs an argument."));
866 // Derive layout number from given argument (string)
867 // and current buffer's textclass (number)
868 LyXTextClass const & tclass = bv->buffer()->params.getLyXTextClass();
869 bool hasLayout = tclass.hasLayout(cmd.argument);
870 string layout = cmd.argument;
872 // If the entry is obsolete, use the new one instead.
874 string const & obs = tclass[layout]->obsoleted_by();
880 cmd.errorMessage(string(N_("Layout ")) + cmd.argument +
885 bool change_layout = (current_layout != layout);
886 if (!change_layout && selection.set() &&
887 selection.start.par() != selection.end.par())
889 Paragraph * spar = selection.start.par();
890 Paragraph * epar = selection.end.par()->next();
891 while (spar != epar) {
892 if (spar->layout()->name() != current_layout) {
893 change_layout = true;
900 current_layout = layout;
902 setLayout(bv, layout);
903 bv->owner()->setLayout(layout);
910 case LFUN_PASTESELECTION: {
914 // this was originally a beforeChange(bv->text), i.e
915 // the outermost LyXText!
916 bv->beforeChange(this);
917 string const clip(bv->workarea().getClipboard());
919 if (cmd.argument == "paragraph")
920 insertStringAsParagraphs(bv, clip);
922 insertStringAsLines(bv, clip);
929 case LFUN_SELFINSERT: {
930 if (cmd.argument.empty())
933 // Automatically delete the currently selected
934 // text and replace it with what is being
935 // typed in now. Depends on lyxrc settings
936 // "auto_region_delete", which defaults to
939 if (lyxrc.auto_region_delete) {
940 if (selection.set()) {
941 cutSelection(bv, false, false);
944 bv->workarea().haveSelection(false);
947 bv->beforeChange(this);
948 LyXFont const old_font(real_current_font);
950 string::const_iterator cit = cmd.argument.begin();
951 string::const_iterator end = cmd.argument.end();
952 for (; cit != end; ++cit)
953 bv->owner()->getIntl().getTransManager().
954 TranslateAndInsert(*cit, this);
957 selection.cursor = cursor;
958 bv->moveCursorUpdate(false);
960 // real_current_font.number can change so we need to
961 // update the minibuffer
962 if (old_font != real_current_font)
963 bv->owner()->view_state_changed();
968 return Inset::UNDISPATCHED;
971 return Inset::DISPATCHED;