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"
26 #include "support/lstrings.h"
27 #include "frontends/LyXView.h"
28 #include "frontends/screen.h"
29 #include "frontends/WorkArea.h"
30 #include "insets/insetspecialchar.h"
31 #include "insets/insettext.h"
32 #include "insets/insetquotes.h"
33 #include "insets/insetcommand.h"
34 #include "undo_funcs.h"
38 extern string current_layout;
42 void finishChange(BufferView * bv, bool fitcur = false)
45 bv->moveCursorUpdate(fitcur);
46 bv->owner()->view_state_changed();
52 bool LyXText::gotoNextInset(BufferView * bv,
53 vector<Inset::Code> const & codes, string const & contents) const
55 LyXCursor res = cursor;
58 if (res.pos() < res.par()->size() - 1) {
59 res.pos(res.pos() + 1);
61 res.par(res.par()->next());
66 !(res.par()->isInset(res.pos())
67 && (inset = res.par()->getInset(res.pos())) != 0
68 && find(codes.begin(), codes.end(), inset->lyxCode())
70 && (contents.empty() ||
71 static_cast<InsetCommand *>(
72 res.par()->getInset(res.pos()))->getContents()
76 setCursor(bv, res.par(), res.pos(), false);
83 void LyXText::gotoInset(BufferView * bv, vector<Inset::Code> const & codes,
87 bv->beforeChange(this);
91 if (same_content && cursor.par()->isInset(cursor.pos())) {
92 Inset const * inset = cursor.par()->getInset(cursor.pos());
93 if (find(codes.begin(), codes.end(), inset->lyxCode())
95 contents = static_cast<InsetCommand const *>(inset)->getContents();
98 if (!gotoNextInset(bv, codes, contents)) {
99 if (cursor.pos() || cursor.par() != ownerParagraph()) {
100 LyXCursor tmp = cursor;
101 cursor.par(ownerParagraph());
103 if (!gotoNextInset(bv, codes, contents)) {
105 bv->owner()->message(_("No more insets"));
108 bv->owner()->message(_("No more insets"));
112 selection.cursor = cursor;
116 void LyXText::gotoInset(BufferView * bv, Inset::Code code, bool same_content)
118 gotoInset(bv, vector<Inset::Code>(1, code), same_content);
122 void LyXText::cursorPrevious(BufferView * bv)
124 if (!cursor.row()->previous()) {
126 int new_y = bv->text->first_y - bv->workarea().workHeight();
127 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
128 bv->updateScrollbar();
134 Row * cursorrow = cursor.row();
136 setCursorFromCoordinates(bv, cursor.x_fix(), y);
140 if (cursorrow == bv->text->cursor.row()) {
141 // we have a row which is higher than the workarea so we leave the
142 // cursor on the start of the row and move only the draw up as soon
143 // as we move the cursor or do something while inside the row (it may
144 // span several workarea-heights) we'll move to the top again, but this
145 // is better than just jump down and only display part of the row.
146 new_y = bv->text->first_y - bv->workarea().workHeight();
149 new_y = bv->text->cursor.iy()
150 + bv->theLockingInset()->insetInInsetY() + y
151 + cursor.row()->height()
152 - bv->workarea().workHeight() + 1;
155 - cursor.row()->baseline()
156 + cursor.row()->height()
157 - bv->workarea().workHeight() + 1;
160 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
161 if (cursor.row()->previous()) {
163 setCursor(bv, cur, cursor.row()->previous()->par(),
164 cursor.row()->previous()->pos(), false);
165 if (cur.y() > first_y) {
169 bv->updateScrollbar();
173 void LyXText::cursorNext(BufferView * bv)
175 if (!cursor.row()->next()) {
176 int y = cursor.y() - cursor.row()->baseline() +
177 cursor.row()->height();
178 if (y > int(first_y + bv->workarea().workHeight())) {
179 bv->screen().draw(bv->text, bv,
180 bv->text->first_y + bv->workarea().workHeight());
181 bv->updateScrollbar();
186 int y = first_y + bv->workarea().workHeight();
187 if (inset_owner && !first_y) {
188 y -= (bv->text->cursor.iy()
190 + bv->theLockingInset()->insetInInsetY());
195 Row * cursorrow = cursor.row();
196 setCursorFromCoordinates(bv, cursor.x_fix(), y);
197 // + workarea().workHeight());
201 if (cursorrow == bv->text->cursor.row()) {
202 // we have a row which is higher than the workarea so we leave the
203 // cursor on the start of the row and move only the draw down as soon
204 // as we move the cursor or do something while inside the row (it may
205 // span several workarea-heights) we'll move to the top again, but this
206 // is better than just jump down and only display part of the row.
207 new_y = bv->text->first_y + bv->workarea().workHeight();
210 new_y = bv->text->cursor.iy()
211 + bv->theLockingInset()->insetInInsetY()
212 + y - cursor.row()->baseline();
214 new_y = cursor.y() - cursor.row()->baseline();
217 bv->screen().draw(bv->text, bv, new_y);
218 if (cursor.row()->next()) {
220 setCursor(bv, cur, cursor.row()->next()->par(),
221 cursor.row()->next()->pos(), false);
222 if (cur.y() < int(first_y + bv->workarea().workHeight())) {
223 cursorDown(bv, true);
226 bv->updateScrollbar();
230 void LyXText::update(BufferView * bv, bool changed)
232 BufferView::UpdateCodes c = BufferView::SELECT | BufferView::FITCUR;
234 bv->update(this, c | BufferView::CHANGE);
240 void specialChar(LyXText * lt, BufferView * bv, InsetSpecialChar::Kind kind)
244 InsetSpecialChar * new_inset = new InsetSpecialChar(kind);
245 if (!bv->insertInset(new_inset))
248 bv->updateInset(new_inset, true);
252 Inset::RESULT LyXText::dispatch(FuncRequest const & cmd)
254 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: action[" << cmd.action
255 <<"] arg[" << cmd.argument << "]" << endl;
257 BufferView * bv = cmd.view();
259 switch (cmd.action) {
261 case LFUN_APPENDIX: {
262 Paragraph * par = cursor.par();
263 bool start = !par->params().startOfAppendix();
265 // ensure that we have only one start_of_appendix in this document
266 Paragraph * tmp = ownerParagraph();
267 for (; tmp; tmp = tmp->next())
268 tmp->params().startOfAppendix(false);
270 par->params().startOfAppendix(start);
272 // we can set the refreshing parameters now
273 status(cmd.view(), LyXText::NEED_MORE_REFRESH);
275 refresh_row = 0; // not needed for full update
276 updateCounters(cmd.view());
277 setCursor(cmd.view(), cursor.par(), cursor.pos());
282 case LFUN_DELETE_WORD_FORWARD:
283 bv->beforeChange(this);
285 deleteWordForward(bv);
290 case LFUN_DELETE_WORD_BACKWARD:
291 bv->beforeChange(this);
293 deleteWordBackward(bv);
298 case LFUN_DELETE_LINE_FORWARD:
299 bv->beforeChange(this);
301 deleteLineForward(bv);
308 if (!selection.mark())
309 bv->beforeChange(this);
316 if (!selection.mark())
317 bv->beforeChange(this);
319 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
320 cursorLeftOneWord(bv);
322 cursorRightOneWord(bv);
327 if (!selection.mark())
328 bv->beforeChange(this);
330 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
331 cursorRightOneWord(bv);
333 cursorLeftOneWord(bv);
337 case LFUN_BEGINNINGBUF:
338 if (!selection.mark())
339 bv->beforeChange(this);
346 if (selection.mark())
347 bv->beforeChange(this);
355 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
359 finishChange(bv, true);
364 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
368 finishChange(bv, true);
374 finishChange(bv, true);
379 cursorDown(bv, true);
380 finishChange(bv, true);
383 case LFUN_UP_PARAGRAPHSEL:
385 cursorUpParagraph(bv);
386 finishChange(bv, true);
389 case LFUN_DOWN_PARAGRAPHSEL:
391 cursorDownParagraph(bv);
392 finishChange(bv, true);
398 finishChange(bv, true);
404 finishChange(bv, true);
410 finishChange(bv, true);
416 finishChange(bv, true);
419 case LFUN_WORDRIGHTSEL:
421 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
422 cursorLeftOneWord(bv);
424 cursorRightOneWord(bv);
425 finishChange(bv, true);
428 case LFUN_WORDLEFTSEL:
430 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
431 cursorRightOneWord(bv);
433 cursorLeftOneWord(bv);
434 finishChange(bv, true);
438 bool is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
439 if (!selection.mark())
440 bv->beforeChange(this);
443 cursorLeft(bv, false);
444 if (cursor.pos() < cursor.par()->size()
445 && cursor.par()->isInset(cursor.pos())
446 && isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
447 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
448 cmd.message(tmpinset->editMessage());
449 tmpinset->edit(bv, !is_rtl);
453 cursorRight(bv, false);
459 // This is soooo ugly. Isn`t it possible to make
461 bool const is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
462 if (!selection.mark())
463 bv->beforeChange(this);
465 LyXCursor const cur = cursor;
467 cursorLeft(bv, false);
468 if ((is_rtl || cur != cursor) && // only if really moved!
469 cursor.pos() < cursor.par()->size() &&
470 cursor.par()->isInset(cursor.pos()) &&
471 isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
472 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
473 cmd.message(tmpinset->editMessage());
474 tmpinset->edit(bv, is_rtl);
478 cursorRight(bv, false);
484 if (!selection.mark())
485 bv->beforeChange(this);
486 bv->update(this, BufferView::UPDATE);
492 if (!selection.mark())
493 bv->beforeChange(this);
494 bv->update(this, BufferView::UPDATE);
499 case LFUN_UP_PARAGRAPH:
500 if (!selection.mark())
501 bv->beforeChange(this);
502 bv->update(this, BufferView::UPDATE);
503 cursorUpParagraph(bv);
507 case LFUN_DOWN_PARAGRAPH:
508 if (!selection.mark())
509 bv->beforeChange(this);
510 bv->update(this, BufferView::UPDATE);
511 cursorDownParagraph(bv);
512 finishChange(bv, false);
516 if (!selection.mark())
517 bv->beforeChange(this);
518 bv->update(this, BufferView::UPDATE);
520 finishChange(bv, false);
523 // moveCursorUpdate(false, false);
524 // owner_->view_state_changed();
528 if (!selection.mark())
529 bv->beforeChange(this);
530 bv->update(this, BufferView::UPDATE);
532 finishChange(bv, false);
536 if (!selection.mark())
537 bv->beforeChange(this);
540 finishChange(bv, false);
544 if (!selection.mark())
545 bv->beforeChange(this);
548 finishChange(bv, false);
552 bv->beforeChange(this);
553 insertChar(bv, Paragraph::META_NEWLINE);
555 setCursor(bv, cursor.par(), cursor.pos());
556 bv->moveCursorUpdate(false);
560 if (!selection.set()) {
562 selection.cursor = cursor;
564 // It is possible to make it a lot faster still
565 // just comment out the line below...
569 cutSelection(bv, true);
572 bv->moveCursorUpdate(false);
573 bv->owner()->view_state_changed();
577 case LFUN_DELETE_SKIP:
578 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
579 if (!selection.set()) {
580 LyXCursor cur = cursor;
581 if (cur.pos() == cur.par()->size()) {
585 && !(cur.par()->params().spaceTop()
586 == VSpace (VSpace::NONE))) {
588 cur.par()->params().lineTop(),
589 cur.par()->params().lineBottom(),
590 cur.par()->params().pagebreakTop(),
591 cur.par()->params().pagebreakBottom(),
592 VSpace(VSpace::NONE),
593 cur.par()->params().spaceBottom(),
594 cur.par()->params().spacing(),
595 cur.par()->params().align(),
596 cur.par()->params().labelWidthString(), 0);
602 selection.cursor = cursor;
606 selection.cursor = cursor;
610 cutSelection(bv, true);
617 if (!selection.set()) {
618 if (bv->owner()->getIntl().getTransManager().backspace()) {
620 selection.cursor = cursor;
622 // It is possible to make it a lot faster still
623 // just comment out the line below...
628 cutSelection(bv, true);
631 bv->owner()->view_state_changed();
635 case LFUN_BACKSPACE_SKIP:
636 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
637 if (!selection.set()) {
638 LyXCursor cur = cursor;
640 && !(cur.par()->params().spaceTop()
641 == VSpace (VSpace::NONE))) {
643 cur.par()->params().lineTop(),
644 cur.par()->params().lineBottom(),
645 cur.par()->params().pagebreakTop(),
646 cur.par()->params().pagebreakBottom(),
647 VSpace(VSpace::NONE), cur.par()->params().spaceBottom(),
648 cur.par()->params().spacing(),
649 cur.par()->params().align(),
650 cur.par()->params().labelWidthString(), 0);
653 selection.cursor = cur;
657 cutSelection(bv, true);
662 case LFUN_BREAKPARAGRAPH:
663 bv->beforeChange(this);
664 breakParagraph(bv, 0);
666 selection.cursor = cursor;
668 bv->owner()->view_state_changed();
671 case LFUN_BREAKPARAGRAPHKEEPLAYOUT:
672 bv->beforeChange(this);
673 breakParagraph(bv, 1);
675 selection.cursor = cursor;
677 bv->owner()->view_state_changed();
680 case LFUN_BREAKPARAGRAPH_SKIP: {
681 // When at the beginning of a paragraph, remove
682 // indentation and add a "defskip" at the top.
683 // Otherwise, do the same as LFUN_BREAKPARAGRAPH.
684 LyXCursor cur = cursor;
685 bv->beforeChange(this);
686 if (cur.pos() == 0) {
687 if (cur.par()->params().spaceTop() == VSpace(VSpace::NONE)) {
689 cur.par()->params().lineTop(),
690 cur.par()->params().lineBottom(),
691 cur.par()->params().pagebreakTop(),
692 cur.par()->params().pagebreakBottom(),
693 VSpace(VSpace::DEFSKIP), cur.par()->params().spaceBottom(),
694 cur.par()->params().spacing(),
695 cur.par()->params().align(),
696 cur.par()->params().labelWidthString(), 1);
701 breakParagraph(bv, 0);
705 selection.cursor = cur;
707 bv->owner()->view_state_changed();
711 case LFUN_PARAGRAPH_SPACING: {
712 Paragraph * par = cursor.par();
713 Spacing::Space cur_spacing = par->params().spacing().getSpace();
714 float cur_value = 1.0;
715 if (cur_spacing == Spacing::Other)
716 cur_value = par->params().spacing().getValue();
718 istringstream is(cmd.argument.c_str());
721 Spacing::Space new_spacing = cur_spacing;
722 float new_value = cur_value;
724 lyxerr << "Missing argument to `paragraph-spacing'"
726 } else if (tmp == "single") {
727 new_spacing = Spacing::Single;
728 } else if (tmp == "onehalf") {
729 new_spacing = Spacing::Onehalf;
730 } else if (tmp == "double") {
731 new_spacing = Spacing::Double;
732 } else if (tmp == "other") {
733 new_spacing = Spacing::Other;
736 lyxerr << "new_value = " << tmpval << endl;
739 } else if (tmp == "default") {
740 new_spacing = Spacing::Default;
742 lyxerr << _("Unknown spacing argument: ")
743 << cmd.argument << endl;
745 if (cur_spacing != new_spacing || cur_value != new_value) {
746 par->params().spacing(Spacing(new_spacing, new_value));
753 case LFUN_INSET_TOGGLE:
755 bv->beforeChange(this);
762 case LFUN_PROTECTEDSPACE:
763 if (cursor.par()->layout()->free_spacing) {
767 specialChar(this, bv, InsetSpecialChar::PROTECTED_SEPARATOR);
769 bv->moveCursorUpdate(false);
772 case LFUN_HYPHENATION:
773 specialChar(this, bv, InsetSpecialChar::HYPHENATION);
776 case LFUN_LIGATURE_BREAK:
777 specialChar(this, bv, InsetSpecialChar::LIGATURE_BREAK);
781 specialChar(this, bv, InsetSpecialChar::LDOTS);
787 insertChar(bv, Paragraph::META_HFILL);
791 case LFUN_END_OF_SENTENCE:
792 specialChar(this, bv, InsetSpecialChar::END_OF_SENTENCE);
795 case LFUN_MENU_SEPARATOR:
796 specialChar(this, bv, InsetSpecialChar::MENU_SEPARATOR);
800 bv->beforeChange(this);
802 selection.cursor = cursor;
803 cmd.message(N_("Mark off"));
807 bv->beforeChange(this);
808 selection.mark(true);
810 selection.cursor = cursor;
811 cmd.message(N_("Mark on"));
815 bv->beforeChange(this);
816 if (selection.mark()) {
818 cmd.message(N_("Mark removed"));
820 selection.mark(true);
822 cmd.message(N_("Mark set"));
824 selection.cursor = cursor;
827 case LFUN_UPCASE_WORD:
829 changeCase(bv, LyXText::text_uppercase);
831 bv->updateInset(inset_owner, true);
835 case LFUN_LOWCASE_WORD:
837 changeCase(bv, LyXText::text_lowercase);
839 bv->updateInset(inset_owner, true);
843 case LFUN_CAPITALIZE_WORD:
845 changeCase(bv, LyXText::text_capitalization);
847 bv->updateInset(inset_owner, true);
851 case LFUN_TRANSPOSE_CHARS:
855 bv->updateInset(inset_owner, true);
860 cmd.message(_("Paste"));
862 // clear the selection
863 bv->toggleSelection();
867 clearSelection(); // bug 393
876 cutSelection(bv, true);
878 cmd.message(_("Cut"));
883 cmd.message(_("Copy"));
886 case LFUN_BEGINNINGBUFSEL:
888 return Inset::UNDISPATCHED;
891 finishChange(bv, true);
896 return Inset::UNDISPATCHED;
899 finishChange(bv, true);
903 cmd.message(tostr(cursor.x()) + ' ' + tostr(cursor.y()));
909 istringstream is(cmd.argument.c_str());
912 lyxerr << "SETXY: Could not parse coordinates in '"
913 << cmd.argument << std::endl;
915 setCursorFromCoordinates(bv, x, y);
920 if (current_font.shape() == LyXFont::ITALIC_SHAPE)
922 else if (current_font.shape() == LyXFont::SMALLCAPS_SHAPE)
929 cmd.message(tostr(cursor.par()->layout()));
933 lyxerr[Debug::INFO] << "LFUN_LAYOUT: (arg) "
934 << cmd.argument << endl;
936 // This is not the good solution to the empty argument
937 // problem, but it will hopefully suffice for 1.2.0.
938 // The correct solution would be to augument the
939 // function list/array with information about what
940 // functions needs arguments and their type.
941 if (cmd.argument.empty()) {
942 cmd.errorMessage(_("LyX function 'layout' needs an argument."));
946 // Derive layout number from given argument (string)
947 // and current buffer's textclass (number)
948 LyXTextClass const & tclass = bv->buffer()->params.getLyXTextClass();
949 bool hasLayout = tclass.hasLayout(cmd.argument);
950 string layout = cmd.argument;
952 // If the entry is obsolete, use the new one instead.
954 string const & obs = tclass[layout]->obsoleted_by();
960 cmd.errorMessage(string(N_("Layout ")) + cmd.argument +
965 bool change_layout = (current_layout != layout);
966 if (!change_layout && selection.set() &&
967 selection.start.par() != selection.end.par())
969 Paragraph * spar = selection.start.par();
970 Paragraph * epar = selection.end.par()->next();
971 while (spar != epar) {
972 if (spar->layout()->name() != current_layout) {
973 change_layout = true;
980 current_layout = layout;
982 setLayout(bv, layout);
983 bv->owner()->setLayout(layout);
990 case LFUN_PASTESELECTION: {
994 // this was originally a beforeChange(bv->text), i.e
995 // the outermost LyXText!
996 bv->beforeChange(this);
997 string const clip(bv->workarea().getClipboard());
999 if (cmd.argument == "paragraph")
1000 insertStringAsParagraphs(bv, clip);
1002 insertStringAsLines(bv, clip);
1009 case LFUN_GOTOERROR:
1010 gotoInset(bv, Inset::ERROR_CODE, false);
1014 gotoInset(bv, Inset::NOTE_CODE, false);
1017 case LFUN_REFERENCE_GOTO:
1019 vector<Inset::Code> tmp;
1020 tmp.push_back(Inset::LABEL_CODE);
1021 tmp.push_back(Inset::REF_CODE);
1022 gotoInset(bv, tmp, true);
1027 Paragraph const * par = cursor.par();
1028 lyx::pos_type pos = cursor.pos();
1033 else if (par->isInset(pos - 1) && par->getInset(pos - 1)->isSpace())
1036 c = par->getChar(pos - 1);
1039 LyXLayout_ptr const & style = par->layout();
1041 if (style->pass_thru ||
1042 par->getFontSettings(bv->buffer()->params,
1043 pos).language()->lang() == "hebrew" ||
1044 (!bv->insertInset(new InsetQuotes(c, bv->buffer()->params))))
1045 bv->owner()->dispatch(FuncRequest(LFUN_SELFINSERT, "\""));
1049 case LFUN_SELFINSERT: {
1050 if (cmd.argument.empty())
1053 // Automatically delete the currently selected
1054 // text and replace it with what is being
1055 // typed in now. Depends on lyxrc settings
1056 // "auto_region_delete", which defaults to
1059 if (lyxrc.auto_region_delete) {
1060 if (selection.set()) {
1061 cutSelection(bv, false, false);
1064 bv->workarea().haveSelection(false);
1067 bv->beforeChange(this);
1068 LyXFont const old_font(real_current_font);
1070 string::const_iterator cit = cmd.argument.begin();
1071 string::const_iterator end = cmd.argument.end();
1072 for (; cit != end; ++cit)
1073 bv->owner()->getIntl().getTransManager().
1074 TranslateAndInsert(*cit, this);
1077 selection.cursor = cursor;
1078 bv->moveCursorUpdate(false);
1080 // real_current_font.number can change so we need to
1081 // update the minibuffer
1082 if (old_font != real_current_font)
1083 bv->owner()->view_state_changed();
1088 return Inset::UNDISPATCHED;
1091 return Inset::DISPATCHED;