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"
32 #include "CoordCache.h"
34 #include "CutAndPaste.h"
36 #include "DispatchResult.h"
37 #include "ErrorList.h"
38 #include "FuncRequest.h"
45 #include "Paragraph.h"
46 #include "TextMetrics.h"
47 #include "paragraph_funcs.h"
48 #include "ParagraphParameters.h"
49 #include "ParIterator.h"
51 #include "ServerSocket.h"
55 #include "frontends/FontMetrics.h"
57 #include "insets/InsetEnvironment.h"
59 #include "mathed/InsetMathHull.h"
61 #include "support/textutils.h"
63 #include <boost/current_function.hpp>
71 using std::ostringstream;
78 : current_font(Font::ALL_INHERIT),
79 background_color_(Color::background),
84 bool Text::isMainText(Buffer const & buffer) const
86 return &buffer.text() == this;
90 //takes screen x,y coordinates
91 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
93 pit_type pit = getPitNearY(bv, y);
94 BOOST_ASSERT(pit != -1);
96 Paragraph const & par = pars_[pit];
99 << BOOST_CURRENT_FUNCTION
104 InsetList::const_iterator iit = par.insetlist.begin();
105 InsetList::const_iterator iend = par.insetlist.end();
106 for (; iit != iend; ++iit) {
107 Inset * inset = iit->inset;
110 << BOOST_CURRENT_FUNCTION
111 << ": examining inset " << inset << endl;
113 if (bv.coordCache().getInsets().has(inset))
115 << BOOST_CURRENT_FUNCTION
116 << ": xo: " << inset->xo(bv) << "..."
117 << inset->xo(bv) + inset->width()
118 << " yo: " << inset->yo(bv) - inset->ascent()
120 << inset->yo(bv) + inset->descent()
124 << BOOST_CURRENT_FUNCTION
125 << ": inset has no cached position" << endl;
127 if (inset->covers(bv, x, y)) {
129 << BOOST_CURRENT_FUNCTION
130 << ": Hit inset: " << inset << endl;
135 << BOOST_CURRENT_FUNCTION
136 << ": No inset hit. " << endl;
142 // Gets the fully instantiated font at a given position in a paragraph
143 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
144 // The difference is that this one is used for displaying, and thus we
145 // are allowed to make cosmetic improvements. For instance make footnotes
147 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
148 pos_type const pos) const
150 BOOST_ASSERT(pos >= 0);
152 Layout_ptr const & layout = par.layout();
156 BufferParams const & params = buffer.params();
157 pos_type const body_pos = par.beginOfBody();
159 // We specialize the 95% common case:
160 if (!par.getDepth()) {
161 Font f = par.getFontSettings(params, pos);
162 if (!isMainText(buffer))
163 applyOuterFont(buffer, f);
166 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
167 lf = layout->labelfont;
168 rlf = layout->reslabelfont;
171 rlf = layout->resfont;
173 // In case the default family has been customized
174 if (lf.family() == Font::INHERIT_FAMILY)
175 rlf.setFamily(params.getFont().family());
176 return f.realize(rlf);
179 // The uncommon case need not be optimized as much
182 layoutfont = layout->labelfont;
184 layoutfont = layout->font;
186 Font font = par.getFontSettings(params, pos);
187 font.realize(layoutfont);
189 if (!isMainText(buffer))
190 applyOuterFont(buffer, font);
192 // Find the pit value belonging to paragraph. This will not break
193 // even if pars_ would not be a vector anymore.
194 // Performance appears acceptable.
196 pit_type pit = pars_.size();
197 for (pit_type it = 0; it < pit; ++it)
198 if (&pars_[it] == &par) {
202 // Realize against environment font information
203 // NOTE: the cast to pit_type should be removed when pit_type
204 // changes to a unsigned integer.
205 if (pit < pit_type(pars_.size()))
206 font.realize(outerFont(pit, pars_));
208 // Realize with the fonts of lesser depth.
209 font.realize(params.getFont());
214 // There are currently two font mechanisms in LyX:
215 // 1. The font attributes in a lyxtext, and
216 // 2. The inset-specific font properties, defined in an inset's
217 // metrics() and draw() methods and handed down the inset chain through
218 // the pi/mi parameters, and stored locally in a lyxtext in font_.
219 // This is where the two are integrated in the final fully realized
221 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
223 lf.reduce(buffer.params().getFont());
225 lf.setLanguage(font.language());
230 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
232 Layout_ptr const & layout = pars_[pit].layout();
234 if (!pars_[pit].getDepth()) {
235 Font lf = layout->resfont;
236 // In case the default family has been customized
237 if (layout->font.family() == Font::INHERIT_FAMILY)
238 lf.setFamily(buffer.params().getFont().family());
242 Font font = layout->font;
243 // Realize with the fonts of lesser depth.
244 //font.realize(outerFont(pit, paragraphs()));
245 font.realize(buffer.params().getFont());
251 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
253 Layout_ptr const & layout = par.layout();
255 if (!par.getDepth()) {
256 Font lf = layout->reslabelfont;
257 // In case the default family has been customized
258 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
259 lf.setFamily(buffer.params().getFont().family());
263 Font font = layout->labelfont;
264 // Realize with the fonts of lesser depth.
265 font.realize(buffer.params().getFont());
271 void Text::setCharFont(Buffer const & buffer, pit_type pit,
272 pos_type pos, Font const & fnt)
275 Layout_ptr const & layout = pars_[pit].layout();
277 // Get concrete layout font to reduce against
280 if (pos < pars_[pit].beginOfBody())
281 layoutfont = layout->labelfont;
283 layoutfont = layout->font;
285 // Realize against environment font information
286 if (pars_[pit].getDepth()) {
288 while (!layoutfont.resolved() &&
289 tp != pit_type(paragraphs().size()) &&
290 pars_[tp].getDepth()) {
291 tp = outerHook(tp, paragraphs());
292 if (tp != pit_type(paragraphs().size()))
293 layoutfont.realize(pars_[tp].layout()->font);
297 // Inside inset, apply the inset's font attributes if any
299 if (!isMainText(buffer))
300 layoutfont.realize(font_);
302 layoutfont.realize(buffer.params().getFont());
304 // Now, reduce font against full layout font
305 font.reduce(layoutfont);
307 pars_[pit].setFont(pos, font);
311 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
312 pos_type pos, Font const & font, bool toggleall)
314 BOOST_ASSERT(pars_[pit].isInset(pos) &&
315 pars_[pit].getInset(pos)->noFontChange());
317 Inset * const inset = pars_[pit].getInset(pos);
318 DocIterator dit = doc_iterator_begin(*inset);
319 // start of the last cell
320 DocIterator end = dit;
321 end.idx() = end.lastidx();
324 Text * text = dit.text();
325 Inset * cell = dit.realInset();
327 DocIterator cellbegin = doc_iterator_begin(*cell);
328 // last position of the cell
329 DocIterator cellend = cellbegin;
330 cellend.pit() = cellend.lastpit();
331 cellend.pos() = cellend.lastpos();
332 text->setFont(buffer, cellbegin, cellend, font, toggleall);
341 // return past-the-last paragraph influenced by a layout change on pit
342 pit_type Text::undoSpan(pit_type pit)
344 pit_type end = paragraphs().size();
345 pit_type nextpit = pit + 1;
348 //because of parindents
349 if (!pars_[pit].getDepth())
350 return boost::next(nextpit);
351 //because of depth constrains
352 for (; nextpit != end; ++pit, ++nextpit) {
353 if (!pars_[pit].getDepth())
360 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
361 docstring const & layout)
363 BOOST_ASSERT(start != end);
365 BufferParams const & bufparams = buffer.params();
366 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
368 for (pit_type pit = start; pit != end; ++pit) {
369 Paragraph & par = pars_[pit];
370 par.applyLayout(lyxlayout);
371 if (lyxlayout->margintype == MARGIN_MANUAL)
372 par.setLabelWidthString(par.translateIfPossible(
373 lyxlayout->labelstring(), buffer.params()));
378 // set layout over selection and make a total rebreak of those paragraphs
379 void Text::setLayout(Cursor & cur, docstring const & layout)
381 BOOST_ASSERT(this == cur.text());
382 // special handling of new environment insets
383 BufferView & bv = cur.bv();
384 BufferParams const & params = bv.buffer()->params();
385 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
386 if (lyxlayout->is_environment) {
387 // move everything in a new environment inset
388 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
389 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
390 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
391 lyx::dispatch(FuncRequest(LFUN_CUT));
392 Inset * inset = new InsetEnvironment(params, layout);
393 insertInset(cur, inset);
394 //inset->edit(cur, true);
395 //lyx::dispatch(FuncRequest(LFUN_PASTE));
399 pit_type start = cur.selBegin().pit();
400 pit_type end = cur.selEnd().pit() + 1;
401 pit_type undopit = undoSpan(end - 1);
402 recUndo(cur, start, undopit - 1);
403 setLayout(cur.buffer(), start, end, layout);
404 updateLabels(cur.buffer());
408 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
409 Paragraph const & par, int max_depth)
411 if (par.layout()->labeltype == LABEL_BIBLIO)
413 int const depth = par.params().depth();
414 if (type == Text::INC_DEPTH && depth < max_depth)
416 if (type == Text::DEC_DEPTH && depth > 0)
422 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
424 BOOST_ASSERT(this == cur.text());
425 // this happens when selecting several cells in tabular (bug 2630)
426 if (cur.selBegin().idx() != cur.selEnd().idx())
429 pit_type const beg = cur.selBegin().pit();
430 pit_type const end = cur.selEnd().pit() + 1;
431 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
433 for (pit_type pit = beg; pit != end; ++pit) {
434 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
436 max_depth = pars_[pit].getMaxDepthAfter();
442 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
444 BOOST_ASSERT(this == cur.text());
445 pit_type const beg = cur.selBegin().pit();
446 pit_type const end = cur.selEnd().pit() + 1;
447 recordUndoSelection(cur);
448 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
450 for (pit_type pit = beg; pit != end; ++pit) {
451 Paragraph & par = pars_[pit];
452 if (lyx::changeDepthAllowed(type, par, max_depth)) {
453 int const depth = par.params().depth();
454 if (type == INC_DEPTH)
455 par.params().depth(depth + 1);
457 par.params().depth(depth - 1);
459 max_depth = par.getMaxDepthAfter();
461 // this handles the counter labels, and also fixes up
462 // depth values for follow-on (child) paragraphs
463 updateLabels(cur.buffer());
467 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
469 BOOST_ASSERT(this == cur.text());
470 // Set the current_font
471 // Determine basis font
473 pit_type pit = cur.pit();
474 if (cur.pos() < pars_[pit].beginOfBody())
475 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
477 layoutfont = getLayoutFont(cur.buffer(), pit);
479 // Update current font
480 real_current_font.update(font,
481 cur.buffer().params().language,
484 // Reduce to implicit settings
485 current_font = real_current_font;
486 current_font.reduce(layoutfont);
487 // And resolve it completely
488 real_current_font.realize(layoutfont);
490 // if there is no selection that's all we need to do
491 if (!cur.selection())
494 // Ok, we have a selection.
495 recordUndoSelection(cur);
497 setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
502 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
503 DocIterator const & end, Font const & font,
506 // Don't use forwardChar here as ditend might have
507 // pos() == lastpos() and forwardChar would miss it.
508 // Can't use forwardPos either as this descends into
510 Language const * language = buffer.params().language;
511 for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
512 if (dit.pos() != dit.lastpos()) {
513 pit_type const pit = dit.pit();
514 pos_type const pos = dit.pos();
515 if (pars_[pit].isInset(pos) &&
516 pars_[pit].getInset(pos)->noFontChange())
517 // We need to propagate the font change to all
518 // text cells of the inset (bug 1973).
519 // FIXME: This should change, see documentation
520 // of noFontChange in Inset.h
521 setInsetFont(buffer, pit, pos, font, toggleall);
522 Font f = getFont(buffer, dit.paragraph(), pos);
523 f.update(font, language, toggleall);
524 setCharFont(buffer, pit, pos, f);
530 // the cursor set functions have a special mechanism. When they
531 // realize you left an empty paragraph, they will delete it.
533 bool Text::cursorHome(Cursor & cur)
535 BOOST_ASSERT(this == cur.text());
536 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
537 Row const & row = pm.getRow(cur.pos(),cur.boundary());
538 return setCursor(cur, cur.pit(), row.pos());
542 bool Text::cursorEnd(Cursor & cur)
544 BOOST_ASSERT(this == cur.text());
545 // if not on the last row of the par, put the cursor before
546 // the final space exept if I have a spanning inset or one string
547 // is so long that we force a break.
548 pos_type end = cur.textRow().endpos();
550 // empty text, end-1 is no valid position
552 bool boundary = false;
553 if (end != cur.lastpos()) {
554 if (!cur.paragraph().isLineSeparator(end-1)
555 && !cur.paragraph().isNewline(end-1))
560 return setCursor(cur, cur.pit(), end, true, boundary);
564 bool Text::cursorTop(Cursor & cur)
566 BOOST_ASSERT(this == cur.text());
567 return setCursor(cur, 0, 0);
571 bool Text::cursorBottom(Cursor & cur)
573 BOOST_ASSERT(this == cur.text());
574 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
578 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
580 BOOST_ASSERT(this == cur.text());
581 // If the mask is completely neutral, tell user
582 if (font == Font(Font::ALL_IGNORE)) {
583 // Could only happen with user style
584 cur.message(_("No font change defined."));
588 // Try implicit word selection
589 // If there is a change in the language the implicit word selection
591 CursorSlice resetCursor = cur.top();
592 bool implicitSelection =
593 font.language() == ignore_language
594 && font.number() == Font::IGNORE
595 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
598 setFont(cur, font, toggleall);
600 // Implicit selections are cleared afterwards
601 // and cursor is set to the original position.
602 if (implicitSelection) {
603 cur.clearSelection();
604 cur.top() = resetCursor;
610 docstring Text::getStringToIndex(Cursor const & cur)
612 BOOST_ASSERT(this == cur.text());
616 idxstring = cur.selectionAsString(false);
618 // Try implicit word selection. If there is a change
619 // in the language the implicit word selection is
622 selectWord(tmpcur, PREVIOUS_WORD);
624 if (!tmpcur.selection())
625 cur.message(_("Nothing to index!"));
626 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
627 cur.message(_("Cannot index more than one paragraph!"));
629 idxstring = tmpcur.selectionAsString(false);
636 void Text::setParagraph(Cursor & cur,
637 Spacing const & spacing, LyXAlignment align,
638 docstring const & labelwidthstring, bool noindent)
640 BOOST_ASSERT(cur.text());
641 // make sure that the depth behind the selection are restored, too
642 pit_type undopit = undoSpan(cur.selEnd().pit());
643 recUndo(cur, cur.selBegin().pit(), undopit - 1);
645 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
647 Paragraph & par = pars_[pit];
648 ParagraphParameters & params = par.params();
649 params.spacing(spacing);
651 // does the layout allow the new alignment?
652 //FIXME The reason we need the first check is because
653 //LYX_ALIGN_LAYOUT isn't required to be possible. It
654 //should be...and will be.
655 if ((align == LYX_ALIGN_LAYOUT) ||
656 (align & par.layout()->alignpossible))
658 par.setLabelWidthString(labelwidthstring);
659 params.noindent(noindent);
664 // this really should just insert the inset and not move the cursor.
665 void Text::insertInset(Cursor & cur, Inset * inset)
667 BOOST_ASSERT(this == cur.text());
669 cur.paragraph().insertInset(cur.pos(), inset, current_font,
670 Change(cur.buffer().params().trackChanges ?
671 Change::INSERTED : Change::UNCHANGED));
675 // needed to insert the selection
676 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
678 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
679 current_font, str, autoBreakRows_);
683 // turn double CR to single CR, others are converted into one
684 // blank. Then insertStringAsLines is called
685 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
687 docstring linestr = str;
688 bool newline_inserted = false;
690 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
691 if (linestr[i] == '\n') {
692 if (newline_inserted) {
693 // we know that \r will be ignored by
694 // insertStringAsLines. Of course, it is a dirty
695 // trick, but it works...
696 linestr[i - 1] = '\r';
700 newline_inserted = true;
702 } else if (isPrintable(linestr[i])) {
703 newline_inserted = false;
706 insertStringAsLines(cur, linestr);
710 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
711 bool setfont, bool boundary)
714 setCursorIntern(cur, par, pos, setfont, boundary);
715 return cur.bv().checkDepm(cur, old);
719 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
721 BOOST_ASSERT(par != int(paragraphs().size()));
725 // now some strict checking
726 Paragraph & para = getPar(par);
728 // None of these should happen, but we're scaredy-cats
730 lyxerr << "dont like -1" << endl;
734 if (pos > para.size()) {
735 lyxerr << "dont like 1, pos: " << pos
736 << " size: " << para.size()
737 << " par: " << par << endl;
743 void Text::setCursorIntern(Cursor & cur,
744 pit_type par, pos_type pos, bool setfont, bool boundary)
746 BOOST_ASSERT(this == cur.text());
747 cur.boundary(boundary);
748 setCursor(cur.top(), par, pos);
754 void Text::setCurrentFont(Cursor & cur)
756 BOOST_ASSERT(this == cur.text());
757 pos_type pos = cur.pos();
758 Paragraph & par = cur.paragraph();
760 // are we behind previous char in fact? -> go to that char
761 if (pos > 0 && cur.boundary())
764 // find position to take the font from
766 // paragraph end? -> font of last char
767 if (pos == cur.lastpos())
769 // on space? -> look at the words in front of space
770 else if (pos > 0 && par.isSeparator(pos)) {
771 // abc| def -> font of c
772 // abc |[WERBEH], i.e. boundary==true -> font of c
773 // abc [WERBEH]| def, font of the space
774 if (!isRTLBoundary(cur.buffer(), par, pos))
780 BufferParams const & bufparams = cur.buffer().params();
781 current_font = par.getFontSettings(bufparams, pos);
782 real_current_font = getFont(cur.buffer(), par, pos);
784 // special case for paragraph end
785 if (cur.pos() == cur.lastpos()
786 && isRTLBoundary(cur.buffer(), par, cur.pos())
787 && !cur.boundary()) {
788 Language const * lang = par.getParLanguage(bufparams);
789 current_font.setLanguage(lang);
790 current_font.setNumber(Font::OFF);
791 real_current_font.setLanguage(lang);
792 real_current_font.setNumber(Font::OFF);
796 // y is screen coordinate
797 pit_type Text::getPitNearY(BufferView & bv, int y) const
799 BOOST_ASSERT(!paragraphs().empty());
800 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
801 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
803 << BOOST_CURRENT_FUNCTION
804 << ": y: " << y << " cache size: " << cc.size()
807 // look for highest numbered paragraph with y coordinate less than given y
810 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
811 CoordCache::InnerParPosCache::const_iterator et = cc.end();
812 CoordCache::InnerParPosCache::const_iterator last = et; last--;
814 TextMetrics & tm = bv.textMetrics(this);
815 ParagraphMetrics const & pm = tm.parMetrics(it->first);
817 // If we are off-screen (before the visible part)
819 // and even before the first paragraph in the cache.
820 && y < it->second.y_ - int(pm.ascent())) {
821 // and we are not at the first paragraph in the inset.
824 // then this is the paragraph we are looking for.
826 // rebreak it and update the CoordCache.
827 tm.redoParagraph(pit);
828 bv.coordCache().parPos()[this][pit] =
829 Point(0, it->second.y_ - pm.descent());
833 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
835 // If we are off-screen (after the visible part)
836 if (y > bv.workHeight()
837 // and even after the first paragraph in the cache.
838 && y >= last->second.y_ + int(pm_last.descent())) {
839 pit = last->first + 1;
840 // and we are not at the last paragraph in the inset.
841 if (pit == int(pars_.size()))
843 // then this is the paragraph we are looking for.
844 // rebreak it and update the CoordCache.
845 tm.redoParagraph(pit);
846 bv.coordCache().parPos()[this][pit] =
847 Point(0, last->second.y_ + pm_last.ascent());
851 for (; it != et; ++it) {
853 << BOOST_CURRENT_FUNCTION
854 << " examining: pit: " << it->first
855 << " y: " << it->second.y_
858 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
860 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
867 << BOOST_CURRENT_FUNCTION
868 << ": found best y: " << yy << " for pit: " << pit
875 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
877 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
879 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
880 BOOST_ASSERT(!pm.rows().empty());
881 RowList::const_iterator rit = pm.rows().begin();
882 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
883 for (; rit != rlast; yy += rit->height(), ++rit)
884 if (yy + rit->height() > y)
890 // x,y are absolute screen coordinates
891 // sets cursor recursively descending into nested editable insets
892 Inset * Text::editXY(Cursor & cur, int x, int y)
894 if (lyxerr.debugging(Debug::WORKAREA)) {
895 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
896 cur.bv().coordCache().dump();
898 pit_type pit = getPitNearY(cur.bv(), y);
899 BOOST_ASSERT(pit != -1);
901 Row const & row = getRowNearY(cur.bv(), y, pit);
904 TextMetrics const & tm = cur.bv().textMetrics(this);
905 int xx = x; // is modified by getColumnNearX
906 pos_type const pos = row.pos()
907 + tm.getColumnNearX(pit, row, xx, bound);
913 // try to descend into nested insets
914 Inset * inset = checkInsetHit(cur.bv(), x, y);
915 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
917 // Either we deconst editXY or better we move current_font
918 // and real_current_font to Cursor
923 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
924 //Inset * insetBehind = pars_[pit].getInset(pos);
926 // This should be just before or just behind the
927 // cursor position set above.
928 BOOST_ASSERT((pos != 0 && inset == insetBefore)
929 || inset == pars_[pit].getInset(pos));
931 // Make sure the cursor points to the position before
933 if (inset == insetBefore) {
938 // Try to descend recursively inside the inset.
939 inset = inset->editXY(cur, x, y);
941 if (cur.top().text() == this)
947 bool Text::checkAndActivateInset(Cursor & cur, bool front)
951 if (front && cur.pos() == cur.lastpos())
953 if (!front && cur.pos() == 0)
955 Inset * inset = front ? cur.nextInset() : cur.prevInset();
956 if (!isHighlyEditableInset(inset))
959 * Apparently, when entering an inset we are expected to be positioned
960 * *before* it in the containing paragraph, regardless of the direction
961 * from which we are entering. Otherwise, cursor placement goes awry,
962 * and when we exit from the beginning, we'll be placed *after* the
967 inset->edit(cur, front);
972 bool Text::cursorLeft(Cursor & cur)
974 // Tell BufferView to test for FitCursor in any case!
975 cur.updateFlags(Update::FitCursor);
977 // not at paragraph start?
979 // if on right side of boundary (i.e. not at paragraph end, but line end)
980 // -> skip it, i.e. set boundary to true, i.e. go only logically left
981 // there are some exceptions to ignore this: lineseps, newlines, spaces
983 // some effectless debug code to see the values in the debugger
984 bool bound = cur.boundary();
985 int rowpos = cur.textRow().pos();
987 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
988 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
989 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
991 if (!cur.boundary() &&
992 cur.textRow().pos() == cur.pos() &&
993 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
994 !cur.paragraph().isNewline(cur.pos() - 1) &&
995 !cur.paragraph().isSeparator(cur.pos() - 1)) {
996 return setCursor(cur, cur.pit(), cur.pos(), true, true);
999 // go left and try to enter inset
1000 if (checkAndActivateInset(cur, false))
1003 // normal character left
1004 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1007 // move to the previous paragraph or do nothing
1009 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1014 bool Text::cursorRight(Cursor & cur)
1016 // Tell BufferView to test for FitCursor in any case!
1017 cur.updateFlags(Update::FitCursor);
1019 // not at paragraph end?
1020 if (cur.pos() != cur.lastpos()) {
1021 // in front of editable inset, i.e. jump into it?
1022 if (checkAndActivateInset(cur, true))
1025 // if left of boundary -> just jump to right side
1026 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1027 if (cur.boundary() &&
1028 !isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1029 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1031 // next position is left of boundary,
1032 // but go to next line for special cases like space, newline, linesep
1034 // some effectless debug code to see the values in the debugger
1035 int endpos = cur.textRow().endpos();
1036 int lastpos = cur.lastpos();
1037 int pos = cur.pos();
1038 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1039 bool newline = cur.paragraph().isNewline(cur.pos());
1040 bool sep = cur.paragraph().isSeparator(cur.pos());
1041 if (cur.pos() != cur.lastpos()) {
1042 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1043 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1044 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1047 if (cur.textRow().endpos() == cur.pos() + 1 &&
1048 cur.textRow().endpos() != cur.lastpos() &&
1049 !cur.paragraph().isNewline(cur.pos()) &&
1050 !cur.paragraph().isLineSeparator(cur.pos()) &&
1051 !cur.paragraph().isSeparator(cur.pos())) {
1052 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1055 // in front of RTL boundary? Stay on this side of the boundary because:
1056 // ab|cDDEEFFghi -> abc|DDEEFFghi
1057 if (isRTLBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1058 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1061 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1064 // move to next paragraph
1065 if (cur.pit() != cur.lastpit())
1066 return setCursor(cur, cur.pit() + 1, 0);
1071 bool Text::cursorUpParagraph(Cursor & cur)
1073 bool updated = false;
1075 updated = setCursor(cur, cur.pit(), 0);
1076 else if (cur.pit() != 0)
1077 updated = setCursor(cur, cur.pit() - 1, 0);
1082 bool Text::cursorDownParagraph(Cursor & cur)
1084 bool updated = false;
1085 if (cur.pit() != cur.lastpit())
1086 updated = setCursor(cur, cur.pit() + 1, 0);
1088 updated = setCursor(cur, cur.pit(), cur.lastpos());
1093 // fix the cursor `cur' after a characters has been deleted at `where'
1094 // position. Called by deleteEmptyParagraphMechanism
1095 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1097 // Do nothing if cursor is not in the paragraph where the
1098 // deletion occured,
1099 if (cur.pit() != where.pit())
1102 // If cursor position is after the deletion place update it
1103 if (cur.pos() > where.pos())
1106 // Check also if we don't want to set the cursor on a spot behind the
1107 // pagragraph because we erased the last character.
1108 if (cur.pos() > cur.lastpos())
1109 cur.pos() = cur.lastpos();
1113 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1114 Cursor & old, bool & need_anchor_change)
1116 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1118 Paragraph & oldpar = old.paragraph();
1120 // We allow all kinds of "mumbo-jumbo" when freespacing.
1121 if (oldpar.isFreeSpacing())
1124 /* Ok I'll put some comments here about what is missing.
1125 There are still some small problems that can lead to
1126 double spaces stored in the document file or space at
1127 the beginning of paragraphs(). This happens if you have
1128 the cursor between to spaces and then save. Or if you
1129 cut and paste and the selection have a space at the
1130 beginning and then save right after the paste. (Lgb)
1133 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1134 // delete the LineSeparator.
1137 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1138 // delete the LineSeparator.
1141 bool const same_inset = &old.inset() == &cur.inset();
1142 bool const same_par = same_inset && old.pit() == cur.pit();
1143 bool const same_par_pos = same_par && old.pos() == cur.pos();
1145 // If the chars around the old cursor were spaces, delete one of them.
1146 if (!same_par_pos) {
1147 // Only if the cursor has really moved.
1149 && old.pos() < oldpar.size()
1150 && oldpar.isLineSeparator(old.pos())
1151 && oldpar.isLineSeparator(old.pos() - 1)
1152 && !oldpar.isDeleted(old.pos() - 1)) {
1153 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1154 #ifdef WITH_WARNINGS
1155 #warning This will not work anymore when we have multiple views of the same buffer
1156 // In this case, we will have to correct also the cursors held by
1157 // other bufferviews. It will probably be easier to do that in a more
1158 // automated way in CursorSlice code. (JMarc 26/09/2001)
1160 // correct all cursor parts
1162 fixCursorAfterDelete(cur.top(), old.top());
1163 need_anchor_change = true;
1169 // only do our magic if we changed paragraph
1173 // don't delete anything if this is the ONLY paragraph!
1174 if (old.lastpit() == 0)
1177 // Do not delete empty paragraphs with keepempty set.
1178 if (oldpar.allowEmpty())
1181 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1183 recordUndo(old, Undo::ATOMIC,
1184 max(old.pit() - 1, pit_type(0)),
1185 min(old.pit() + 1, old.lastpit()));
1186 ParagraphList & plist = old.text()->paragraphs();
1187 plist.erase(boost::next(plist.begin(), old.pit()));
1189 // see #warning above
1190 if (cur.depth() >= old.depth()) {
1191 CursorSlice & curslice = cur[old.depth() - 1];
1192 if (&curslice.inset() == &old.inset()
1193 && curslice.pit() > old.pit()) {
1195 // since a paragraph has been deleted, all the
1196 // insets after `old' have been copied and
1197 // their address has changed. Therefore we
1198 // need to `regenerate' cur. (JMarc)
1199 cur.updateInsets(&(cur.bottom().inset()));
1200 need_anchor_change = true;
1206 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1207 need_anchor_change = true;
1208 // We return true here because the Paragraph contents changed and
1209 // we need a redraw before further action is processed.
1217 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1219 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1221 for (pit_type pit = first; pit <= last; ++pit) {
1222 Paragraph & par = pars_[pit];
1224 // We allow all kinds of "mumbo-jumbo" when freespacing.
1225 if (par.isFreeSpacing())
1228 for (pos_type pos = 1; pos < par.size(); ++pos) {
1229 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1230 && !par.isDeleted(pos - 1)) {
1231 if (par.eraseChar(pos - 1, trackChanges)) {
1237 // don't delete anything if this is the only remaining paragraph within the given range
1238 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1242 // don't delete empty paragraphs with keepempty set
1243 if (par.allowEmpty())
1246 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1247 pars_.erase(boost::next(pars_.begin(), pit));
1253 par.stripLeadingSpaces(trackChanges);
1258 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1260 recordUndo(cur, Undo::ATOMIC, first, last);
1264 void Text::recUndo(Cursor & cur, pit_type par) const
1266 recordUndo(cur, Undo::ATOMIC, par, par);