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"
41 extern string current_layout;
45 void finishChange(BufferView * bv, bool fitcur = false)
48 bv->moveCursorUpdate(fitcur);
49 bv->owner()->view_state_changed();
55 bool LyXText::gotoNextInset(BufferView * bv,
56 vector<Inset::Code> const & codes, string const & contents) const
58 LyXCursor res = cursor;
61 if (res.pos() < res.par()->size() - 1) {
62 res.pos(res.pos() + 1);
64 res.par(res.par()->next());
69 !(res.par()->isInset(res.pos())
70 && (inset = res.par()->getInset(res.pos())) != 0
71 && find(codes.begin(), codes.end(), inset->lyxCode())
73 && (contents.empty() ||
74 static_cast<InsetCommand *>(
75 res.par()->getInset(res.pos()))->getContents()
79 setCursor(bv, res.par(), res.pos(), false);
86 void LyXText::gotoInset(BufferView * bv, vector<Inset::Code> const & codes,
90 bv->beforeChange(this);
94 if (same_content && cursor.par()->isInset(cursor.pos())) {
95 Inset const * inset = cursor.par()->getInset(cursor.pos());
96 if (find(codes.begin(), codes.end(), inset->lyxCode())
98 contents = static_cast<InsetCommand const *>(inset)->getContents();
101 if (!gotoNextInset(bv, codes, contents)) {
102 if (cursor.pos() || cursor.par() != ownerParagraph()) {
103 LyXCursor tmp = cursor;
104 cursor.par(ownerParagraph());
106 if (!gotoNextInset(bv, codes, contents)) {
108 bv->owner()->message(_("No more insets"));
111 bv->owner()->message(_("No more insets"));
115 selection.cursor = cursor;
119 void LyXText::gotoInset(BufferView * bv, Inset::Code code, bool same_content)
121 gotoInset(bv, vector<Inset::Code>(1, code), same_content);
125 void LyXText::cursorPrevious(BufferView * bv)
127 if (!cursor.row()->previous()) {
129 int new_y = bv->text->first_y - bv->workarea().workHeight();
130 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
131 bv->updateScrollbar();
137 Row * cursorrow = cursor.row();
139 setCursorFromCoordinates(bv, cursor.x_fix(), y);
143 if (cursorrow == bv->text->cursor.row()) {
144 // we have a row which is higher than the workarea so we leave the
145 // cursor on the start of the row and move only the draw up as soon
146 // as we move the cursor or do something while inside the row (it may
147 // span several workarea-heights) we'll move to the top again, but this
148 // is better than just jump down and only display part of the row.
149 new_y = bv->text->first_y - bv->workarea().workHeight();
152 new_y = bv->text->cursor.iy()
153 + bv->theLockingInset()->insetInInsetY() + y
154 + cursor.row()->height()
155 - bv->workarea().workHeight() + 1;
158 - cursor.row()->baseline()
159 + cursor.row()->height()
160 - bv->workarea().workHeight() + 1;
163 bv->screen().draw(bv->text, bv, new_y < 0 ? 0 : new_y);
164 if (cursor.row()->previous()) {
166 setCursor(bv, cur, cursor.row()->previous()->par(),
167 cursor.row()->previous()->pos(), false);
168 if (cur.y() > first_y) {
172 bv->updateScrollbar();
176 void LyXText::cursorNext(BufferView * bv)
178 if (!cursor.row()->next()) {
179 int y = cursor.y() - cursor.row()->baseline() +
180 cursor.row()->height();
181 if (y > int(first_y + bv->workarea().workHeight())) {
182 bv->screen().draw(bv->text, bv,
183 bv->text->first_y + bv->workarea().workHeight());
184 bv->updateScrollbar();
189 int y = first_y + bv->workarea().workHeight();
190 if (inset_owner && !first_y) {
191 y -= (bv->text->cursor.iy()
193 + bv->theLockingInset()->insetInInsetY());
198 Row * cursorrow = cursor.row();
199 setCursorFromCoordinates(bv, cursor.x_fix(), y);
200 // + workarea().workHeight());
204 if (cursorrow == bv->text->cursor.row()) {
205 // we have a row which is higher than the workarea so we leave the
206 // cursor on the start of the row and move only the draw down as soon
207 // as we move the cursor or do something while inside the row (it may
208 // span several workarea-heights) we'll move to the top again, but this
209 // is better than just jump down and only display part of the row.
210 new_y = bv->text->first_y + bv->workarea().workHeight();
213 new_y = bv->text->cursor.iy()
214 + bv->theLockingInset()->insetInInsetY()
215 + y - cursor.row()->baseline();
217 new_y = cursor.y() - cursor.row()->baseline();
220 bv->screen().draw(bv->text, bv, new_y);
221 if (cursor.row()->next()) {
223 setCursor(bv, cur, cursor.row()->next()->par(),
224 cursor.row()->next()->pos(), false);
225 if (cur.y() < int(first_y + bv->workarea().workHeight())) {
226 cursorDown(bv, true);
229 bv->updateScrollbar();
233 void LyXText::update(BufferView * bv, bool changed)
235 BufferView::UpdateCodes c = BufferView::SELECT | BufferView::FITCUR;
237 bv->update(this, c | BufferView::CHANGE);
243 void specialChar(LyXText * lt, BufferView * bv, InsetSpecialChar::Kind kind)
247 InsetSpecialChar * new_inset = new InsetSpecialChar(kind);
248 if (!bv->insertInset(new_inset))
251 bv->updateInset(new_inset, true);
255 Inset::RESULT LyXText::dispatch(FuncRequest const & cmd)
257 lyxerr[Debug::ACTION] << "LyXFunc::dispatch: action[" << cmd.action
258 <<"] arg[" << cmd.argument << "]" << endl;
260 BufferView * bv = cmd.view();
262 switch (cmd.action) {
264 case LFUN_APPENDIX: {
265 Paragraph * par = cursor.par();
266 bool start = !par->params().startOfAppendix();
268 // ensure that we have only one start_of_appendix in this document
269 Paragraph * tmp = ownerParagraph();
270 for (; tmp; tmp = tmp->next())
271 tmp->params().startOfAppendix(false);
273 par->params().startOfAppendix(start);
275 // we can set the refreshing parameters now
276 status(cmd.view(), LyXText::NEED_MORE_REFRESH);
278 refresh_row = 0; // not needed for full update
279 updateCounters(cmd.view());
280 setCursor(cmd.view(), cursor.par(), cursor.pos());
285 case LFUN_DELETE_WORD_FORWARD:
286 bv->beforeChange(this);
288 deleteWordForward(bv);
293 case LFUN_DELETE_WORD_BACKWARD:
294 bv->beforeChange(this);
296 deleteWordBackward(bv);
301 case LFUN_DELETE_LINE_FORWARD:
302 bv->beforeChange(this);
304 deleteLineForward(bv);
311 if (!selection.mark())
312 bv->beforeChange(this);
319 if (!selection.mark())
320 bv->beforeChange(this);
322 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
323 cursorLeftOneWord(bv);
325 cursorRightOneWord(bv);
330 if (!selection.mark())
331 bv->beforeChange(this);
333 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
334 cursorRightOneWord(bv);
336 cursorLeftOneWord(bv);
340 case LFUN_BEGINNINGBUF:
341 if (!selection.mark())
342 bv->beforeChange(this);
349 if (selection.mark())
350 bv->beforeChange(this);
358 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
362 finishChange(bv, true);
367 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
371 finishChange(bv, true);
377 finishChange(bv, true);
382 cursorDown(bv, true);
383 finishChange(bv, true);
386 case LFUN_UP_PARAGRAPHSEL:
388 cursorUpParagraph(bv);
389 finishChange(bv, true);
392 case LFUN_DOWN_PARAGRAPHSEL:
394 cursorDownParagraph(bv);
395 finishChange(bv, true);
401 finishChange(bv, true);
407 finishChange(bv, true);
413 finishChange(bv, true);
419 finishChange(bv, true);
422 case LFUN_WORDRIGHTSEL:
424 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
425 cursorLeftOneWord(bv);
427 cursorRightOneWord(bv);
428 finishChange(bv, true);
431 case LFUN_WORDLEFTSEL:
433 if (cursor.par()->isRightToLeftPar(bv->buffer()->params))
434 cursorRightOneWord(bv);
436 cursorLeftOneWord(bv);
437 finishChange(bv, true);
441 bool is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
442 if (!selection.mark())
443 bv->beforeChange(this);
446 cursorLeft(bv, false);
447 if (cursor.pos() < cursor.par()->size()
448 && cursor.par()->isInset(cursor.pos())
449 && isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
450 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
451 cmd.message(tmpinset->editMessage());
452 tmpinset->edit(bv, !is_rtl);
456 cursorRight(bv, false);
462 // This is soooo ugly. Isn`t it possible to make
464 bool const is_rtl = cursor.par()->isRightToLeftPar(bv->buffer()->params);
465 if (!selection.mark())
466 bv->beforeChange(this);
468 LyXCursor const cur = cursor;
470 cursorLeft(bv, false);
471 if ((is_rtl || cur != cursor) && // only if really moved!
472 cursor.pos() < cursor.par()->size() &&
473 cursor.par()->isInset(cursor.pos()) &&
474 isHighlyEditableInset(cursor.par()->getInset(cursor.pos()))) {
475 Inset * tmpinset = cursor.par()->getInset(cursor.pos());
476 cmd.message(tmpinset->editMessage());
477 tmpinset->edit(bv, is_rtl);
481 cursorRight(bv, false);
487 if (!selection.mark())
488 bv->beforeChange(this);
489 bv->update(this, BufferView::UPDATE);
495 if (!selection.mark())
496 bv->beforeChange(this);
497 bv->update(this, BufferView::UPDATE);
502 case LFUN_UP_PARAGRAPH:
503 if (!selection.mark())
504 bv->beforeChange(this);
505 bv->update(this, BufferView::UPDATE);
506 cursorUpParagraph(bv);
510 case LFUN_DOWN_PARAGRAPH:
511 if (!selection.mark())
512 bv->beforeChange(this);
513 bv->update(this, BufferView::UPDATE);
514 cursorDownParagraph(bv);
515 finishChange(bv, false);
519 if (!selection.mark())
520 bv->beforeChange(this);
521 bv->update(this, BufferView::UPDATE);
523 finishChange(bv, false);
526 // moveCursorUpdate(false, false);
527 // owner_->view_state_changed();
531 if (!selection.mark())
532 bv->beforeChange(this);
533 bv->update(this, BufferView::UPDATE);
535 finishChange(bv, false);
539 if (!selection.mark())
540 bv->beforeChange(this);
543 finishChange(bv, false);
547 if (!selection.mark())
548 bv->beforeChange(this);
551 finishChange(bv, false);
555 bv->beforeChange(this);
556 insertChar(bv, Paragraph::META_NEWLINE);
558 setCursor(bv, cursor.par(), cursor.pos());
559 bv->moveCursorUpdate(false);
563 if (!selection.set()) {
565 selection.cursor = cursor;
567 // It is possible to make it a lot faster still
568 // just comment out the line below...
572 cutSelection(bv, true);
575 bv->moveCursorUpdate(false);
576 bv->owner()->view_state_changed();
580 case LFUN_DELETE_SKIP:
581 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
582 if (!selection.set()) {
583 LyXCursor cur = cursor;
584 if (cur.pos() == cur.par()->size()) {
588 && !(cur.par()->params().spaceTop()
589 == VSpace (VSpace::NONE))) {
591 cur.par()->params().lineTop(),
592 cur.par()->params().lineBottom(),
593 cur.par()->params().pagebreakTop(),
594 cur.par()->params().pagebreakBottom(),
595 VSpace(VSpace::NONE),
596 cur.par()->params().spaceBottom(),
597 cur.par()->params().spacing(),
598 cur.par()->params().align(),
599 cur.par()->params().labelWidthString(), 0);
605 selection.cursor = cursor;
609 selection.cursor = cursor;
613 cutSelection(bv, true);
620 if (!selection.set()) {
621 if (bv->owner()->getIntl().getTransManager().backspace()) {
623 selection.cursor = cursor;
625 // It is possible to make it a lot faster still
626 // just comment out the line below...
631 cutSelection(bv, true);
634 bv->owner()->view_state_changed();
638 case LFUN_BACKSPACE_SKIP:
639 // Reverse the effect of LFUN_BREAKPARAGRAPH_SKIP.
640 if (!selection.set()) {
641 LyXCursor cur = cursor;
643 && !(cur.par()->params().spaceTop()
644 == VSpace (VSpace::NONE))) {
646 cur.par()->params().lineTop(),
647 cur.par()->params().lineBottom(),
648 cur.par()->params().pagebreakTop(),
649 cur.par()->params().pagebreakBottom(),
650 VSpace(VSpace::NONE), cur.par()->params().spaceBottom(),
651 cur.par()->params().spacing(),
652 cur.par()->params().align(),
653 cur.par()->params().labelWidthString(), 0);
656 selection.cursor = cur;
660 cutSelection(bv, true);
665 case LFUN_BREAKPARAGRAPH:
666 bv->beforeChange(this);
667 breakParagraph(bv, 0);
669 selection.cursor = cursor;
671 bv->owner()->view_state_changed();
674 case LFUN_BREAKPARAGRAPHKEEPLAYOUT:
675 bv->beforeChange(this);
676 breakParagraph(bv, 1);
678 selection.cursor = cursor;
680 bv->owner()->view_state_changed();
683 case LFUN_BREAKPARAGRAPH_SKIP: {
684 // When at the beginning of a paragraph, remove
685 // indentation and add a "defskip" at the top.
686 // Otherwise, do the same as LFUN_BREAKPARAGRAPH.
687 LyXCursor cur = cursor;
688 bv->beforeChange(this);
689 if (cur.pos() == 0) {
690 if (cur.par()->params().spaceTop() == VSpace(VSpace::NONE)) {
692 cur.par()->params().lineTop(),
693 cur.par()->params().lineBottom(),
694 cur.par()->params().pagebreakTop(),
695 cur.par()->params().pagebreakBottom(),
696 VSpace(VSpace::DEFSKIP), cur.par()->params().spaceBottom(),
697 cur.par()->params().spacing(),
698 cur.par()->params().align(),
699 cur.par()->params().labelWidthString(), 1);
704 breakParagraph(bv, 0);
708 selection.cursor = cur;
710 bv->owner()->view_state_changed();
714 case LFUN_PARAGRAPH_SPACING: {
715 Paragraph * par = cursor.par();
716 Spacing::Space cur_spacing = par->params().spacing().getSpace();
717 float cur_value = 1.0;
718 if (cur_spacing == Spacing::Other)
719 cur_value = par->params().spacing().getValue();
721 istringstream is(cmd.argument.c_str());
724 Spacing::Space new_spacing = cur_spacing;
725 float new_value = cur_value;
727 lyxerr << "Missing argument to `paragraph-spacing'"
729 } else if (tmp == "single") {
730 new_spacing = Spacing::Single;
731 } else if (tmp == "onehalf") {
732 new_spacing = Spacing::Onehalf;
733 } else if (tmp == "double") {
734 new_spacing = Spacing::Double;
735 } else if (tmp == "other") {
736 new_spacing = Spacing::Other;
739 lyxerr << "new_value = " << tmpval << endl;
742 } else if (tmp == "default") {
743 new_spacing = Spacing::Default;
745 lyxerr << _("Unknown spacing argument: ")
746 << cmd.argument << endl;
748 if (cur_spacing != new_spacing || cur_value != new_value) {
749 par->params().spacing(Spacing(new_spacing, new_value));
756 case LFUN_INSET_TOGGLE:
758 bv->beforeChange(this);
765 case LFUN_PROTECTEDSPACE:
766 if (cursor.par()->layout()->free_spacing) {
770 specialChar(this, bv, InsetSpecialChar::PROTECTED_SEPARATOR);
772 bv->moveCursorUpdate(false);
775 case LFUN_HYPHENATION:
776 specialChar(this, bv, InsetSpecialChar::HYPHENATION);
779 case LFUN_LIGATURE_BREAK:
780 specialChar(this, bv, InsetSpecialChar::LIGATURE_BREAK);
784 specialChar(this, bv, InsetSpecialChar::LDOTS);
790 insertChar(bv, Paragraph::META_HFILL);
794 case LFUN_END_OF_SENTENCE:
795 specialChar(this, bv, InsetSpecialChar::END_OF_SENTENCE);
798 case LFUN_MENU_SEPARATOR:
799 specialChar(this, bv, InsetSpecialChar::MENU_SEPARATOR);
803 bv->beforeChange(this);
805 selection.cursor = cursor;
806 cmd.message(N_("Mark off"));
810 bv->beforeChange(this);
811 selection.mark(true);
813 selection.cursor = cursor;
814 cmd.message(N_("Mark on"));
818 bv->beforeChange(this);
819 if (selection.mark()) {
821 cmd.message(N_("Mark removed"));
823 selection.mark(true);
825 cmd.message(N_("Mark set"));
827 selection.cursor = cursor;
830 case LFUN_UPCASE_WORD:
832 changeCase(bv, LyXText::text_uppercase);
834 bv->updateInset(inset_owner, true);
838 case LFUN_LOWCASE_WORD:
840 changeCase(bv, LyXText::text_lowercase);
842 bv->updateInset(inset_owner, true);
846 case LFUN_CAPITALIZE_WORD:
848 changeCase(bv, LyXText::text_capitalization);
850 bv->updateInset(inset_owner, true);
854 case LFUN_TRANSPOSE_CHARS:
858 bv->updateInset(inset_owner, true);
863 cmd.message(_("Paste"));
865 // clear the selection
866 bv->toggleSelection();
870 clearSelection(); // bug 393
879 cutSelection(bv, true);
881 cmd.message(_("Cut"));
886 cmd.message(_("Copy"));
889 case LFUN_BEGINNINGBUFSEL:
891 return Inset::UNDISPATCHED;
894 finishChange(bv, true);
899 return Inset::UNDISPATCHED;
902 finishChange(bv, true);
906 cmd.message(tostr(cursor.x()) + ' ' + tostr(cursor.y()));
912 istringstream is(cmd.argument.c_str());
915 lyxerr << "SETXY: Could not parse coordinates in '"
916 << cmd.argument << std::endl;
918 setCursorFromCoordinates(bv, x, y);
923 if (current_font.shape() == LyXFont::ITALIC_SHAPE)
925 else if (current_font.shape() == LyXFont::SMALLCAPS_SHAPE)
932 cmd.message(tostr(cursor.par()->layout()));
936 lyxerr[Debug::INFO] << "LFUN_LAYOUT: (arg) "
937 << cmd.argument << endl;
939 // This is not the good solution to the empty argument
940 // problem, but it will hopefully suffice for 1.2.0.
941 // The correct solution would be to augument the
942 // function list/array with information about what
943 // functions needs arguments and their type.
944 if (cmd.argument.empty()) {
945 cmd.errorMessage(_("LyX function 'layout' needs an argument."));
949 // Derive layout number from given argument (string)
950 // and current buffer's textclass (number)
951 LyXTextClass const & tclass = bv->buffer()->params.getLyXTextClass();
952 bool hasLayout = tclass.hasLayout(cmd.argument);
953 string layout = cmd.argument;
955 // If the entry is obsolete, use the new one instead.
957 string const & obs = tclass[layout]->obsoleted_by();
963 cmd.errorMessage(string(N_("Layout ")) + cmd.argument +
968 bool change_layout = (current_layout != layout);
969 if (!change_layout && selection.set() &&
970 selection.start.par() != selection.end.par())
972 Paragraph * spar = selection.start.par();
973 Paragraph * epar = selection.end.par()->next();
974 while (spar != epar) {
975 if (spar->layout()->name() != current_layout) {
976 change_layout = true;
983 current_layout = layout;
985 setLayout(bv, layout);
986 bv->owner()->setLayout(layout);
993 case LFUN_PASTESELECTION: {
997 // this was originally a beforeChange(bv->text), i.e
998 // the outermost LyXText!
999 bv->beforeChange(this);
1000 string const clip(bv->workarea().getClipboard());
1001 if (!clip.empty()) {
1002 if (cmd.argument == "paragraph")
1003 insertStringAsParagraphs(bv, clip);
1005 insertStringAsLines(bv, clip);
1012 case LFUN_GOTOERROR:
1013 gotoInset(bv, Inset::ERROR_CODE, false);
1017 gotoInset(bv, Inset::NOTE_CODE, false);
1020 case LFUN_REFERENCE_GOTO:
1022 vector<Inset::Code> tmp;
1023 tmp.push_back(Inset::LABEL_CODE);
1024 tmp.push_back(Inset::REF_CODE);
1025 gotoInset(bv, tmp, true);
1030 Paragraph const * par = cursor.par();
1031 lyx::pos_type pos = cursor.pos();
1036 else if (par->isInset(pos - 1) && par->getInset(pos - 1)->isSpace())
1039 c = par->getChar(pos - 1);
1042 LyXLayout_ptr const & style = par->layout();
1044 if (style->pass_thru ||
1045 par->getFontSettings(bv->buffer()->params,
1046 pos).language()->lang() == "hebrew" ||
1047 (!bv->insertInset(new InsetQuotes(c, bv->buffer()->params))))
1048 bv->owner()->dispatch(FuncRequest(LFUN_SELFINSERT, "\""));
1052 case LFUN_DATE_INSERT: { // jdblair: date-insert cmd
1053 time_t now_time_t = time(NULL);
1054 struct tm * now_tm = localtime(&now_time_t);
1055 setlocale(LC_TIME, "");
1057 if (!cmd.argument.empty())
1060 arg = lyxrc.date_insert_format;
1062 int const datetmp_len =
1063 ::strftime(datetmp, 32, arg.c_str(), now_tm);
1065 for (int i = 0; i < datetmp_len; i++) {
1066 insertChar(bv, datetmp[i]);
1069 selection.cursor = cursor;
1070 bv->moveCursorUpdate(false);
1074 case LFUN_SELFINSERT: {
1075 if (cmd.argument.empty())
1078 // Automatically delete the currently selected
1079 // text and replace it with what is being
1080 // typed in now. Depends on lyxrc settings
1081 // "auto_region_delete", which defaults to
1084 if (lyxrc.auto_region_delete) {
1085 if (selection.set()) {
1086 cutSelection(bv, false, false);
1089 bv->workarea().haveSelection(false);
1092 bv->beforeChange(this);
1093 LyXFont const old_font(real_current_font);
1095 string::const_iterator cit = cmd.argument.begin();
1096 string::const_iterator end = cmd.argument.end();
1097 for (; cit != end; ++cit)
1098 bv->owner()->getIntl().getTransManager().
1099 TranslateAndInsert(*cit, this);
1102 selection.cursor = cursor;
1103 bv->moveCursorUpdate(false);
1105 // real_current_font.number can change so we need to
1106 // update the minibuffer
1107 if (old_font != real_current_font)
1108 bv->owner()->view_state_changed();
1113 return Inset::UNDISPATCHED;
1116 return Inset::DISPATCHED;