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
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
30 #include "bufferview_funcs.h"
33 #include "CoordCache.h"
35 #include "CutAndPaste.h"
37 #include "DispatchResult.h"
38 #include "ErrorList.h"
39 #include "FuncRequest.h"
46 #include "Paragraph.h"
47 #include "TextMetrics.h"
48 #include "paragraph_funcs.h"
49 #include "ParagraphParameters.h"
50 #include "ParIterator.h"
52 #include "ServerSocket.h"
56 #include "frontends/FontMetrics.h"
58 #include "insets/InsetEnvironment.h"
60 #include "mathed/InsetMathHull.h"
62 #include "support/textutils.h"
64 #include <boost/current_function.hpp>
72 using std::ostringstream;
76 using std::istringstream;
79 : current_font(Font::ALL_INHERIT),
80 background_color_(Color::background),
85 bool Text::isMainText(Buffer const & buffer) const
87 return &buffer.text() == this;
91 //takes screen x,y coordinates
92 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
94 pit_type pit = getPitNearY(bv, y);
95 BOOST_ASSERT(pit != -1);
97 Paragraph const & par = pars_[pit];
100 << BOOST_CURRENT_FUNCTION
105 InsetList::const_iterator iit = par.insetlist.begin();
106 InsetList::const_iterator iend = par.insetlist.end();
107 for (; iit != iend; ++iit) {
108 Inset * inset = iit->inset;
111 << BOOST_CURRENT_FUNCTION
112 << ": examining inset " << inset << endl;
114 if (bv.coordCache().getInsets().has(inset))
116 << BOOST_CURRENT_FUNCTION
117 << ": xo: " << inset->xo(bv) << "..."
118 << inset->xo(bv) + inset->width()
119 << " yo: " << inset->yo(bv) - inset->ascent()
121 << inset->yo(bv) + inset->descent()
125 << BOOST_CURRENT_FUNCTION
126 << ": inset has no cached position" << endl;
128 if (inset->covers(bv, x, y)) {
130 << BOOST_CURRENT_FUNCTION
131 << ": Hit inset: " << inset << endl;
136 << BOOST_CURRENT_FUNCTION
137 << ": No inset hit. " << endl;
143 // Gets the fully instantiated font at a given position in a paragraph
144 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
145 // The difference is that this one is used for displaying, and thus we
146 // are allowed to make cosmetic improvements. For instance make footnotes
148 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
149 pos_type const pos) const
151 BOOST_ASSERT(pos >= 0);
153 LayoutPtr 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 LayoutPtr 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 LayoutPtr 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 LayoutPtr 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 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
311 pos_type pos, Font const & font, bool toggleall)
313 BOOST_ASSERT(pars_[pit].isInset(pos) &&
314 pars_[pit].getInset(pos)->noFontChange());
316 Inset * const inset = pars_[pit].getInset(pos);
317 CursorSlice::idx_type endidx = inset->nargs();
318 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
319 Text * text = cs.text();
321 // last position of the cell
322 CursorSlice cellend = cs;
323 cellend.pit() = cellend.lastpit();
324 cellend.pos() = cellend.lastpos();
325 text->setFont(buffer, cs, cellend, font, toggleall);
331 // return past-the-last paragraph influenced by a layout change on pit
332 pit_type Text::undoSpan(pit_type pit)
334 pit_type end = paragraphs().size();
335 pit_type nextpit = pit + 1;
338 //because of parindents
339 if (!pars_[pit].getDepth())
340 return boost::next(nextpit);
341 //because of depth constrains
342 for (; nextpit != end; ++pit, ++nextpit) {
343 if (!pars_[pit].getDepth())
350 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
351 docstring const & layout)
353 BOOST_ASSERT(start != end);
355 BufferParams const & bufparams = buffer.params();
356 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
358 for (pit_type pit = start; pit != end; ++pit) {
359 Paragraph & par = pars_[pit];
360 par.applyLayout(lyxlayout);
361 if (lyxlayout->margintype == MARGIN_MANUAL)
362 par.setLabelWidthString(par.translateIfPossible(
363 lyxlayout->labelstring(), buffer.params()));
368 // set layout over selection and make a total rebreak of those paragraphs
369 void Text::setLayout(Cursor & cur, docstring const & layout)
371 BOOST_ASSERT(this == cur.text());
372 // special handling of new environment insets
373 BufferView & bv = cur.bv();
374 BufferParams const & params = bv.buffer().params();
375 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
376 if (lyxlayout->is_environment) {
377 // move everything in a new environment inset
378 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
379 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
380 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
381 lyx::dispatch(FuncRequest(LFUN_CUT));
382 Inset * inset = new InsetEnvironment(params, layout);
383 insertInset(cur, inset);
384 //inset->edit(cur, true);
385 //lyx::dispatch(FuncRequest(LFUN_PASTE));
389 pit_type start = cur.selBegin().pit();
390 pit_type end = cur.selEnd().pit() + 1;
391 pit_type undopit = undoSpan(end - 1);
392 recUndo(cur, start, undopit - 1);
393 setLayout(cur.buffer(), start, end, layout);
394 updateLabels(cur.buffer());
398 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
399 Paragraph const & par, int max_depth)
401 if (par.layout()->labeltype == LABEL_BIBLIO)
403 int const depth = par.params().depth();
404 if (type == Text::INC_DEPTH && depth < max_depth)
406 if (type == Text::DEC_DEPTH && depth > 0)
412 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
414 BOOST_ASSERT(this == cur.text());
415 // this happens when selecting several cells in tabular (bug 2630)
416 if (cur.selBegin().idx() != cur.selEnd().idx())
419 pit_type const beg = cur.selBegin().pit();
420 pit_type const end = cur.selEnd().pit() + 1;
421 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
423 for (pit_type pit = beg; pit != end; ++pit) {
424 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
426 max_depth = pars_[pit].getMaxDepthAfter();
432 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
434 BOOST_ASSERT(this == cur.text());
435 pit_type const beg = cur.selBegin().pit();
436 pit_type const end = cur.selEnd().pit() + 1;
437 recordUndoSelection(cur);
438 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
440 for (pit_type pit = beg; pit != end; ++pit) {
441 Paragraph & par = pars_[pit];
442 if (lyx::changeDepthAllowed(type, par, max_depth)) {
443 int const depth = par.params().depth();
444 if (type == INC_DEPTH)
445 par.params().depth(depth + 1);
447 par.params().depth(depth - 1);
449 max_depth = par.getMaxDepthAfter();
451 // this handles the counter labels, and also fixes up
452 // depth values for follow-on (child) paragraphs
453 updateLabels(cur.buffer());
457 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
459 BOOST_ASSERT(this == cur.text());
460 // Set the current_font
461 // Determine basis font
463 pit_type pit = cur.pit();
464 if (cur.pos() < pars_[pit].beginOfBody())
465 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
467 layoutfont = getLayoutFont(cur.buffer(), pit);
469 // Update current font
470 real_current_font.update(font,
471 cur.buffer().params().language,
474 // Reduce to implicit settings
475 current_font = real_current_font;
476 current_font.reduce(layoutfont);
477 // And resolve it completely
478 real_current_font.realize(layoutfont);
480 // if there is no selection that's all we need to do
481 if (!cur.selection())
484 // Ok, we have a selection.
485 recordUndoSelection(cur);
487 setFont(cur.buffer(), cur.selectionBegin().top(),
488 cur.selectionEnd().top(), font, toggleall);
492 void Text::setFont(Buffer const & buffer, CursorSlice const & begin,
493 CursorSlice const & end, Font const & font,
496 // Don't use forwardChar here as ditend might have
497 // pos() == lastpos() and forwardChar would miss it.
498 // Can't use forwardPos either as this descends into
500 Language const * language = buffer.params().language;
501 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
502 if (dit.pos() != dit.lastpos()) {
503 pit_type const pit = dit.pit();
504 pos_type const pos = dit.pos();
505 if (pars_[pit].isInset(pos) &&
506 pars_[pit].getInset(pos)->noFontChange())
507 // We need to propagate the font change to all
508 // text cells of the inset (bug 1973).
509 // FIXME: This should change, see documentation
510 // of noFontChange in Inset.h
511 setInsetFont(buffer, pit, pos, font, toggleall);
512 Font f = getFont(buffer, dit.paragraph(), pos);
513 f.update(font, language, toggleall);
514 setCharFont(buffer, pit, pos, f);
520 // the cursor set functions have a special mechanism. When they
521 // realize you left an empty paragraph, they will delete it.
523 bool Text::cursorHome(Cursor & cur)
525 BOOST_ASSERT(this == cur.text());
526 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
527 Row const & row = pm.getRow(cur.pos(),cur.boundary());
528 return setCursor(cur, cur.pit(), row.pos());
532 bool Text::cursorEnd(Cursor & cur)
534 BOOST_ASSERT(this == cur.text());
535 // if not on the last row of the par, put the cursor before
536 // the final space exept if I have a spanning inset or one string
537 // is so long that we force a break.
538 pos_type end = cur.textRow().endpos();
540 // empty text, end-1 is no valid position
542 bool boundary = false;
543 if (end != cur.lastpos()) {
544 if (!cur.paragraph().isLineSeparator(end-1)
545 && !cur.paragraph().isNewline(end-1))
550 return setCursor(cur, cur.pit(), end, true, boundary);
554 bool Text::cursorTop(Cursor & cur)
556 BOOST_ASSERT(this == cur.text());
557 return setCursor(cur, 0, 0);
561 bool Text::cursorBottom(Cursor & cur)
563 BOOST_ASSERT(this == cur.text());
564 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
568 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
570 BOOST_ASSERT(this == cur.text());
571 // If the mask is completely neutral, tell user
572 if (font == Font(Font::ALL_IGNORE)) {
573 // Could only happen with user style
574 cur.message(_("No font change defined."));
578 // Try implicit word selection
579 // If there is a change in the language the implicit word selection
581 CursorSlice resetCursor = cur.top();
582 bool implicitSelection =
583 font.language() == ignore_language
584 && font.number() == Font::IGNORE
585 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
588 setFont(cur, font, toggleall);
590 // Implicit selections are cleared afterwards
591 // and cursor is set to the original position.
592 if (implicitSelection) {
593 cur.clearSelection();
594 cur.top() = resetCursor;
600 docstring Text::getStringToIndex(Cursor const & cur)
602 BOOST_ASSERT(this == cur.text());
606 idxstring = cur.selectionAsString(false);
608 // Try implicit word selection. If there is a change
609 // in the language the implicit word selection is
612 selectWord(tmpcur, PREVIOUS_WORD);
614 if (!tmpcur.selection())
615 cur.message(_("Nothing to index!"));
616 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
617 cur.message(_("Cannot index more than one paragraph!"));
619 idxstring = tmpcur.selectionAsString(false);
626 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
628 BOOST_ASSERT(cur.text());
629 // make sure that the depth behind the selection are restored, too
630 pit_type undopit = undoSpan(cur.selEnd().pit());
631 recUndo(cur, cur.selBegin().pit(), undopit - 1);
633 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
635 Paragraph & par = pars_[pit];
636 ParagraphParameters params = par.params();
637 params.read(to_utf8(arg), merge);
638 Layout const & layout = *(par.layout());
639 par.params().apply(params, layout);
644 //FIXME This is a little redundant now, but it's probably worth keeping,
645 //especially if we're going to go away from using serialization internally
647 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
649 BOOST_ASSERT(cur.text());
650 // make sure that the depth behind the selection are restored, too
651 pit_type undopit = undoSpan(cur.selEnd().pit());
652 recUndo(cur, cur.selBegin().pit(), undopit - 1);
654 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
656 Paragraph & par = pars_[pit];
657 Layout const & layout = *(par.layout());
658 par.params().apply(p, layout);
663 // this really should just insert the inset and not move the cursor.
664 void Text::insertInset(Cursor & cur, Inset * inset)
666 BOOST_ASSERT(this == cur.text());
668 cur.paragraph().insertInset(cur.pos(), inset, current_font,
669 Change(cur.buffer().params().trackChanges ?
670 Change::INSERTED : Change::UNCHANGED));
674 // needed to insert the selection
675 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
677 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
678 current_font, str, autoBreakRows_);
682 // turn double CR to single CR, others are converted into one
683 // blank. Then insertStringAsLines is called
684 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
686 docstring linestr = str;
687 bool newline_inserted = false;
689 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
690 if (linestr[i] == '\n') {
691 if (newline_inserted) {
692 // we know that \r will be ignored by
693 // insertStringAsLines. Of course, it is a dirty
694 // trick, but it works...
695 linestr[i - 1] = '\r';
699 newline_inserted = true;
701 } else if (isPrintable(linestr[i])) {
702 newline_inserted = false;
705 insertStringAsLines(cur, linestr);
709 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
710 bool setfont, bool boundary)
713 setCursorIntern(cur, par, pos, setfont, boundary);
714 return cur.bv().checkDepm(cur, old);
718 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
720 BOOST_ASSERT(par != int(paragraphs().size()));
724 // now some strict checking
725 Paragraph & para = getPar(par);
727 // None of these should happen, but we're scaredy-cats
729 lyxerr << "dont like -1" << endl;
733 if (pos > para.size()) {
734 lyxerr << "dont like 1, pos: " << pos
735 << " size: " << para.size()
736 << " par: " << par << endl;
742 void Text::setCursorIntern(Cursor & cur,
743 pit_type par, pos_type pos, bool setfont, bool boundary)
745 BOOST_ASSERT(this == cur.text());
746 cur.boundary(boundary);
747 setCursor(cur.top(), par, pos);
753 void Text::setCurrentFont(Cursor & cur)
755 BOOST_ASSERT(this == cur.text());
756 pos_type pos = cur.pos();
757 Paragraph & par = cur.paragraph();
759 // are we behind previous char in fact? -> go to that char
760 if (pos > 0 && cur.boundary())
763 // find position to take the font from
765 // paragraph end? -> font of last char
766 if (pos == cur.lastpos())
768 // on space? -> look at the words in front of space
769 else if (pos > 0 && par.isSeparator(pos)) {
770 // abc| def -> font of c
771 // abc |[WERBEH], i.e. boundary==true -> font of c
772 // abc [WERBEH]| def, font of the space
773 if (!isRTLBoundary(cur.buffer(), par, pos))
779 BufferParams const & bufparams = cur.buffer().params();
780 current_font = par.getFontSettings(bufparams, pos);
781 real_current_font = getFont(cur.buffer(), par, pos);
783 // special case for paragraph end
784 if (cur.pos() == cur.lastpos()
785 && isRTLBoundary(cur.buffer(), par, cur.pos())
786 && !cur.boundary()) {
787 Language const * lang = par.getParLanguage(bufparams);
788 current_font.setLanguage(lang);
789 current_font.setNumber(Font::OFF);
790 real_current_font.setLanguage(lang);
791 real_current_font.setNumber(Font::OFF);
795 // y is screen coordinate
796 pit_type Text::getPitNearY(BufferView & bv, int y) const
798 BOOST_ASSERT(!paragraphs().empty());
799 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
800 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
802 << BOOST_CURRENT_FUNCTION
803 << ": y: " << y << " cache size: " << cc.size()
806 // look for highest numbered paragraph with y coordinate less than given y
809 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
810 CoordCache::InnerParPosCache::const_iterator et = cc.end();
811 CoordCache::InnerParPosCache::const_iterator last = et; last--;
813 TextMetrics & tm = bv.textMetrics(this);
814 ParagraphMetrics const & pm = tm.parMetrics(it->first);
816 // If we are off-screen (before the visible part)
818 // and even before the first paragraph in the cache.
819 && y < it->second.y_ - int(pm.ascent())) {
820 // and we are not at the first paragraph in the inset.
823 // then this is the paragraph we are looking for.
825 // rebreak it and update the CoordCache.
826 tm.redoParagraph(pit);
827 bv.coordCache().parPos()[this][pit] =
828 Point(0, it->second.y_ - pm.descent());
832 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
834 // If we are off-screen (after the visible part)
835 if (y > bv.workHeight()
836 // and even after the first paragraph in the cache.
837 && y >= last->second.y_ + int(pm_last.descent())) {
838 pit = last->first + 1;
839 // and we are not at the last paragraph in the inset.
840 if (pit == int(pars_.size()))
842 // then this is the paragraph we are looking for.
843 // rebreak it and update the CoordCache.
844 tm.redoParagraph(pit);
845 bv.coordCache().parPos()[this][pit] =
846 Point(0, last->second.y_ + pm_last.ascent());
850 for (; it != et; ++it) {
852 << BOOST_CURRENT_FUNCTION
853 << " examining: pit: " << it->first
854 << " y: " << it->second.y_
857 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
859 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
866 << BOOST_CURRENT_FUNCTION
867 << ": found best y: " << yy << " for pit: " << pit
874 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
876 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
878 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
879 BOOST_ASSERT(!pm.rows().empty());
880 RowList::const_iterator rit = pm.rows().begin();
881 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
882 for (; rit != rlast; yy += rit->height(), ++rit)
883 if (yy + rit->height() > y)
889 // x,y are absolute screen coordinates
890 // sets cursor recursively descending into nested editable insets
891 Inset * Text::editXY(Cursor & cur, int x, int y)
893 if (lyxerr.debugging(Debug::WORKAREA)) {
894 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
895 cur.bv().coordCache().dump();
897 pit_type pit = getPitNearY(cur.bv(), y);
898 BOOST_ASSERT(pit != -1);
900 Row const & row = getRowNearY(cur.bv(), y, pit);
903 TextMetrics const & tm = cur.bv().textMetrics(this);
904 int xx = x; // is modified by getColumnNearX
905 pos_type const pos = row.pos()
906 + tm.getColumnNearX(pit, row, xx, bound);
912 // try to descend into nested insets
913 Inset * inset = checkInsetHit(cur.bv(), x, y);
914 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
916 // Either we deconst editXY or better we move current_font
917 // and real_current_font to Cursor
922 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
923 //Inset * insetBehind = pars_[pit].getInset(pos);
925 // This should be just before or just behind the
926 // cursor position set above.
927 BOOST_ASSERT((pos != 0 && inset == insetBefore)
928 || inset == pars_[pit].getInset(pos));
930 // Make sure the cursor points to the position before
932 if (inset == insetBefore) {
937 // Try to descend recursively inside the inset.
938 inset = inset->editXY(cur, x, y);
940 if (cur.top().text() == this)
946 bool Text::checkAndActivateInset(Cursor & cur, bool front)
950 if (front && cur.pos() == cur.lastpos())
952 if (!front && cur.pos() == 0)
954 Inset * inset = front ? cur.nextInset() : cur.prevInset();
955 if (!isHighlyEditableInset(inset))
958 * Apparently, when entering an inset we are expected to be positioned
959 * *before* it in the containing paragraph, regardless of the direction
960 * from which we are entering. Otherwise, cursor placement goes awry,
961 * and when we exit from the beginning, we'll be placed *after* the
966 inset->edit(cur, front);
971 bool Text::cursorLeft(Cursor & cur)
973 // Tell BufferView to test for FitCursor in any case!
974 cur.updateFlags(Update::FitCursor);
976 // not at paragraph start?
978 // if on right side of boundary (i.e. not at paragraph end, but line end)
979 // -> skip it, i.e. set boundary to true, i.e. go only logically left
980 // there are some exceptions to ignore this: lineseps, newlines, spaces
982 // some effectless debug code to see the values in the debugger
983 bool bound = cur.boundary();
984 int rowpos = cur.textRow().pos();
986 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
987 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
988 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
990 if (!cur.boundary() &&
991 cur.textRow().pos() == cur.pos() &&
992 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
993 !cur.paragraph().isNewline(cur.pos() - 1) &&
994 !cur.paragraph().isSeparator(cur.pos() - 1)) {
995 return setCursor(cur, cur.pit(), cur.pos(), true, true);
998 // go left and try to enter inset
999 if (checkAndActivateInset(cur, false))
1002 // normal character left
1003 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1006 // move to the previous paragraph or do nothing
1008 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1013 bool Text::cursorRight(Cursor & cur)
1015 // Tell BufferView to test for FitCursor in any case!
1016 cur.updateFlags(Update::FitCursor);
1018 // not at paragraph end?
1019 if (cur.pos() != cur.lastpos()) {
1020 // in front of editable inset, i.e. jump into it?
1021 if (checkAndActivateInset(cur, true))
1024 // if left of boundary -> just jump to right side
1025 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1026 if (cur.boundary() &&
1027 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1028 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1030 // next position is left of boundary,
1031 // but go to next line for special cases like space, newline, linesep
1033 // some effectless debug code to see the values in the debugger
1034 int endpos = cur.textRow().endpos();
1035 int lastpos = cur.lastpos();
1036 int pos = cur.pos();
1037 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1038 bool newline = cur.paragraph().isNewline(cur.pos());
1039 bool sep = cur.paragraph().isSeparator(cur.pos());
1040 if (cur.pos() != cur.lastpos()) {
1041 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1042 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1043 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1046 if (cur.textRow().endpos() == cur.pos() + 1 &&
1047 cur.textRow().endpos() != cur.lastpos() &&
1048 !cur.paragraph().isNewline(cur.pos()) &&
1049 !cur.paragraph().isLineSeparator(cur.pos()) &&
1050 !cur.paragraph().isSeparator(cur.pos())) {
1051 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1054 // in front of RTL boundary? Stay on this side of the boundary because:
1055 // ab|cDDEEFFghi -> abc|DDEEFFghi
1056 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1057 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1060 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1063 // move to next paragraph
1064 if (cur.pit() != cur.lastpit())
1065 return setCursor(cur, cur.pit() + 1, 0);
1070 bool Text::cursorUpParagraph(Cursor & cur)
1072 bool updated = false;
1074 updated = setCursor(cur, cur.pit(), 0);
1075 else if (cur.pit() != 0)
1076 updated = setCursor(cur, cur.pit() - 1, 0);
1081 bool Text::cursorDownParagraph(Cursor & cur)
1083 bool updated = false;
1084 if (cur.pit() != cur.lastpit())
1085 updated = setCursor(cur, cur.pit() + 1, 0);
1087 updated = setCursor(cur, cur.pit(), cur.lastpos());
1092 // fix the cursor `cur' after a characters has been deleted at `where'
1093 // position. Called by deleteEmptyParagraphMechanism
1094 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1096 // Do nothing if cursor is not in the paragraph where the
1097 // deletion occured,
1098 if (cur.pit() != where.pit())
1101 // If cursor position is after the deletion place update it
1102 if (cur.pos() > where.pos())
1105 // Check also if we don't want to set the cursor on a spot behind the
1106 // pagragraph because we erased the last character.
1107 if (cur.pos() > cur.lastpos())
1108 cur.pos() = cur.lastpos();
1112 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1113 Cursor & old, bool & need_anchor_change)
1115 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1117 Paragraph & oldpar = old.paragraph();
1119 // We allow all kinds of "mumbo-jumbo" when freespacing.
1120 if (oldpar.isFreeSpacing())
1123 /* Ok I'll put some comments here about what is missing.
1124 There are still some small problems that can lead to
1125 double spaces stored in the document file or space at
1126 the beginning of paragraphs(). This happens if you have
1127 the cursor between to spaces and then save. Or if you
1128 cut and paste and the selection have a space at the
1129 beginning and then save right after the paste. (Lgb)
1132 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1133 // delete the LineSeparator.
1136 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1137 // delete the LineSeparator.
1140 bool const same_inset = &old.inset() == &cur.inset();
1141 bool const same_par = same_inset && old.pit() == cur.pit();
1142 bool const same_par_pos = same_par && old.pos() == cur.pos();
1144 // If the chars around the old cursor were spaces, delete one of them.
1145 if (!same_par_pos) {
1146 // Only if the cursor has really moved.
1148 && old.pos() < oldpar.size()
1149 && oldpar.isLineSeparator(old.pos())
1150 && oldpar.isLineSeparator(old.pos() - 1)
1151 && !oldpar.isDeleted(old.pos() - 1)
1152 && !oldpar.isDeleted(old.pos())) {
1153 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1154 // FIXME: This will not work anymore when we have multiple views of the same buffer
1155 // In this case, we will have to correct also the cursors held by
1156 // other bufferviews. It will probably be easier to do that in a more
1157 // automated way in CursorSlice code. (JMarc 26/09/2001)
1158 // correct all cursor parts
1160 fixCursorAfterDelete(cur.top(), old.top());
1161 need_anchor_change = true;
1167 // only do our magic if we changed paragraph
1171 // don't delete anything if this is the ONLY paragraph!
1172 if (old.lastpit() == 0)
1175 // Do not delete empty paragraphs with keepempty set.
1176 if (oldpar.allowEmpty())
1179 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1181 recordUndo(old, Undo::ATOMIC,
1182 max(old.pit() - 1, pit_type(0)),
1183 min(old.pit() + 1, old.lastpit()));
1184 ParagraphList & plist = old.text()->paragraphs();
1185 plist.erase(boost::next(plist.begin(), old.pit()));
1187 // see #warning (FIXME?) above
1188 if (cur.depth() >= old.depth()) {
1189 CursorSlice & curslice = cur[old.depth() - 1];
1190 if (&curslice.inset() == &old.inset()
1191 && curslice.pit() > old.pit()) {
1193 // since a paragraph has been deleted, all the
1194 // insets after `old' have been copied and
1195 // their address has changed. Therefore we
1196 // need to `regenerate' cur. (JMarc)
1197 cur.updateInsets(&(cur.bottom().inset()));
1198 need_anchor_change = true;
1204 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1205 need_anchor_change = true;
1206 // We return true here because the Paragraph contents changed and
1207 // we need a redraw before further action is processed.
1215 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1217 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1219 for (pit_type pit = first; pit <= last; ++pit) {
1220 Paragraph & par = pars_[pit];
1222 // We allow all kinds of "mumbo-jumbo" when freespacing.
1223 if (par.isFreeSpacing())
1226 for (pos_type pos = 1; pos < par.size(); ++pos) {
1227 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1228 && !par.isDeleted(pos - 1)) {
1229 if (par.eraseChar(pos - 1, trackChanges)) {
1235 // don't delete anything if this is the only remaining paragraph within the given range
1236 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1240 // don't delete empty paragraphs with keepempty set
1241 if (par.allowEmpty())
1244 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1245 pars_.erase(boost::next(pars_.begin(), pit));
1251 par.stripLeadingSpaces(trackChanges);
1256 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1258 recordUndo(cur, Undo::ATOMIC, first, last);
1262 void Text::recUndo(Cursor & cur, pit_type par) const
1264 recordUndo(cur, Undo::ATOMIC, par, par);