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 // Set the current_font
441 // Determine basis font
443 pit_type pit = cur.pit();
444 if (cur.pos() < pars_[pit].beginOfBody())
445 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
447 layoutfont = getLayoutFont(cur.buffer(), pit);
449 // Update current font
450 real_current_font.update(font,
451 cur.buffer().params().language,
454 // Reduce to implicit settings
455 current_font = real_current_font;
456 current_font.reduce(layoutfont);
457 // And resolve it completely
458 real_current_font.realize(layoutfont);
460 // if there is no selection that's all we need to do
461 if (!cur.selection())
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."));
544 // Try implicit word selection
545 // If there is a change in the language the implicit word selection
547 CursorSlice resetCursor = cur.top();
548 bool implicitSelection =
549 font.language() == ignore_language
550 && font.number() == Font::IGNORE
551 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
554 setFont(cur, font, toggleall);
556 // Implicit selections are cleared afterwards
557 // and cursor is set to the original position.
558 if (implicitSelection) {
559 cur.clearSelection();
560 cur.top() = resetCursor;
566 docstring Text::getStringToIndex(Cursor const & cur)
568 BOOST_ASSERT(this == cur.text());
572 idxstring = cur.selectionAsString(false);
574 // Try implicit word selection. If there is a change
575 // in the language the implicit word selection is
578 selectWord(tmpcur, PREVIOUS_WORD);
580 if (!tmpcur.selection())
581 cur.message(_("Nothing to index!"));
582 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
583 cur.message(_("Cannot index more than one paragraph!"));
585 idxstring = tmpcur.selectionAsString(false);
592 void Text::setParagraph(Cursor & cur,
593 Spacing const & spacing, LyXAlignment align,
594 docstring const & labelwidthstring, bool noindent)
596 BOOST_ASSERT(cur.text());
597 // make sure that the depth behind the selection are restored, too
598 pit_type undopit = undoSpan(cur.selEnd().pit());
599 recUndo(cur, cur.selBegin().pit(), undopit - 1);
601 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
603 Paragraph & par = pars_[pit];
604 ParagraphParameters & params = par.params();
605 params.spacing(spacing);
607 // does the layout allow the new alignment?
608 Layout_ptr const & layout = par.layout();
610 if (align == LYX_ALIGN_LAYOUT)
611 align = layout->align;
612 if (align & layout->alignpossible) {
613 if (align == layout->align)
614 params.align(LYX_ALIGN_LAYOUT);
618 par.setLabelWidthString(labelwidthstring);
619 params.noindent(noindent);
624 // this really should just insert the inset and not move the cursor.
625 void Text::insertInset(Cursor & cur, Inset * inset)
627 BOOST_ASSERT(this == cur.text());
629 cur.paragraph().insertInset(cur.pos(), inset,
630 Change(cur.buffer().params().trackChanges ?
631 Change::INSERTED : Change::UNCHANGED));
635 // needed to insert the selection
636 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
638 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
639 current_font, str, autoBreakRows_);
643 // turn double CR to single CR, others are converted into one
644 // blank. Then insertStringAsLines is called
645 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
647 docstring linestr = str;
648 bool newline_inserted = false;
650 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
651 if (linestr[i] == '\n') {
652 if (newline_inserted) {
653 // we know that \r will be ignored by
654 // insertStringAsLines. Of course, it is a dirty
655 // trick, but it works...
656 linestr[i - 1] = '\r';
660 newline_inserted = true;
662 } else if (isPrintable(linestr[i])) {
663 newline_inserted = false;
666 insertStringAsLines(cur, linestr);
670 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
671 bool setfont, bool boundary)
674 setCursorIntern(cur, par, pos, setfont, boundary);
675 return cur.bv().checkDepm(cur, old);
679 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
681 BOOST_ASSERT(par != int(paragraphs().size()));
685 // now some strict checking
686 Paragraph & para = getPar(par);
688 // None of these should happen, but we're scaredy-cats
690 lyxerr << "dont like -1" << endl;
694 if (pos > para.size()) {
695 lyxerr << "dont like 1, pos: " << pos
696 << " size: " << para.size()
697 << " par: " << par << endl;
703 void Text::setCursorIntern(Cursor & cur,
704 pit_type par, pos_type pos, bool setfont, bool boundary)
706 BOOST_ASSERT(this == cur.text());
707 cur.boundary(boundary);
708 setCursor(cur.top(), par, pos);
714 void Text::setCurrentFont(Cursor & cur)
716 BOOST_ASSERT(this == cur.text());
717 pos_type pos = cur.pos();
718 Paragraph & par = cur.paragraph();
720 if (cur.boundary() && pos > 0 && pos < cur.lastpos()) {
722 // We may have just moved to the previous row ---
723 // we're going to be needing its bidi tables!
724 bidi.computeTables(par, cur.buffer(), cur.textRow());
728 if (pos == cur.lastpos())
730 else // potentional bug... BUG (Lgb)
731 if (par.isSeparator(pos)) {
732 if (pos > cur.textRow().pos() &&
733 bidi.level(pos) % 2 ==
734 bidi.level(pos - 1) % 2)
736 else if (pos + 1 < cur.lastpos())
741 BufferParams const & bufparams = cur.buffer().params();
742 current_font = par.getFontSettings(bufparams, pos);
743 real_current_font = getFont(cur.buffer(), par, pos);
745 if (cur.pos() == cur.lastpos()
746 && bidi.isBoundary(cur.buffer(), par, cur.pos())
747 && !cur.boundary()) {
748 Language const * lang = par.getParLanguage(bufparams);
749 current_font.setLanguage(lang);
750 current_font.setNumber(Font::OFF);
751 real_current_font.setLanguage(lang);
752 real_current_font.setNumber(Font::OFF);
756 // y is screen coordinate
757 pit_type Text::getPitNearY(BufferView & bv, int y) const
759 BOOST_ASSERT(!paragraphs().empty());
760 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
761 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
763 << BOOST_CURRENT_FUNCTION
764 << ": y: " << y << " cache size: " << cc.size()
767 // look for highest numbered paragraph with y coordinate less than given y
770 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
771 CoordCache::InnerParPosCache::const_iterator et = cc.end();
772 CoordCache::InnerParPosCache::const_iterator last = et; last--;
774 TextMetrics & tm = bv.textMetrics(this);
775 ParagraphMetrics const & pm = tm.parMetrics(it->first);
777 // If we are off-screen (before the visible part)
779 // and even before the first paragraph in the cache.
780 && y < it->second.y_ - int(pm.ascent())) {
781 // and we are not at the first paragraph in the inset.
784 // then this is the paragraph we are looking for.
786 // rebreak it and update the CoordCache.
787 tm.redoParagraph(pit);
788 bv.coordCache().parPos()[this][pit] =
789 Point(0, it->second.y_ - pm.descent());
793 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
795 // If we are off-screen (after the visible part)
796 if (y > bv.workHeight()
797 // and even after the first paragraph in the cache.
798 && y >= last->second.y_ + int(pm_last.descent())) {
799 pit = last->first + 1;
800 // and we are not at the last paragraph in the inset.
801 if (pit == int(pars_.size()))
803 // then this is the paragraph we are looking for.
804 // rebreak it and update the CoordCache.
805 tm.redoParagraph(pit);
806 bv.coordCache().parPos()[this][pit] =
807 Point(0, last->second.y_ + pm_last.ascent());
811 for (; it != et; ++it) {
813 << BOOST_CURRENT_FUNCTION
814 << " examining: pit: " << it->first
815 << " y: " << it->second.y_
818 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
820 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
827 << BOOST_CURRENT_FUNCTION
828 << ": found best y: " << yy << " for pit: " << pit
835 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
837 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
839 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
840 BOOST_ASSERT(!pm.rows().empty());
841 RowList::const_iterator rit = pm.rows().begin();
842 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
843 for (; rit != rlast; yy += rit->height(), ++rit)
844 if (yy + rit->height() > y)
850 // x,y are absolute screen coordinates
851 // sets cursor recursively descending into nested editable insets
852 Inset * Text::editXY(Cursor & cur, int x, int y)
854 if (lyxerr.debugging(Debug::WORKAREA)) {
855 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
856 cur.bv().coordCache().dump();
858 pit_type pit = getPitNearY(cur.bv(), y);
859 BOOST_ASSERT(pit != -1);
861 Row const & row = getRowNearY(cur.bv(), y, pit);
864 TextMetrics const & tm = cur.bv().textMetrics(this);
865 int xx = x; // is modified by getColumnNearX
866 pos_type const pos = row.pos()
867 + tm.getColumnNearX(pit, row, xx, bound);
873 // try to descend into nested insets
874 Inset * inset = checkInsetHit(cur.bv(), x, y);
875 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
877 // Either we deconst editXY or better we move current_font
878 // and real_current_font to Cursor
883 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
884 //Inset * insetBehind = pars_[pit].getInset(pos);
886 // This should be just before or just behind the
887 // cursor position set above.
888 BOOST_ASSERT((pos != 0 && inset == insetBefore)
889 || inset == pars_[pit].getInset(pos));
891 // Make sure the cursor points to the position before
893 if (inset == insetBefore)
896 // Try to descend recursively inside the inset.
897 inset = inset->editXY(cur, x, y);
899 if (cur.top().text() == this)
905 bool Text::checkAndActivateInset(Cursor & cur, bool front)
909 if (cur.pos() == cur.lastpos())
911 Inset * inset = front ? cur.nextInset() : cur.prevInset();
912 if (!isHighlyEditableInset(inset))
915 * Apparently, when entering an inset we are expected to be positioned
916 * *before* it in the containing paragraph, regardless of the direction
917 * from which we are entering. Otherwise, cursor placement goes awry,
918 * and when we exit from the beginning, we'll be placed *after* the
923 inset->edit(cur, front);
928 bool Text::cursorLeft(Cursor & cur)
930 // Tell BufferView to test for FitCursor in any case!
931 cur.updateFlags(Update::FitCursor);
935 return setCursor(cur, cur.pit(), cur.pos(), true, false);
937 bool updateNeeded = false;
938 // If checkAndActivateInset returns true, that means that
939 // the cursor was placed inside it, so we're done
940 if (!checkAndActivateInset(cur, false)) {
941 if (!cur.boundary() &&
942 cur.textRow().pos() == cur.pos()
943 // FIXME: the following two conditions are copied
944 // from cursorRight; however, isLineSeparator()
945 // is definitely wrong here, isNewline I'm not sure
946 // about. I'm leaving them as comments for now,
947 // until we understand why they should or shouldn't
950 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
951 !cur.paragraph().isNewline(cur.pos() - 1)*/) {
952 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(),
955 updateNeeded |= setCursor(cur, cur.pit(),cur.pos() - 1,
962 // Steps into the paragraph above
963 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
969 bool Text::cursorRight(Cursor & cur)
971 // Tell BufferView to test for FitCursor in any case!
972 cur.updateFlags(Update::FitCursor);
974 if (cur.pos() != cur.lastpos()) {
976 return setCursor(cur, cur.pit(), cur.pos(),
979 bool updateNeeded = false;
980 // If checkAndActivateInset returns true, that means that
981 // the cursor was placed inside it, so we're done
982 if (!checkAndActivateInset(cur, true)) {
983 if (cur.textRow().endpos() == cur.pos() + 1 &&
984 cur.textRow().endpos() != cur.lastpos() &&
985 !cur.paragraph().isLineSeparator(cur.pos()) &&
986 !cur.paragraph().isNewline(cur.pos())) {
989 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
994 if (cur.pit() != cur.lastpit())
995 return setCursor(cur, cur.pit() + 1, 0);
1000 bool Text::cursorUp(Cursor & cur)
1002 // Tell BufferView to test for FitCursor in any case!
1003 cur.updateFlags(Update::FitCursor);
1005 TextMetrics const & tm = cur.bv().textMetrics(this);
1006 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1009 if (cur.pos() && cur.boundary())
1010 row = pm.pos2row(cur.pos()-1);
1012 row = pm.pos2row(cur.pos());
1014 int x = cur.targetX();
1016 // We want to keep the x-target on subsequent up movements
1017 // that cross beyond the end of short lines. Thus a special
1018 // handling when the cursor is at the end of line: Use the new
1019 // x-target only if the old one was before the end of line.
1020 if (cur.pos() != pm.rows()[row].endpos()
1021 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1022 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1027 if (!cur.selection()) {
1028 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1030 // Go to middle of previous row. 16 found to work OK;
1031 // 12 = top/bottom margin of display math
1032 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1033 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1034 cur.clearSelection();
1036 // This happens when you move out of an inset.
1037 // And to give the DEPM the possibility of doing
1038 // something we must provide it with two different
1044 cur.bv().checkDepm(dummy, old);
1048 bool updateNeeded = false;
1051 updateNeeded |= setCursor(cur, cur.pit(),
1052 tm.x2pos(cur.pit(), row - 1, x));
1053 } else if (cur.pit() > 0) {
1055 //cannot use 'par' now
1056 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1057 updateNeeded |= setCursor(cur, cur.pit(),
1058 tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1063 return updateNeeded;
1067 bool Text::cursorDown(Cursor & cur)
1069 // Tell BufferView to test for FitCursor in any case!
1070 cur.updateFlags(Update::FitCursor);
1072 TextMetrics const & tm = cur.bv().textMetrics(this);
1073 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1076 if (cur.pos() && cur.boundary())
1077 row = pm.pos2row(cur.pos()-1);
1079 row = pm.pos2row(cur.pos());
1081 int x = cur.targetX();
1083 // We want to keep the x-target on subsequent down movements
1084 // that cross beyond the end of short lines. Thus a special
1085 // handling when the cursor is at the end of line: Use the new
1086 // x-target only if the old one was before the end of line.
1087 if (cur.pos() != pm.rows()[row].endpos()
1088 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1089 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1094 if (!cur.selection()) {
1095 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1097 // To middle of next row
1098 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1099 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1100 cur.clearSelection();
1102 // This happens when you move out of an inset.
1103 // And to give the DEPM the possibility of doing
1104 // something we must provide it with two different
1110 bool const changed = cur.bv().checkDepm(dummy, old);
1112 // Make sure that cur gets back whatever happened to dummy(Lgb)
1119 bool updateNeeded = false;
1121 if (row + 1 < int(pm.rows().size())) {
1122 updateNeeded |= setCursor(cur, cur.pit(),
1123 tm.x2pos(cur.pit(), row + 1, x));
1124 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1126 updateNeeded |= setCursor(cur, cur.pit(),
1127 tm.x2pos(cur.pit(), 0, x));
1132 return updateNeeded;
1136 bool Text::cursorUpParagraph(Cursor & cur)
1138 bool updated = false;
1140 updated = setCursor(cur, cur.pit(), 0);
1141 else if (cur.pit() != 0)
1142 updated = setCursor(cur, cur.pit() - 1, 0);
1147 bool Text::cursorDownParagraph(Cursor & cur)
1149 bool updated = false;
1150 if (cur.pit() != cur.lastpit())
1151 updated = setCursor(cur, cur.pit() + 1, 0);
1153 updated = setCursor(cur, cur.pit(), cur.lastpos());
1158 // fix the cursor `cur' after a characters has been deleted at `where'
1159 // position. Called by deleteEmptyParagraphMechanism
1160 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1162 // Do nothing if cursor is not in the paragraph where the
1163 // deletion occured,
1164 if (cur.pit() != where.pit())
1167 // If cursor position is after the deletion place update it
1168 if (cur.pos() > where.pos())
1171 // Check also if we don't want to set the cursor on a spot behind the
1172 // pagragraph because we erased the last character.
1173 if (cur.pos() > cur.lastpos())
1174 cur.pos() = cur.lastpos();
1178 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1179 Cursor & old, bool & need_anchor_change)
1181 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1183 Paragraph & oldpar = old.paragraph();
1185 // We allow all kinds of "mumbo-jumbo" when freespacing.
1186 if (oldpar.isFreeSpacing())
1189 /* Ok I'll put some comments here about what is missing.
1190 There are still some small problems that can lead to
1191 double spaces stored in the document file or space at
1192 the beginning of paragraphs(). This happens if you have
1193 the cursor between to spaces and then save. Or if you
1194 cut and paste and the selection have a space at the
1195 beginning and then save right after the paste. (Lgb)
1198 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1199 // delete the LineSeparator.
1202 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1203 // delete the LineSeparator.
1206 bool const same_inset = &old.inset() == &cur.inset();
1207 bool const same_par = same_inset && old.pit() == cur.pit();
1208 bool const same_par_pos = same_par && old.pos() == cur.pos();
1210 // If the chars around the old cursor were spaces, delete one of them.
1211 if (!same_par_pos) {
1212 // Only if the cursor has really moved.
1214 && old.pos() < oldpar.size()
1215 && oldpar.isLineSeparator(old.pos())
1216 && oldpar.isLineSeparator(old.pos() - 1)
1217 && !oldpar.isDeleted(old.pos() - 1)) {
1218 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1219 #ifdef WITH_WARNINGS
1220 #warning This will not work anymore when we have multiple views of the same buffer
1221 // In this case, we will have to correct also the cursors held by
1222 // other bufferviews. It will probably be easier to do that in a more
1223 // automated way in CursorSlice code. (JMarc 26/09/2001)
1225 // correct all cursor parts
1227 fixCursorAfterDelete(cur.top(), old.top());
1228 need_anchor_change = true;
1234 // only do our magic if we changed paragraph
1238 // don't delete anything if this is the ONLY paragraph!
1239 if (old.lastpit() == 0)
1242 // Do not delete empty paragraphs with keepempty set.
1243 if (oldpar.allowEmpty())
1246 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1248 recordUndo(old, Undo::ATOMIC,
1249 max(old.pit() - 1, pit_type(0)),
1250 min(old.pit() + 1, old.lastpit()));
1251 ParagraphList & plist = old.text()->paragraphs();
1252 plist.erase(boost::next(plist.begin(), old.pit()));
1254 // see #warning above
1255 if (cur.depth() >= old.depth()) {
1256 CursorSlice & curslice = cur[old.depth() - 1];
1257 if (&curslice.inset() == &old.inset()
1258 && curslice.pit() > old.pit()) {
1260 // since a paragraph has been deleted, all the
1261 // insets after `old' have been copied and
1262 // their address has changed. Therefore we
1263 // need to `regenerate' cur. (JMarc)
1264 cur.updateInsets(&(cur.bottom().inset()));
1265 need_anchor_change = true;
1271 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1272 need_anchor_change = true;
1273 // We return true here because the Paragraph contents changed and
1274 // we need a redraw before further action is processed.
1282 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1284 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1286 for (pit_type pit = first; pit <= last; ++pit) {
1287 Paragraph & par = pars_[pit];
1289 // We allow all kinds of "mumbo-jumbo" when freespacing.
1290 if (par.isFreeSpacing())
1293 for (pos_type pos = 1; pos < par.size(); ++pos) {
1294 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1295 && !par.isDeleted(pos - 1)) {
1296 if (par.eraseChar(pos - 1, trackChanges)) {
1302 // don't delete anything if this is the only remaining paragraph within the given range
1303 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1307 // don't delete empty paragraphs with keepempty set
1308 if (par.allowEmpty())
1311 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1312 pars_.erase(boost::next(pars_.begin(), pit));
1318 par.stripLeadingSpaces(trackChanges);
1323 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1325 recordUndo(cur, Undo::ATOMIC, first, last);
1329 void Text::recUndo(Cursor & cur, pit_type par) const
1331 recordUndo(cur, Undo::ATOMIC, par, par);