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 "BufferList.h"
27 #include "BufferParams.h"
28 #include "BufferView.h"
29 #include "bufferview_funcs.h"
31 #include "CoordCache.h"
33 #include "CutAndPaste.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
44 #include "Paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "ParIterator.h"
50 #include "ServerSocket.h"
54 #include "frontends/FontMetrics.h"
56 #include "insets/InsetEnvironment.h"
58 #include "mathed/InsetMathHull.h"
60 #include "support/textutils.h"
62 #include <boost/current_function.hpp>
70 using std::ostringstream;
77 : current_font(Font::ALL_INHERIT),
78 background_color_(Color::background),
83 bool Text::isMainText(Buffer const & buffer) const
85 return &buffer.text() == this;
89 //takes screen x,y coordinates
90 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
92 pit_type pit = getPitNearY(bv, y);
93 BOOST_ASSERT(pit != -1);
95 Paragraph const & par = pars_[pit];
98 << BOOST_CURRENT_FUNCTION
103 InsetList::const_iterator iit = par.insetlist.begin();
104 InsetList::const_iterator iend = par.insetlist.end();
105 for (; iit != iend; ++iit) {
106 Inset * inset = iit->inset;
109 << BOOST_CURRENT_FUNCTION
110 << ": examining inset " << inset << endl;
112 if (bv.coordCache().getInsets().has(inset))
114 << BOOST_CURRENT_FUNCTION
115 << ": xo: " << inset->xo(bv) << "..."
116 << inset->xo(bv) + inset->width()
117 << " yo: " << inset->yo(bv) - inset->ascent()
119 << inset->yo(bv) + inset->descent()
123 << BOOST_CURRENT_FUNCTION
124 << ": inset has no cached position" << endl;
126 if (inset->covers(bv, x, y)) {
128 << BOOST_CURRENT_FUNCTION
129 << ": Hit inset: " << inset << endl;
134 << BOOST_CURRENT_FUNCTION
135 << ": No inset hit. " << endl;
141 // Gets the fully instantiated font at a given position in a paragraph
142 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
143 // The difference is that this one is used for displaying, and thus we
144 // are allowed to make cosmetic improvements. For instance make footnotes
146 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
147 pos_type const pos) const
149 BOOST_ASSERT(pos >= 0);
151 Layout_ptr const & layout = par.layout();
155 BufferParams const & params = buffer.params();
156 pos_type const body_pos = par.beginOfBody();
158 // We specialize the 95% common case:
159 if (!par.getDepth()) {
160 Font f = par.getFontSettings(params, pos);
161 if (!isMainText(buffer))
162 applyOuterFont(buffer, f);
165 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
166 lf = layout->labelfont;
167 rlf = layout->reslabelfont;
170 rlf = layout->resfont;
172 // In case the default family has been customized
173 if (lf.family() == Font::INHERIT_FAMILY)
174 rlf.setFamily(params.getFont().family());
175 return f.realize(rlf);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 Font font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
188 if (!isMainText(buffer))
189 applyOuterFont(buffer, font);
191 // Find the pit value belonging to paragraph. This will not break
192 // even if pars_ would not be a vector anymore.
193 // Performance appears acceptable.
195 pit_type pit = pars_.size();
196 for (pit_type it = 0; it < pit; ++it)
197 if (&pars_[it] == &par) {
201 // Realize against environment font information
202 // NOTE: the cast to pit_type should be removed when pit_type
203 // changes to a unsigned integer.
204 if (pit < pit_type(pars_.size()))
205 font.realize(outerFont(pit, pars_));
207 // Realize with the fonts of lesser depth.
208 font.realize(params.getFont());
213 // There are currently two font mechanisms in LyX:
214 // 1. The font attributes in a lyxtext, and
215 // 2. The inset-specific font properties, defined in an inset's
216 // metrics() and draw() methods and handed down the inset chain through
217 // the pi/mi parameters, and stored locally in a lyxtext in font_.
218 // This is where the two are integrated in the final fully realized
220 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
222 lf.reduce(buffer.params().getFont());
224 lf.setLanguage(font.language());
229 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
231 Layout_ptr const & layout = pars_[pit].layout();
233 if (!pars_[pit].getDepth()) {
234 Font lf = layout->resfont;
235 // In case the default family has been customized
236 if (layout->font.family() == Font::INHERIT_FAMILY)
237 lf.setFamily(buffer.params().getFont().family());
241 Font font = layout->font;
242 // Realize with the fonts of lesser depth.
243 //font.realize(outerFont(pit, paragraphs()));
244 font.realize(buffer.params().getFont());
250 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
252 Layout_ptr const & layout = par.layout();
254 if (!par.getDepth()) {
255 Font lf = layout->reslabelfont;
256 // In case the default family has been customized
257 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
258 lf.setFamily(buffer.params().getFont().family());
262 Font font = layout->labelfont;
263 // Realize with the fonts of lesser depth.
264 font.realize(buffer.params().getFont());
270 void Text::setCharFont(Buffer const & buffer, pit_type pit,
271 pos_type pos, Font const & fnt)
274 Layout_ptr const & layout = pars_[pit].layout();
276 // Get concrete layout font to reduce against
279 if (pos < pars_[pit].beginOfBody())
280 layoutfont = layout->labelfont;
282 layoutfont = layout->font;
284 // Realize against environment font information
285 if (pars_[pit].getDepth()) {
287 while (!layoutfont.resolved() &&
288 tp != pit_type(paragraphs().size()) &&
289 pars_[tp].getDepth()) {
290 tp = outerHook(tp, paragraphs());
291 if (tp != pit_type(paragraphs().size()))
292 layoutfont.realize(pars_[tp].layout()->font);
296 // Inside inset, apply the inset's font attributes if any
298 if (!isMainText(buffer))
299 layoutfont.realize(font_);
301 layoutfont.realize(buffer.params().getFont());
303 // Now, reduce font against full layout font
304 font.reduce(layoutfont);
306 pars_[pit].setFont(pos, font);
310 // return past-the-last paragraph influenced by a layout change on pit
311 pit_type Text::undoSpan(pit_type pit)
313 pit_type end = paragraphs().size();
314 pit_type nextpit = pit + 1;
317 //because of parindents
318 if (!pars_[pit].getDepth())
319 return boost::next(nextpit);
320 //because of depth constrains
321 for (; nextpit != end; ++pit, ++nextpit) {
322 if (!pars_[pit].getDepth())
329 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
330 string const & layout)
332 BOOST_ASSERT(start != end);
334 BufferParams const & bufparams = buffer.params();
335 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
337 for (pit_type pit = start; pit != end; ++pit) {
338 Paragraph & par = pars_[pit];
339 par.applyLayout(lyxlayout);
340 if (lyxlayout->margintype == MARGIN_MANUAL)
341 par.setLabelWidthString(par.translateIfPossible(
342 lyxlayout->labelstring(), buffer.params()));
347 // set layout over selection and make a total rebreak of those paragraphs
348 void Text::setLayout(Cursor & cur, string const & layout)
350 BOOST_ASSERT(this == cur.text());
351 // special handling of new environment insets
352 BufferView & bv = cur.bv();
353 BufferParams const & params = bv.buffer()->params();
354 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
355 if (lyxlayout->is_environment) {
356 // move everything in a new environment inset
357 LYXERR(Debug::DEBUG) << "setting layout " << layout << endl;
358 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
359 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
360 lyx::dispatch(FuncRequest(LFUN_CUT));
361 Inset * inset = new InsetEnvironment(params, layout);
362 insertInset(cur, inset);
363 //inset->edit(cur, true);
364 //lyx::dispatch(FuncRequest(LFUN_PASTE));
368 pit_type start = cur.selBegin().pit();
369 pit_type end = cur.selEnd().pit() + 1;
370 pit_type undopit = undoSpan(end - 1);
371 recUndo(cur, start, undopit - 1);
372 setLayout(cur.buffer(), start, end, layout);
373 updateLabels(cur.buffer());
377 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
378 Paragraph const & par, int max_depth)
380 if (par.layout()->labeltype == LABEL_BIBLIO)
382 int const depth = par.params().depth();
383 if (type == Text::INC_DEPTH && depth < max_depth)
385 if (type == Text::DEC_DEPTH && depth > 0)
391 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
393 BOOST_ASSERT(this == cur.text());
394 // this happens when selecting several cells in tabular (bug 2630)
395 if (cur.selBegin().idx() != cur.selEnd().idx())
398 pit_type const beg = cur.selBegin().pit();
399 pit_type const end = cur.selEnd().pit() + 1;
400 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
402 for (pit_type pit = beg; pit != end; ++pit) {
403 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
405 max_depth = pars_[pit].getMaxDepthAfter();
411 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
413 BOOST_ASSERT(this == cur.text());
414 pit_type const beg = cur.selBegin().pit();
415 pit_type const end = cur.selEnd().pit() + 1;
416 recordUndoSelection(cur);
417 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
419 for (pit_type pit = beg; pit != end; ++pit) {
420 Paragraph & par = pars_[pit];
421 if (lyx::changeDepthAllowed(type, par, max_depth)) {
422 int const depth = par.params().depth();
423 if (type == INC_DEPTH)
424 par.params().depth(depth + 1);
426 par.params().depth(depth - 1);
428 max_depth = par.getMaxDepthAfter();
430 // this handles the counter labels, and also fixes up
431 // depth values for follow-on (child) paragraphs
432 updateLabels(cur.buffer());
436 // set font over selection
437 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
439 BOOST_ASSERT(this == cur.text());
440 // if there is no selection just set the current_font
441 if (!cur.selection()) {
442 // Determine basis font
444 pit_type pit = cur.pit();
445 if (cur.pos() < pars_[pit].beginOfBody())
446 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
448 layoutfont = getLayoutFont(cur.buffer(), pit);
450 // Update current font
451 real_current_font.update(font,
452 cur.buffer().params().language,
455 // Reduce to implicit settings
456 current_font = real_current_font;
457 current_font.reduce(layoutfont);
458 // And resolve it completely
459 real_current_font.realize(layoutfont);
464 // Ok, we have a selection.
465 recordUndoSelection(cur);
467 DocIterator dit = cur.selectionBegin();
468 DocIterator ditend = cur.selectionEnd();
470 BufferParams const & params = cur.buffer().params();
472 // Don't use forwardChar here as ditend might have
473 // pos() == lastpos() and forwardChar would miss it.
474 // Can't use forwardPos either as this descends into
476 for (; dit != ditend; dit.forwardPosNoDescend()) {
477 if (dit.pos() != dit.lastpos()) {
478 Font f = getFont(cur.buffer(), dit.paragraph(), dit.pos());
479 f.update(font, params.language, toggleall);
480 setCharFont(cur.buffer(), dit.pit(), dit.pos(), f);
486 // the cursor set functions have a special mechanism. When they
487 // realize you left an empty paragraph, they will delete it.
489 bool Text::cursorHome(Cursor & cur)
491 BOOST_ASSERT(this == cur.text());
492 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
493 Row const & row = pm.getRow(cur.pos(),cur.boundary());
494 return setCursor(cur, cur.pit(), row.pos());
498 bool Text::cursorEnd(Cursor & cur)
500 BOOST_ASSERT(this == cur.text());
501 // if not on the last row of the par, put the cursor before
502 // the final space exept if I have a spanning inset or one string
503 // is so long that we force a break.
504 pos_type end = cur.textRow().endpos();
506 // empty text, end-1 is no valid position
508 bool boundary = false;
509 if (end != cur.lastpos()) {
510 if (!cur.paragraph().isLineSeparator(end-1)
511 && !cur.paragraph().isNewline(end-1))
516 return setCursor(cur, cur.pit(), end, true, boundary);
520 bool Text::cursorTop(Cursor & cur)
522 BOOST_ASSERT(this == cur.text());
523 return setCursor(cur, 0, 0);
527 bool Text::cursorBottom(Cursor & cur)
529 BOOST_ASSERT(this == cur.text());
530 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
534 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
536 BOOST_ASSERT(this == cur.text());
537 // If the mask is completely neutral, tell user
538 if (font == Font(Font::ALL_IGNORE)) {
539 // Could only happen with user style
540 cur.message(_("No font change defined. "
541 "Use Character under the Layout menu to define font change."));
545 // Try implicit word selection
546 // If there is a change in the language the implicit word selection
548 CursorSlice resetCursor = cur.top();
549 bool implicitSelection =
550 font.language() == ignore_language
551 && font.number() == Font::IGNORE
552 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
555 setFont(cur, font, toggleall);
557 // Implicit selections are cleared afterwards
558 // and cursor is set to the original position.
559 if (implicitSelection) {
560 cur.clearSelection();
561 cur.top() = resetCursor;
567 docstring Text::getStringToIndex(Cursor const & cur)
569 BOOST_ASSERT(this == cur.text());
573 idxstring = cur.selectionAsString(false);
575 // Try implicit word selection. If there is a change
576 // in the language the implicit word selection is
579 selectWord(tmpcur, PREVIOUS_WORD);
581 if (!tmpcur.selection())
582 cur.message(_("Nothing to index!"));
583 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
584 cur.message(_("Cannot index more than one paragraph!"));
586 idxstring = tmpcur.selectionAsString(false);
593 void Text::setParagraph(Cursor & cur,
594 Spacing const & spacing, LyXAlignment align,
595 docstring const & labelwidthstring, bool noindent)
597 BOOST_ASSERT(cur.text());
598 // make sure that the depth behind the selection are restored, too
599 pit_type undopit = undoSpan(cur.selEnd().pit());
600 recUndo(cur, cur.selBegin().pit(), undopit - 1);
602 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
604 Paragraph & par = pars_[pit];
605 ParagraphParameters & params = par.params();
606 params.spacing(spacing);
608 // does the layout allow the new alignment?
609 Layout_ptr const & layout = par.layout();
611 if (align == LYX_ALIGN_LAYOUT)
612 align = layout->align;
613 if (align & layout->alignpossible) {
614 if (align == layout->align)
615 params.align(LYX_ALIGN_LAYOUT);
619 par.setLabelWidthString(labelwidthstring);
620 params.noindent(noindent);
625 // this really should just insert the inset and not move the cursor.
626 void Text::insertInset(Cursor & cur, Inset * inset)
628 BOOST_ASSERT(this == cur.text());
630 cur.paragraph().insertInset(cur.pos(), inset,
631 Change(cur.buffer().params().trackChanges ?
632 Change::INSERTED : Change::UNCHANGED));
636 // needed to insert the selection
637 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
639 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
640 current_font, str, autoBreakRows_);
644 // turn double CR to single CR, others are converted into one
645 // blank. Then insertStringAsLines is called
646 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
648 docstring linestr = str;
649 bool newline_inserted = false;
651 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
652 if (linestr[i] == '\n') {
653 if (newline_inserted) {
654 // we know that \r will be ignored by
655 // insertStringAsLines. Of course, it is a dirty
656 // trick, but it works...
657 linestr[i - 1] = '\r';
661 newline_inserted = true;
663 } else if (isPrintable(linestr[i])) {
664 newline_inserted = false;
667 insertStringAsLines(cur, linestr);
671 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
672 bool setfont, bool boundary)
675 setCursorIntern(cur, par, pos, setfont, boundary);
676 return cur.bv().checkDepm(cur, old);
680 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
682 BOOST_ASSERT(par != int(paragraphs().size()));
686 // now some strict checking
687 Paragraph & para = getPar(par);
689 // None of these should happen, but we're scaredy-cats
691 lyxerr << "dont like -1" << endl;
695 if (pos > para.size()) {
696 lyxerr << "dont like 1, pos: " << pos
697 << " size: " << para.size()
698 << " par: " << par << endl;
704 void Text::setCursorIntern(Cursor & cur,
705 pit_type par, pos_type pos, bool setfont, bool boundary)
707 BOOST_ASSERT(this == cur.text());
708 cur.boundary(boundary);
709 setCursor(cur.top(), par, pos);
715 void Text::setCurrentFont(Cursor & cur)
717 BOOST_ASSERT(this == cur.text());
718 pos_type pos = cur.pos();
719 Paragraph & par = cur.paragraph();
721 if (cur.boundary() && pos > 0)
725 if (pos == cur.lastpos())
727 else // potentional bug... BUG (Lgb)
728 if (par.isSeparator(pos)) {
729 if (pos > cur.textRow().pos() &&
730 bidi.level(pos) % 2 ==
731 bidi.level(pos - 1) % 2)
733 else if (pos + 1 < cur.lastpos())
738 BufferParams const & bufparams = cur.buffer().params();
739 current_font = par.getFontSettings(bufparams, pos);
740 real_current_font = getFont(cur.buffer(), par, pos);
742 if (cur.pos() == cur.lastpos()
743 && bidi.isBoundary(cur.buffer(), par, cur.pos())
744 && !cur.boundary()) {
745 Language const * lang = par.getParLanguage(bufparams);
746 current_font.setLanguage(lang);
747 current_font.setNumber(Font::OFF);
748 real_current_font.setLanguage(lang);
749 real_current_font.setNumber(Font::OFF);
753 // y is screen coordinate
754 pit_type Text::getPitNearY(BufferView & bv, int y) const
756 BOOST_ASSERT(!paragraphs().empty());
757 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
758 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
760 << BOOST_CURRENT_FUNCTION
761 << ": y: " << y << " cache size: " << cc.size()
764 // look for highest numbered paragraph with y coordinate less than given y
767 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
768 CoordCache::InnerParPosCache::const_iterator et = cc.end();
769 CoordCache::InnerParPosCache::const_iterator last = et; last--;
771 TextMetrics & tm = bv.textMetrics(this);
772 ParagraphMetrics const & pm = tm.parMetrics(it->first);
774 // If we are off-screen (before the visible part)
776 // and even before the first paragraph in the cache.
777 && y < it->second.y_ - int(pm.ascent())) {
778 // and we are not at the first paragraph in the inset.
781 // then this is the paragraph we are looking for.
783 // rebreak it and update the CoordCache.
784 tm.redoParagraph(pit);
785 bv.coordCache().parPos()[this][pit] =
786 Point(0, it->second.y_ - pm.descent());
790 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
792 // If we are off-screen (after the visible part)
793 if (y > bv.workHeight()
794 // and even after the first paragraph in the cache.
795 && y >= last->second.y_ + int(pm_last.descent())) {
796 pit = last->first + 1;
797 // and we are not at the last paragraph in the inset.
798 if (pit == int(pars_.size()))
800 // then this is the paragraph we are looking for.
801 // rebreak it and update the CoordCache.
802 tm.redoParagraph(pit);
803 bv.coordCache().parPos()[this][pit] =
804 Point(0, last->second.y_ + pm_last.ascent());
808 for (; it != et; ++it) {
810 << BOOST_CURRENT_FUNCTION
811 << " examining: pit: " << it->first
812 << " y: " << it->second.y_
815 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
817 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
824 << BOOST_CURRENT_FUNCTION
825 << ": found best y: " << yy << " for pit: " << pit
832 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
834 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
836 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
837 BOOST_ASSERT(!pm.rows().empty());
838 RowList::const_iterator rit = pm.rows().begin();
839 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
840 for (; rit != rlast; yy += rit->height(), ++rit)
841 if (yy + rit->height() > y)
847 // x,y are absolute screen coordinates
848 // sets cursor recursively descending into nested editable insets
849 Inset * Text::editXY(Cursor & cur, int x, int y)
851 if (lyxerr.debugging(Debug::WORKAREA)) {
852 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
853 cur.bv().coordCache().dump();
855 pit_type pit = getPitNearY(cur.bv(), y);
856 BOOST_ASSERT(pit != -1);
858 Row const & row = getRowNearY(cur.bv(), y, pit);
861 TextMetrics const & tm = cur.bv().textMetrics(this);
862 int xx = x; // is modified by getColumnNearX
863 pos_type const pos = row.pos()
864 + tm.getColumnNearX(pit, row, xx, bound);
870 // try to descend into nested insets
871 Inset * inset = checkInsetHit(cur.bv(), x, y);
872 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
874 // Either we deconst editXY or better we move current_font
875 // and real_current_font to Cursor
880 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
881 //Inset * insetBehind = pars_[pit].getInset(pos);
883 // This should be just before or just behind the
884 // cursor position set above.
885 BOOST_ASSERT((pos != 0 && inset == insetBefore)
886 || inset == pars_[pit].getInset(pos));
888 // Make sure the cursor points to the position before
890 if (inset == insetBefore)
893 // Try to descend recursively inside the inset.
894 inset = inset->editXY(cur, x, y);
896 if (cur.top().text() == this)
902 bool Text::checkAndActivateInset(Cursor & cur, bool front)
906 if (cur.pos() == cur.lastpos())
908 Inset * inset = cur.nextInset();
909 if (!isHighlyEditableInset(inset))
911 inset->edit(cur, front);
916 bool Text::cursorLeft(Cursor & cur)
918 // Tell BufferView to test for FitCursor in any case!
919 cur.updateFlags(Update::FitCursor);
921 if (!cur.boundary() && cur.pos() > 0 &&
922 cur.textRow().pos() == cur.pos() &&
923 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
924 !cur.paragraph().isNewline(cur.pos()-1)) {
925 return setCursor(cur, cur.pit(), cur.pos(), true, true);
927 if (cur.pos() != 0) {
928 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
929 if (!checkAndActivateInset(cur, false)) {
930 /** FIXME: What's this cause purpose???
931 bool boundary = cur.boundary();
932 if (false && !boundary &&
933 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
935 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
941 if (cur.pit() != 0) {
942 // Steps into the paragraph above
943 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
949 bool Text::cursorRight(Cursor & cur)
951 // Tell BufferView to test for FitCursor in any case!
952 cur.updateFlags(Update::FitCursor);
954 if (cur.pos() != cur.lastpos()) {
956 return setCursor(cur, cur.pit(), cur.pos(),
959 bool updateNeeded = false;
960 if (!checkAndActivateInset(cur, true)) {
961 if (cur.textRow().endpos() == cur.pos() + 1 &&
962 cur.textRow().endpos() != cur.lastpos() &&
963 !cur.paragraph().isLineSeparator(cur.pos()) &&
964 !cur.paragraph().isNewline(cur.pos())) {
967 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
968 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
970 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
975 if (cur.pit() != cur.lastpit())
976 return setCursor(cur, cur.pit() + 1, 0);
981 bool Text::cursorUp(Cursor & cur)
983 // Tell BufferView to test for FitCursor in any case!
984 cur.updateFlags(Update::FitCursor);
986 TextMetrics const & tm = cur.bv().textMetrics(this);
987 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
990 if (cur.pos() && cur.boundary())
991 row = pm.pos2row(cur.pos()-1);
993 row = pm.pos2row(cur.pos());
995 // remember current position only if we are not at the end of a row.
996 if (cur.pos() != pm.rows()[row].endpos())
998 int const x = cur.targetX();
1000 if (!cur.selection()) {
1001 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1003 // Go to middle of previous row. 16 found to work OK;
1004 // 12 = top/bottom margin of display math
1005 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1006 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1007 cur.clearSelection();
1009 // This happens when you move out of an inset.
1010 // And to give the DEPM the possibility of doing
1011 // something we must provide it with two different
1017 cur.bv().checkDepm(dummy, old);
1021 bool updateNeeded = false;
1024 updateNeeded |= setCursor(cur, cur.pit(),
1025 tm.x2pos(cur.pit(), row - 1, x));
1026 } else if (cur.pit() > 0) {
1028 //cannot use 'par' now
1029 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1030 updateNeeded |= setCursor(cur, cur.pit(),
1031 tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1036 return updateNeeded;
1040 bool Text::cursorDown(Cursor & cur)
1042 // Tell BufferView to test for FitCursor in any case!
1043 cur.updateFlags(Update::FitCursor);
1045 TextMetrics const & tm = cur.bv().textMetrics(this);
1046 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1049 if (cur.pos() && cur.boundary())
1050 row = pm.pos2row(cur.pos()-1);
1052 row = pm.pos2row(cur.pos());
1054 // remember current position only if we are not at the end of a row.
1055 if (cur.pos() != pm.rows()[row].endpos())
1057 int const x = cur.targetX();
1059 if (!cur.selection()) {
1060 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1062 // To middle of next row
1063 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1064 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1065 cur.clearSelection();
1067 // This happens when you move out of an inset.
1068 // And to give the DEPM the possibility of doing
1069 // something we must provide it with two different
1075 bool const changed = cur.bv().checkDepm(dummy, old);
1077 // Make sure that cur gets back whatever happened to dummy(Lgb)
1084 bool updateNeeded = false;
1086 if (row + 1 < int(pm.rows().size())) {
1087 updateNeeded |= setCursor(cur, cur.pit(),
1088 tm.x2pos(cur.pit(), row + 1, x));
1089 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1091 updateNeeded |= setCursor(cur, cur.pit(),
1092 tm.x2pos(cur.pit(), 0, x));
1097 return updateNeeded;
1101 bool Text::cursorUpParagraph(Cursor & cur)
1103 bool updated = false;
1105 updated = setCursor(cur, cur.pit(), 0);
1106 else if (cur.pit() != 0)
1107 updated = setCursor(cur, cur.pit() - 1, 0);
1112 bool Text::cursorDownParagraph(Cursor & cur)
1114 bool updated = false;
1115 if (cur.pit() != cur.lastpit())
1116 updated = setCursor(cur, cur.pit() + 1, 0);
1118 updated = setCursor(cur, cur.pit(), cur.lastpos());
1123 // fix the cursor `cur' after a characters has been deleted at `where'
1124 // position. Called by deleteEmptyParagraphMechanism
1125 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1127 // Do nothing if cursor is not in the paragraph where the
1128 // deletion occured,
1129 if (cur.pit() != where.pit())
1132 // If cursor position is after the deletion place update it
1133 if (cur.pos() > where.pos())
1136 // Check also if we don't want to set the cursor on a spot behind the
1137 // pagragraph because we erased the last character.
1138 if (cur.pos() > cur.lastpos())
1139 cur.pos() = cur.lastpos();
1143 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1144 Cursor & old, bool & need_anchor_change)
1146 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1148 Paragraph & oldpar = old.paragraph();
1150 // We allow all kinds of "mumbo-jumbo" when freespacing.
1151 if (oldpar.isFreeSpacing())
1154 /* Ok I'll put some comments here about what is missing.
1155 There are still some small problems that can lead to
1156 double spaces stored in the document file or space at
1157 the beginning of paragraphs(). This happens if you have
1158 the cursor between to spaces and then save. Or if you
1159 cut and paste and the selection have a space at the
1160 beginning and then save right after the paste. (Lgb)
1163 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1164 // delete the LineSeparator.
1167 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1168 // delete the LineSeparator.
1171 bool const same_inset = &old.inset() == &cur.inset();
1172 bool const same_par = same_inset && old.pit() == cur.pit();
1173 bool const same_par_pos = same_par && old.pos() == cur.pos();
1175 // If the chars around the old cursor were spaces, delete one of them.
1176 if (!same_par_pos) {
1177 // Only if the cursor has really moved.
1179 && old.pos() < oldpar.size()
1180 && oldpar.isLineSeparator(old.pos())
1181 && oldpar.isLineSeparator(old.pos() - 1)
1182 && !oldpar.isDeleted(old.pos() - 1)) {
1183 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1184 #ifdef WITH_WARNINGS
1185 #warning This will not work anymore when we have multiple views of the same buffer
1186 // In this case, we will have to correct also the cursors held by
1187 // other bufferviews. It will probably be easier to do that in a more
1188 // automated way in CursorSlice code. (JMarc 26/09/2001)
1190 // correct all cursor parts
1192 fixCursorAfterDelete(cur.top(), old.top());
1193 need_anchor_change = true;
1199 // only do our magic if we changed paragraph
1203 // don't delete anything if this is the ONLY paragraph!
1204 if (old.lastpit() == 0)
1207 // Do not delete empty paragraphs with keepempty set.
1208 if (oldpar.allowEmpty())
1211 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1213 recordUndo(old, Undo::ATOMIC,
1214 max(old.pit() - 1, pit_type(0)),
1215 min(old.pit() + 1, old.lastpit()));
1216 ParagraphList & plist = old.text()->paragraphs();
1217 plist.erase(boost::next(plist.begin(), old.pit()));
1219 // see #warning above
1220 if (cur.depth() >= old.depth()) {
1221 CursorSlice & curslice = cur[old.depth() - 1];
1222 if (&curslice.inset() == &old.inset()
1223 && curslice.pit() > old.pit()) {
1225 // since a paragraph has been deleted, all the
1226 // insets after `old' have been copied and
1227 // their address has changed. Therefore we
1228 // need to `regenerate' cur. (JMarc)
1229 cur.updateInsets(&(cur.bottom().inset()));
1230 need_anchor_change = true;
1236 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1237 need_anchor_change = true;
1238 // We return true here because the Paragraph contents changed and
1239 // we need a redraw before further action is processed.
1247 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1249 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1251 for (pit_type pit = first; pit <= last; ++pit) {
1252 Paragraph & par = pars_[pit];
1254 // We allow all kinds of "mumbo-jumbo" when freespacing.
1255 if (par.isFreeSpacing())
1258 for (pos_type pos = 1; pos < par.size(); ++pos) {
1259 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1260 && !par.isDeleted(pos - 1)) {
1261 if (par.eraseChar(pos - 1, trackChanges)) {
1267 // don't delete anything if this is the only remaining paragraph within the given range
1268 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1272 // don't delete empty paragraphs with keepempty set
1273 if (par.allowEmpty())
1276 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1277 pars_.erase(boost::next(pars_.begin(), pit));
1283 par.stripLeadingSpaces(trackChanges);
1288 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1290 recordUndo(cur, Undo::ATOMIC, first, last);
1294 void Text::recUndo(Cursor & cur, pit_type par) const
1296 recordUndo(cur, Undo::ATOMIC, par, par);