3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
29 #include "coordcache.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
52 #include "insets/insetenv.h"
54 #include "support/textutils.h"
56 #include <boost/current_function.hpp>
64 using std::ostringstream;
69 LyXText::LyXText(BufferView * bv)
70 : maxwidth_(bv ? bv->workWidth() : 100),
71 current_font(LyXFont::ALL_INHERIT),
72 background_color_(LColor::background),
78 void LyXText::init(BufferView * bv)
82 maxwidth_ = bv->workWidth();
87 pit_type const end = paragraphs().size();
88 for (pit_type pit = 0; pit != end; ++pit)
89 pars_[pit].rows().clear();
91 current_font = getFont(pars_[0], 0);
92 updateCounters(*bv->buffer());
96 bool LyXText::isMainText() const
98 return &bv()->buffer()->text() == this;
102 //takes screen x,y coordinates
103 InsetBase * LyXText::checkInsetHit(int x, int y) const
105 pit_type pit = getPitNearY(y);
106 BOOST_ASSERT(pit != -1);
108 Paragraph const & par = pars_[pit];
111 << BOOST_CURRENT_FUNCTION
116 InsetList::const_iterator iit = par.insetlist.begin();
117 InsetList::const_iterator iend = par.insetlist.end();
118 for (; iit != iend; ++iit) {
119 InsetBase * inset = iit->inset;
122 << BOOST_CURRENT_FUNCTION
123 << ": examining inset " << inset << endl;
125 if (theCoords.getInsets().has(inset))
127 << BOOST_CURRENT_FUNCTION
128 << ": xo: " << inset->xo() << "..."
129 << inset->xo() + inset->width()
130 << " yo: " << inset->yo() - inset->ascent()
132 << inset->yo() + inset->descent()
136 << BOOST_CURRENT_FUNCTION
137 << ": inset has no cached position" << endl;
139 if (inset->covers(x, y)) {
141 << BOOST_CURRENT_FUNCTION
142 << ": Hit inset: " << inset << endl;
147 << BOOST_CURRENT_FUNCTION
148 << ": No inset hit. " << endl;
154 // Gets the fully instantiated font at a given position in a paragraph
155 // Basically the same routine as Paragraph::getFont() in paragraph.C.
156 // The difference is that this one is used for displaying, and thus we
157 // are allowed to make cosmetic improvements. For instance make footnotes
159 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
161 BOOST_ASSERT(pos >= 0);
163 LyXLayout_ptr const & layout = par.layout();
167 BufferParams const & params = bv()->buffer()->params();
168 pos_type const body_pos = par.beginOfBody();
170 // We specialize the 95% common case:
171 if (!par.getDepth()) {
172 LyXFont f = par.getFontSettings(params, pos);
175 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
176 return f.realize(layout->reslabelfont);
178 return f.realize(layout->resfont);
181 // The uncommon case need not be optimized as much
184 layoutfont = layout->labelfont;
186 layoutfont = layout->font;
188 LyXFont font = par.getFontSettings(params, pos);
189 font.realize(layoutfont);
192 applyOuterFont(font);
194 // Realize with the fonts of lesser depth.
195 font.realize(defaultfont_);
200 // There are currently two font mechanisms in LyX:
201 // 1. The font attributes in a lyxtext, and
202 // 2. The inset-specific font properties, defined in an inset's
203 // metrics() and draw() methods and handed down the inset chain through
204 // the pi/mi parameters, and stored locally in a lyxtext in font_.
205 // This is where the two are integrated in the final fully realized
207 void LyXText::applyOuterFont(LyXFont & font) const {
209 lf.reduce(defaultfont_);
211 lf.setLanguage(font.language());
216 LyXFont LyXText::getLayoutFont(pit_type const pit) const
218 LyXLayout_ptr const & layout = pars_[pit].layout();
220 if (!pars_[pit].getDepth())
221 return layout->resfont;
223 LyXFont font = layout->font;
224 // Realize with the fonts of lesser depth.
225 //font.realize(outerFont(pit, paragraphs()));
226 font.realize(defaultfont_);
232 LyXFont LyXText::getLabelFont(Paragraph const & par) const
234 LyXLayout_ptr const & layout = par.layout();
237 return layout->reslabelfont;
239 LyXFont font = layout->labelfont;
240 // Realize with the fonts of lesser depth.
241 font.realize(defaultfont_);
247 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
250 LyXLayout_ptr const & layout = pars_[pit].layout();
252 // Get concrete layout font to reduce against
255 if (pos < pars_[pit].beginOfBody())
256 layoutfont = layout->labelfont;
258 layoutfont = layout->font;
260 // Realize against environment font information
261 if (pars_[pit].getDepth()) {
263 while (!layoutfont.resolved() &&
264 tp != pit_type(paragraphs().size()) &&
265 pars_[tp].getDepth()) {
266 tp = outerHook(tp, paragraphs());
267 if (tp != pit_type(paragraphs().size()))
268 layoutfont.realize(pars_[tp].layout()->font);
272 layoutfont.realize(defaultfont_);
274 // Now, reduce font against full layout font
275 font.reduce(layoutfont);
277 pars_[pit].setFont(pos, font);
281 // return past-the-last paragraph influenced by a layout change on pit
282 pit_type LyXText::undoSpan(pit_type pit)
284 pit_type end = paragraphs().size();
285 pit_type nextpit = pit + 1;
288 //because of parindents
289 if (!pars_[pit].getDepth())
290 return boost::next(nextpit);
291 //because of depth constrains
292 for (; nextpit != end; ++pit, ++nextpit) {
293 if (!pars_[pit].getDepth())
300 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
302 BOOST_ASSERT(start != end);
304 BufferParams const & bufparams = bv()->buffer()->params();
305 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
307 for (pit_type pit = start; pit != end; ++pit) {
308 pars_[pit].applyLayout(lyxlayout);
309 if (lyxlayout->margintype == MARGIN_MANUAL)
310 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
315 // set layout over selection and make a total rebreak of those paragraphs
316 void LyXText::setLayout(LCursor & cur, string const & layout)
318 BOOST_ASSERT(this == cur.text());
319 // special handling of new environment insets
320 BufferView & bv = cur.bv();
321 BufferParams const & params = bv.buffer()->params();
322 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
323 if (lyxlayout->is_environment) {
324 // move everything in a new environment inset
325 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
326 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
327 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
328 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
329 InsetBase * inset = new InsetEnvironment(params, layout);
330 insertInset(cur, inset);
331 //inset->edit(cur, true);
332 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
336 pit_type start = cur.selBegin().pit();
337 pit_type end = cur.selEnd().pit() + 1;
338 pit_type undopit = undoSpan(end - 1);
339 recUndo(start, undopit - 1);
340 setLayout(start, end, layout);
341 updateCounters(cur.buffer());
348 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
349 Paragraph const & par, int max_depth)
351 if (par.layout()->labeltype == LABEL_BIBLIO)
353 int const depth = par.params().depth();
354 if (type == LyXText::INC_DEPTH && depth < max_depth)
356 if (type == LyXText::DEC_DEPTH && depth > 0)
365 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
367 BOOST_ASSERT(this == cur.text());
368 pit_type const beg = cur.selBegin().pit();
369 pit_type const end = cur.selEnd().pit() + 1;
370 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
372 for (pit_type pit = beg; pit != end; ++pit) {
373 if (::changeDepthAllowed(type, pars_[pit], max_depth))
375 max_depth = pars_[pit].getMaxDepthAfter();
381 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
383 BOOST_ASSERT(this == cur.text());
384 pit_type const beg = cur.selBegin().pit();
385 pit_type const end = cur.selEnd().pit() + 1;
386 recordUndoSelection(cur);
387 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
389 for (pit_type pit = beg; pit != end; ++pit) {
390 Paragraph & par = pars_[pit];
391 if (::changeDepthAllowed(type, par, max_depth)) {
392 int const depth = par.params().depth();
393 if (type == INC_DEPTH)
394 par.params().depth(depth + 1);
396 par.params().depth(depth - 1);
398 max_depth = par.getMaxDepthAfter();
400 // this handles the counter labels, and also fixes up
401 // depth values for follow-on (child) paragraphs
402 updateCounters(cur.buffer());
406 // set font over selection
407 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
409 BOOST_ASSERT(this == cur.text());
410 // if there is no selection just set the current_font
411 if (!cur.selection()) {
412 // Determine basis font
414 pit_type pit = cur.pit();
415 if (cur.pos() < pars_[pit].beginOfBody())
416 layoutfont = getLabelFont(pars_[pit]);
418 layoutfont = getLayoutFont(pit);
420 // Update current font
421 real_current_font.update(font,
422 cur.buffer().params().language,
425 // Reduce to implicit settings
426 current_font = real_current_font;
427 current_font.reduce(layoutfont);
428 // And resolve it completely
429 real_current_font.realize(layoutfont);
434 // Ok, we have a selection.
435 recordUndoSelection(cur);
437 DocIterator dit = cur.selectionBegin();
438 DocIterator ditend = cur.selectionEnd();
440 BufferParams const & params = cur.buffer().params();
442 // Don't use forwardChar here as ditend might have
443 // pos() == lastpos() and forwardChar would miss it.
444 // Can't use forwardPos either as this descends into
446 for (; dit != ditend; dit.forwardPosNoDescend()) {
447 if (dit.pos() != dit.lastpos()) {
448 LyXFont f = getFont(dit.paragraph(), dit.pos());
449 f.update(font, params.language, toggleall);
450 setCharFont(dit.pit(), dit.pos(), f);
456 // the cursor set functions have a special mechanism. When they
457 // realize you left an empty paragraph, they will delete it.
459 void LyXText::cursorHome(LCursor & cur)
461 BOOST_ASSERT(this == cur.text());
462 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
464 setCursor(cur, cur.pit(), row.pos());
468 void LyXText::cursorEnd(LCursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 // if not on the last row of the par, put the cursor before
472 // the final space exept if I have a spanning inset or one string
473 // is so long that we force a break.
474 pos_type end = cur.textRow().endpos();
476 // empty text, end-1 is no valid position
478 bool boundary = false;
479 if (end != cur.lastpos()) {
480 if (!cur.paragraph().isLineSeparator(end-1)
481 && !cur.paragraph().isNewline(end-1))
486 setCursor(cur, cur.pit(), end, true, boundary);
490 void LyXText::cursorTop(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 setCursor(cur, 0, 0);
497 void LyXText::cursorBottom(LCursor & cur)
499 BOOST_ASSERT(this == cur.text());
500 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
504 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
506 BOOST_ASSERT(this == cur.text());
507 // If the mask is completely neutral, tell user
508 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
509 // Could only happen with user style
510 cur.message(_("No font change defined. "
511 "Use Character under the Layout menu to define font change."));
515 // Try implicit word selection
516 // If there is a change in the language the implicit word selection
518 CursorSlice resetCursor = cur.top();
519 bool implicitSelection =
520 font.language() == ignore_language
521 && font.number() == LyXFont::IGNORE
522 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
525 setFont(cur, font, toggleall);
527 // Implicit selections are cleared afterwards
528 // and cursor is set to the original position.
529 if (implicitSelection) {
530 cur.clearSelection();
531 cur.top() = resetCursor;
537 string LyXText::getStringToIndex(LCursor const & cur)
539 BOOST_ASSERT(this == cur.text());
542 if (cur.selection()) {
543 idxstring = cur.selectionAsString(false);
545 // Try implicit word selection. If there is a change
546 // in the language the implicit word selection is
548 LCursor tmpcur = cur;
549 selectWord(tmpcur, lyx::PREVIOUS_WORD);
551 if (!tmpcur.selection())
552 cur.message(_("Nothing to index!"));
553 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
554 cur.message(_("Cannot index more than one paragraph!"));
556 idxstring = tmpcur.selectionAsString(false);
563 void LyXText::setParagraph(LCursor & cur,
564 Spacing const & spacing, LyXAlignment align,
565 string const & labelwidthstring, bool noindent)
567 BOOST_ASSERT(cur.text());
568 // make sure that the depth behind the selection are restored, too
569 pit_type undopit = undoSpan(cur.selEnd().pit());
570 recUndo(cur.selBegin().pit(), undopit - 1);
572 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
574 Paragraph & par = pars_[pit];
575 ParagraphParameters & params = par.params();
576 params.spacing(spacing);
578 // does the layout allow the new alignment?
579 LyXLayout_ptr const & layout = par.layout();
581 if (align == LYX_ALIGN_LAYOUT)
582 align = layout->align;
583 if (align & layout->alignpossible) {
584 if (align == layout->align)
585 params.align(LYX_ALIGN_LAYOUT);
589 par.setLabelWidthString(labelwidthstring);
590 params.noindent(noindent);
595 // this really should just insert the inset and not move the cursor.
596 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
598 BOOST_ASSERT(this == cur.text());
600 cur.paragraph().insertInset(cur.pos(), inset);
604 // needed to insert the selection
605 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
607 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
608 current_font, str, autoBreakRows_);
612 // turn double CR to single CR, others are converted into one
613 // blank. Then insertStringAsLines is called
614 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
616 string linestr = str;
617 bool newline_inserted = false;
619 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
620 if (linestr[i] == '\n') {
621 if (newline_inserted) {
622 // we know that \r will be ignored by
623 // insertStringAsLines. Of course, it is a dirty
624 // trick, but it works...
625 linestr[i - 1] = '\r';
629 newline_inserted = true;
631 } else if (IsPrintable(linestr[i])) {
632 newline_inserted = false;
635 insertStringAsLines(cur, linestr);
639 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
640 bool setfont, bool boundary)
643 setCursorIntern(cur, par, pos, setfont, boundary);
644 return deleteEmptyParagraphMechanism(cur, old);
648 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
650 BOOST_ASSERT(par != int(paragraphs().size()));
654 // now some strict checking
655 Paragraph & para = getPar(par);
657 // None of these should happen, but we're scaredy-cats
659 lyxerr << "dont like -1" << endl;
663 if (pos > para.size()) {
664 lyxerr << "dont like 1, pos: " << pos
665 << " size: " << para.size()
666 << " par: " << par << endl;
672 void LyXText::setCursorIntern(LCursor & cur,
673 pit_type par, pos_type pos, bool setfont, bool boundary)
675 cur.boundary(boundary);
676 setCursor(cur.top(), par, pos);
683 void LyXText::setCurrentFont(LCursor & cur)
685 BOOST_ASSERT(this == cur.text());
686 pos_type pos = cur.pos();
687 Paragraph & par = cur.paragraph();
689 if (cur.boundary() && pos > 0)
693 if (pos == cur.lastpos())
695 else // potentional bug... BUG (Lgb)
696 if (par.isSeparator(pos)) {
697 if (pos > cur.textRow().pos() &&
698 bidi.level(pos) % 2 ==
699 bidi.level(pos - 1) % 2)
701 else if (pos + 1 < cur.lastpos())
706 BufferParams const & bufparams = cur.buffer().params();
707 current_font = par.getFontSettings(bufparams, pos);
708 real_current_font = getFont(par, pos);
710 if (cur.pos() == cur.lastpos()
711 && bidi.isBoundary(cur.buffer(), par, cur.pos())
712 && !cur.boundary()) {
713 Language const * lang = par.getParLanguage(bufparams);
714 current_font.setLanguage(lang);
715 current_font.setNumber(LyXFont::OFF);
716 real_current_font.setLanguage(lang);
717 real_current_font.setNumber(LyXFont::OFF);
722 // x is an absolute screen coord
723 // returns the column near the specified x-coordinate of the row
724 // x is set to the real beginning of this column
725 pos_type LyXText::getColumnNearX(pit_type const pit,
726 Row const & row, int & x, bool & boundary) const
728 int const xo = theCoords.get(this, pit).x_;
730 RowMetrics const r = computeRowMetrics(pit, row);
731 Paragraph const & par = pars_[pit];
733 pos_type vc = row.pos();
734 pos_type end = row.endpos();
736 LyXLayout_ptr const & layout = par.layout();
738 bool left_side = false;
740 pos_type body_pos = par.beginOfBody();
743 double last_tmpx = tmpx;
746 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
749 // check for empty row
755 while (vc < end && tmpx <= x) {
756 c = bidi.vis2log(vc);
758 if (body_pos > 0 && c == body_pos - 1) {
759 tmpx += r.label_hfill +
760 font_metrics::width(layout->labelsep, getLabelFont(par));
761 if (par.isLineSeparator(body_pos - 1))
762 tmpx -= singleWidth(par, body_pos - 1);
765 if (hfillExpansion(par, row, c)) {
766 tmpx += singleWidth(par, c);
770 tmpx += r.label_hfill;
771 } else if (par.isSeparator(c)) {
772 tmpx += singleWidth(par, c);
776 tmpx += singleWidth(par, c);
781 if ((tmpx + last_tmpx) / 2 > x) {
786 BOOST_ASSERT(vc <= end); // This shouldn't happen.
789 // This (rtl_support test) is not needed, but gives
790 // some speedup if rtl_support == false
791 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
793 // If lastrow is false, we don't need to compute
795 bool const rtl = lastrow ? isRTL(par) : false;
797 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
798 (!rtl && !left_side && vc == end && x > tmpx + 5)))
800 else if (vc == row.pos()) {
801 c = bidi.vis2log(vc);
802 if (bidi.level(c) % 2 == 1)
805 c = bidi.vis2log(vc - 1);
806 bool const rtl = (bidi.level(c) % 2 == 1);
807 if (left_side == rtl) {
809 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
813 // I believe this code is not needed anymore (Jug 20050717)
815 // The following code is necessary because the cursor position past
816 // the last char in a row is logically equivalent to that before
817 // the first char in the next row. That's why insets causing row
818 // divisions -- Newline and display-style insets -- must be treated
819 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
820 // Newline inset, air gap below:
821 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
822 if (bidi.level(end -1) % 2 == 0)
823 tmpx -= singleWidth(par, end - 1);
825 tmpx += singleWidth(par, end - 1);
829 // Air gap above display inset:
830 if (row.pos() < end && c >= end && end < par.size()
831 && par.isInset(end) && par.getInset(end)->display()) {
834 // Air gap below display inset:
835 if (row.pos() < end && c >= end && par.isInset(end - 1)
836 && par.getInset(end - 1)->display()) {
842 pos_type const col = c - row.pos();
844 if (!c || end == par.size())
847 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
852 return min(col, end - 1 - row.pos());
856 // y is screen coordinate
857 pit_type LyXText::getPitNearY(int y) const
859 BOOST_ASSERT(!paragraphs().empty());
860 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
861 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
863 << BOOST_CURRENT_FUNCTION
864 << ": y: " << y << " cache size: " << cc.size()
867 // look for highest numbered paragraph with y coordinate less than given y
870 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
871 CoordCache::InnerParPosCache::const_iterator et = cc.end();
872 for (; it != et; ++it) {
874 << BOOST_CURRENT_FUNCTION
875 << " examining: pit: " << it->first
876 << " y: " << it->second.y_
879 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
886 << BOOST_CURRENT_FUNCTION
887 << ": found best y: " << yy << " for pit: " << pit
894 Row const & LyXText::getRowNearY(int y, pit_type pit) const
896 Paragraph const & par = pars_[pit];
897 int yy = theCoords.get(this, pit).y_ - par.ascent();
898 BOOST_ASSERT(!par.rows().empty());
899 RowList::const_iterator rit = par.rows().begin();
900 RowList::const_iterator const rlast = boost::prior(par.rows().end());
901 for (; rit != rlast; yy += rit->height(), ++rit)
902 if (yy + rit->height() > y)
908 // x,y are absolute screen coordinates
909 // sets cursor recursively descending into nested editable insets
910 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
912 pit_type pit = getPitNearY(y);
913 BOOST_ASSERT(pit != -1);
914 Row const & row = getRowNearY(y, pit);
917 int xx = x; // is modified by getColumnNearX
918 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
924 // try to descend into nested insets
925 InsetBase * inset = checkInsetHit(x, y);
926 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
928 // Either we deconst editXY or better we move current_font
929 // and real_current_font to LCursor
934 // This should be just before or just behind the
935 // cursor position set above.
936 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
937 || inset == pars_[pit].getInset(pos));
938 // Make sure the cursor points to the position before
940 if (inset == pars_[pit].getInset(pos - 1))
942 inset = inset->editXY(cur, x, y);
943 if (cur.top().text() == this)
949 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
953 if (cur.pos() == cur.lastpos())
955 InsetBase * inset = cur.nextInset();
956 if (!isHighlyEditableInset(inset))
958 inset->edit(cur, front);
963 bool LyXText::cursorLeft(LCursor & cur)
965 if (!cur.boundary() && cur.pos() > 0 &&
966 cur.textRow().pos() == cur.pos() &&
967 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
968 !cur.paragraph().isNewline(cur.pos()-1)) {
969 return setCursor(cur, cur.pit(), cur.pos(), true, true);
971 if (cur.pos() != 0) {
972 bool boundary = cur.boundary();
973 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
974 if (!checkAndActivateInset(cur, false)) {
975 if (false && !boundary &&
976 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
978 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
983 if (cur.pit() != 0) {
984 // Steps into the paragraph above
985 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
991 bool LyXText::cursorRight(LCursor & cur)
993 if (cur.pos() != cur.lastpos()) {
995 return setCursor(cur, cur.pit(), cur.pos(),
998 bool updateNeeded = false;
999 if (!checkAndActivateInset(cur, true)) {
1000 if (cur.textRow().endpos() == cur.pos() + 1 &&
1001 cur.textRow().endpos() != cur.lastpos() &&
1002 !cur.paragraph().isLineSeparator(cur.pos()) &&
1003 !cur.paragraph().isNewline(cur.pos())) {
1006 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1007 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1009 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1011 return updateNeeded;
1014 if (cur.pit() != cur.lastpit())
1015 return setCursor(cur, cur.pit() + 1, 0);
1020 bool LyXText::cursorUp(LCursor & cur)
1022 Paragraph const & par = cur.paragraph();
1024 int const x = cur.targetX();
1026 if (cur.pos() && cur.boundary())
1027 row = par.pos2row(cur.pos()-1);
1029 row = par.pos2row(cur.pos());
1031 if (!cur.selection()) {
1032 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1034 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1035 cur.clearSelection();
1037 // This happens when you move out of an inset.
1038 // And to give the DEPM the possibility of doing
1039 // something we must provide it with two different
1041 LCursor dummy = cur;
1045 return deleteEmptyParagraphMechanism(dummy, old);
1048 bool updateNeeded = false;
1051 updateNeeded |= setCursor(cur, cur.pit(),
1052 x2pos(cur.pit(), row - 1, x));
1053 } else if (cur.pit() > 0) {
1055 //cannot use 'par' now
1056 updateNeeded |= setCursor(cur, cur.pit(),
1057 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1062 return updateNeeded;
1066 bool LyXText::cursorDown(LCursor & cur)
1068 Paragraph const & par = cur.paragraph();
1070 int const x = cur.targetX();
1072 if (cur.pos() && cur.boundary())
1073 row = par.pos2row(cur.pos()-1);
1075 row = par.pos2row(cur.pos());
1077 if (!cur.selection()) {
1078 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1080 editXY(cur, x, y + par.rows()[row].descent() + 1);
1081 cur.clearSelection();
1083 // This happens when you move out of an inset.
1084 // And to give the DEPM the possibility of doing
1085 // something we must provide it with two different
1087 LCursor dummy = cur;
1091 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1093 // Make sure that cur gets back whatever happened to dummy(Lgb)
1100 bool updateNeeded = false;
1102 if (row + 1 < int(par.rows().size())) {
1103 updateNeeded |= setCursor(cur, cur.pit(),
1104 x2pos(cur.pit(), row + 1, x));
1105 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1107 updateNeeded |= setCursor(cur, cur.pit(),
1108 x2pos(cur.pit(), 0, x));
1113 return updateNeeded;
1117 bool LyXText::cursorUpParagraph(LCursor & cur)
1119 bool updated = false;
1121 updated = setCursor(cur, cur.pit(), 0);
1122 else if (cur.pit() != 0)
1123 updated = setCursor(cur, cur.pit() - 1, 0);
1128 bool LyXText::cursorDownParagraph(LCursor & cur)
1130 bool updated = false;
1131 if (cur.pit() != cur.lastpit())
1132 updated = setCursor(cur, cur.pit() + 1, 0);
1134 updated = setCursor(cur, cur.pit(), cur.lastpos());
1139 // fix the cursor `cur' after a characters has been deleted at `where'
1140 // position. Called by deleteEmptyParagraphMechanism
1141 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1143 // Do nothing if cursor is not in the paragraph where the
1144 // deletion occured,
1145 if (cur.pit() != where.pit())
1148 // If cursor position is after the deletion place update it
1149 if (cur.pos() > where.pos())
1152 // Check also if we don't want to set the cursor on a spot behind the
1153 // pagragraph because we erased the last character.
1154 if (cur.pos() > cur.lastpos())
1155 cur.pos() = cur.lastpos();
1159 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1161 // Would be wrong to delete anything if we have a selection.
1162 if (cur.selection())
1165 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1166 // old should point to us
1167 BOOST_ASSERT(old.text() == this);
1169 Paragraph & oldpar = old.paragraph();
1171 // We allow all kinds of "mumbo-jumbo" when freespacing.
1172 if (oldpar.isFreeSpacing())
1175 /* Ok I'll put some comments here about what is missing.
1176 There are still some small problems that can lead to
1177 double spaces stored in the document file or space at
1178 the beginning of paragraphs(). This happens if you have
1179 the cursor between to spaces and then save. Or if you
1180 cut and paste and the selection have a space at the
1181 beginning and then save right after the paste. (Lgb)
1184 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1185 // delete the LineSeparator.
1188 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1189 // delete the LineSeparator.
1192 bool const same_inset = &old.inset() == &cur.inset();
1193 bool const same_par = same_inset && old.pit() == cur.pit();
1194 bool const same_par_pos = same_par && old.pos() == cur.pos();
1196 // If the chars around the old cursor were spaces, delete one of them.
1197 if (!same_par_pos) {
1198 // Only if the cursor has really moved.
1200 && old.pos() < oldpar.size()
1201 && oldpar.isLineSeparator(old.pos())
1202 && oldpar.isLineSeparator(old.pos() - 1)
1203 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1204 // We need to set the text to Change::INSERTED to
1205 // get it erased properly
1206 oldpar.setChange(old.pos() -1, Change::INSERTED);
1207 oldpar.erase(old.pos() - 1);
1208 #ifdef WITH_WARNINGS
1209 #warning This will not work anymore when we have multiple views of the same buffer
1210 // In this case, we will have to correct also the cursors held by
1211 // other bufferviews. It will probably be easier to do that in a more
1212 // automated way in CursorSlice code. (JMarc 26/09/2001)
1214 // correct all cursor parts
1216 fixCursorAfterDelete(cur.top(), old.top());
1223 // only do our magic if we changed paragraph
1227 // don't delete anything if this is the ONLY paragraph!
1228 if (old.lastpit() == 0)
1231 // Do not delete empty paragraphs with keepempty set.
1232 if (oldpar.allowEmpty())
1235 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1237 recordUndo(old, Undo::ATOMIC,
1238 old.pit(), min(old.pit() + 1, old.lastpit()));
1239 ParagraphList & plist = old.text()->paragraphs();
1240 plist.erase(plist.begin() + old.pit());
1242 // see #warning above
1243 if (cur.depth() >= old.depth()) {
1244 CursorSlice & curslice = cur[old.depth() - 1];
1245 if (&curslice.inset() == &old.inset()
1246 && curslice.pit() > old.pit()) {
1248 // since a paragraph has been deleted, all the
1249 // insets after `old' have been copied and
1250 // their address has changed. Therefore we
1251 // need to `regenerate' cur. (JMarc)
1252 cur.updateInsets(&(cur.bottom().inset()));
1256 updateCounters(old.buffer());
1260 if (oldpar.stripLeadingSpaces())
1267 void LyXText::recUndo(pit_type first, pit_type last) const
1269 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1273 void LyXText::recUndo(pit_type par) const
1275 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1279 int defaultRowHeight()
1281 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);