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)
273 BOOST_ASSERT(!pars_[pit].isInset(pos) ||
274 !pars_[pit].getInset(pos)->noFontChange());
277 Layout_ptr const & layout = pars_[pit].layout();
279 // Get concrete layout font to reduce against
282 if (pos < pars_[pit].beginOfBody())
283 layoutfont = layout->labelfont;
285 layoutfont = layout->font;
287 // Realize against environment font information
288 if (pars_[pit].getDepth()) {
290 while (!layoutfont.resolved() &&
291 tp != pit_type(paragraphs().size()) &&
292 pars_[tp].getDepth()) {
293 tp = outerHook(tp, paragraphs());
294 if (tp != pit_type(paragraphs().size()))
295 layoutfont.realize(pars_[tp].layout()->font);
299 // Inside inset, apply the inset's font attributes if any
301 if (!isMainText(buffer))
302 layoutfont.realize(font_);
304 layoutfont.realize(buffer.params().getFont());
306 // Now, reduce font against full layout font
307 font.reduce(layoutfont);
309 pars_[pit].setFont(pos, font);
313 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
314 pos_type pos, Font const & font, bool toggleall)
316 BOOST_ASSERT(pars_[pit].isInset(pos) &&
317 pars_[pit].getInset(pos)->noFontChange());
319 Inset * const inset = pars_[pit].getInset(pos);
320 DocIterator dit = doc_iterator_begin(*inset);
321 // start of the last cell
322 DocIterator end = dit;
323 end.idx() = end.lastidx();
326 Text * text = dit.text();
327 Inset * cell = dit.realInset();
329 DocIterator cellbegin = doc_iterator_begin(*cell);
330 // last position of the cell
331 DocIterator cellend = cellbegin;
332 cellend.pit() = cellend.lastpit();
333 cellend.pos() = cellend.lastpos();
334 text->setFont(buffer, cellbegin, cellend, font, toggleall);
343 // return past-the-last paragraph influenced by a layout change on pit
344 pit_type Text::undoSpan(pit_type pit)
346 pit_type end = paragraphs().size();
347 pit_type nextpit = pit + 1;
350 //because of parindents
351 if (!pars_[pit].getDepth())
352 return boost::next(nextpit);
353 //because of depth constrains
354 for (; nextpit != end; ++pit, ++nextpit) {
355 if (!pars_[pit].getDepth())
362 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
363 string const & layout)
365 BOOST_ASSERT(start != end);
367 BufferParams const & bufparams = buffer.params();
368 Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
370 for (pit_type pit = start; pit != end; ++pit) {
371 Paragraph & par = pars_[pit];
372 par.applyLayout(lyxlayout);
373 if (lyxlayout->margintype == MARGIN_MANUAL)
374 par.setLabelWidthString(par.translateIfPossible(
375 lyxlayout->labelstring(), buffer.params()));
380 // set layout over selection and make a total rebreak of those paragraphs
381 void Text::setLayout(Cursor & cur, string const & layout)
383 BOOST_ASSERT(this == cur.text());
384 // special handling of new environment insets
385 BufferView & bv = cur.bv();
386 BufferParams const & params = bv.buffer()->params();
387 Layout_ptr const & lyxlayout = params.getTextClass()[layout];
388 if (lyxlayout->is_environment) {
389 // move everything in a new environment inset
390 LYXERR(Debug::DEBUG) << "setting layout " << layout << endl;
391 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
392 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
393 lyx::dispatch(FuncRequest(LFUN_CUT));
394 Inset * inset = new InsetEnvironment(params, layout);
395 insertInset(cur, inset);
396 //inset->edit(cur, true);
397 //lyx::dispatch(FuncRequest(LFUN_PASTE));
401 pit_type start = cur.selBegin().pit();
402 pit_type end = cur.selEnd().pit() + 1;
403 pit_type undopit = undoSpan(end - 1);
404 recUndo(cur, start, undopit - 1);
405 setLayout(cur.buffer(), start, end, layout);
406 updateLabels(cur.buffer());
410 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
411 Paragraph const & par, int max_depth)
413 if (par.layout()->labeltype == LABEL_BIBLIO)
415 int const depth = par.params().depth();
416 if (type == Text::INC_DEPTH && depth < max_depth)
418 if (type == Text::DEC_DEPTH && depth > 0)
424 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
426 BOOST_ASSERT(this == cur.text());
427 // this happens when selecting several cells in tabular (bug 2630)
428 if (cur.selBegin().idx() != cur.selEnd().idx())
431 pit_type const beg = cur.selBegin().pit();
432 pit_type const end = cur.selEnd().pit() + 1;
433 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
435 for (pit_type pit = beg; pit != end; ++pit) {
436 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
438 max_depth = pars_[pit].getMaxDepthAfter();
444 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
446 BOOST_ASSERT(this == cur.text());
447 pit_type const beg = cur.selBegin().pit();
448 pit_type const end = cur.selEnd().pit() + 1;
449 recordUndoSelection(cur);
450 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
452 for (pit_type pit = beg; pit != end; ++pit) {
453 Paragraph & par = pars_[pit];
454 if (lyx::changeDepthAllowed(type, par, max_depth)) {
455 int const depth = par.params().depth();
456 if (type == INC_DEPTH)
457 par.params().depth(depth + 1);
459 par.params().depth(depth - 1);
461 max_depth = par.getMaxDepthAfter();
463 // this handles the counter labels, and also fixes up
464 // depth values for follow-on (child) paragraphs
465 updateLabels(cur.buffer());
469 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
471 BOOST_ASSERT(this == cur.text());
472 // Set the current_font
473 // Determine basis font
475 pit_type pit = cur.pit();
476 if (cur.pos() < pars_[pit].beginOfBody())
477 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
479 layoutfont = getLayoutFont(cur.buffer(), pit);
481 // Update current font
482 real_current_font.update(font,
483 cur.buffer().params().language,
486 // Reduce to implicit settings
487 current_font = real_current_font;
488 current_font.reduce(layoutfont);
489 // And resolve it completely
490 real_current_font.realize(layoutfont);
492 // if there is no selection that's all we need to do
493 if (!cur.selection())
496 // Ok, we have a selection.
497 recordUndoSelection(cur);
499 setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
504 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
505 DocIterator const & end, Font const & font,
508 // Don't use forwardChar here as ditend might have
509 // pos() == lastpos() and forwardChar would miss it.
510 // Can't use forwardPos either as this descends into
512 Language const * language = buffer.params().language;
513 for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
514 if (dit.pos() != dit.lastpos()) {
515 pit_type const pit = dit.pit();
516 pos_type const pos = dit.pos();
517 if (pars_[pit].isInset(pos) &&
518 pars_[pit].getInset(pos)->noFontChange())
519 // We need to propagate the font change to all
520 // text cells of the inset (bug 1973).
521 // FIXME: This should change, see documentation
522 // of noFontChange in insetbase.h
523 setInsetFont(buffer, pit, pos, font, toggleall);
525 Font f = getFont(buffer, dit.paragraph(), pos);
526 f.update(font, language, toggleall);
527 setCharFont(buffer, pit, pos, f);
534 // the cursor set functions have a special mechanism. When they
535 // realize you left an empty paragraph, they will delete it.
537 bool Text::cursorHome(Cursor & cur)
539 BOOST_ASSERT(this == cur.text());
540 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
541 Row const & row = pm.getRow(cur.pos(),cur.boundary());
542 return setCursor(cur, cur.pit(), row.pos());
546 bool Text::cursorEnd(Cursor & cur)
548 BOOST_ASSERT(this == cur.text());
549 // if not on the last row of the par, put the cursor before
550 // the final space exept if I have a spanning inset or one string
551 // is so long that we force a break.
552 pos_type end = cur.textRow().endpos();
554 // empty text, end-1 is no valid position
556 bool boundary = false;
557 if (end != cur.lastpos()) {
558 if (!cur.paragraph().isLineSeparator(end-1)
559 && !cur.paragraph().isNewline(end-1))
564 return setCursor(cur, cur.pit(), end, true, boundary);
568 bool Text::cursorTop(Cursor & cur)
570 BOOST_ASSERT(this == cur.text());
571 return setCursor(cur, 0, 0);
575 bool Text::cursorBottom(Cursor & cur)
577 BOOST_ASSERT(this == cur.text());
578 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
582 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
584 BOOST_ASSERT(this == cur.text());
585 // If the mask is completely neutral, tell user
586 if (font == Font(Font::ALL_IGNORE)) {
587 // Could only happen with user style
588 cur.message(_("No font change defined."));
592 // Try implicit word selection
593 // If there is a change in the language the implicit word selection
595 CursorSlice resetCursor = cur.top();
596 bool implicitSelection =
597 font.language() == ignore_language
598 && font.number() == Font::IGNORE
599 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
602 setFont(cur, font, toggleall);
604 // Implicit selections are cleared afterwards
605 // and cursor is set to the original position.
606 if (implicitSelection) {
607 cur.clearSelection();
608 cur.top() = resetCursor;
614 docstring Text::getStringToIndex(Cursor const & cur)
616 BOOST_ASSERT(this == cur.text());
620 idxstring = cur.selectionAsString(false);
622 // Try implicit word selection. If there is a change
623 // in the language the implicit word selection is
626 selectWord(tmpcur, PREVIOUS_WORD);
628 if (!tmpcur.selection())
629 cur.message(_("Nothing to index!"));
630 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
631 cur.message(_("Cannot index more than one paragraph!"));
633 idxstring = tmpcur.selectionAsString(false);
640 void Text::setParagraph(Cursor & cur,
641 Spacing const & spacing, LyXAlignment align,
642 docstring const & labelwidthstring, bool noindent)
644 BOOST_ASSERT(cur.text());
645 // make sure that the depth behind the selection are restored, too
646 pit_type undopit = undoSpan(cur.selEnd().pit());
647 recUndo(cur, cur.selBegin().pit(), undopit - 1);
649 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
651 Paragraph & par = pars_[pit];
652 ParagraphParameters & params = par.params();
653 params.spacing(spacing);
655 // does the layout allow the new alignment?
656 Layout_ptr const & layout = par.layout();
658 if (align == LYX_ALIGN_LAYOUT)
659 align = layout->align;
660 if (align & layout->alignpossible) {
661 if (align == layout->align)
662 params.align(LYX_ALIGN_LAYOUT);
666 par.setLabelWidthString(labelwidthstring);
667 params.noindent(noindent);
672 // this really should just insert the inset and not move the cursor.
673 void Text::insertInset(Cursor & cur, Inset * inset)
675 BOOST_ASSERT(this == cur.text());
677 cur.paragraph().insertInset(cur.pos(), inset,
678 Change(cur.buffer().params().trackChanges ?
679 Change::INSERTED : Change::UNCHANGED));
683 // needed to insert the selection
684 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
686 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
687 current_font, str, autoBreakRows_);
691 // turn double CR to single CR, others are converted into one
692 // blank. Then insertStringAsLines is called
693 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
695 docstring linestr = str;
696 bool newline_inserted = false;
698 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
699 if (linestr[i] == '\n') {
700 if (newline_inserted) {
701 // we know that \r will be ignored by
702 // insertStringAsLines. Of course, it is a dirty
703 // trick, but it works...
704 linestr[i - 1] = '\r';
708 newline_inserted = true;
710 } else if (isPrintable(linestr[i])) {
711 newline_inserted = false;
714 insertStringAsLines(cur, linestr);
718 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
719 bool setfont, bool boundary)
722 setCursorIntern(cur, par, pos, setfont, boundary);
723 return cur.bv().checkDepm(cur, old);
727 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
729 BOOST_ASSERT(par != int(paragraphs().size()));
733 // now some strict checking
734 Paragraph & para = getPar(par);
736 // None of these should happen, but we're scaredy-cats
738 lyxerr << "dont like -1" << endl;
742 if (pos > para.size()) {
743 lyxerr << "dont like 1, pos: " << pos
744 << " size: " << para.size()
745 << " par: " << par << endl;
751 void Text::setCursorIntern(Cursor & cur,
752 pit_type par, pos_type pos, bool setfont, bool boundary)
754 BOOST_ASSERT(this == cur.text());
755 cur.boundary(boundary);
756 setCursor(cur.top(), par, pos);
762 void Text::setCurrentFont(Cursor & cur)
764 BOOST_ASSERT(this == cur.text());
765 pos_type pos = cur.pos();
766 Paragraph & par = cur.paragraph();
768 if (cur.boundary() && pos > 0 && pos < cur.lastpos()) {
770 // We may have just moved to the previous row ---
771 // we're going to be needing its bidi tables!
772 bidi.computeTables(par, cur.buffer(), cur.textRow());
776 if (pos == cur.lastpos())
778 else // potentional bug... BUG (Lgb)
779 if (par.isSeparator(pos)) {
780 if (pos > cur.textRow().pos() &&
781 bidi.level(pos) % 2 ==
782 bidi.level(pos - 1) % 2)
784 else if (pos + 1 < cur.lastpos())
789 BufferParams const & bufparams = cur.buffer().params();
790 current_font = par.getFontSettings(bufparams, pos);
791 real_current_font = getFont(cur.buffer(), par, pos);
793 if (cur.pos() == cur.lastpos()
794 && bidi.isBoundary(cur.buffer(), par, cur.pos())
795 && !cur.boundary()) {
796 Language const * lang = par.getParLanguage(bufparams);
797 current_font.setLanguage(lang);
798 current_font.setNumber(Font::OFF);
799 real_current_font.setLanguage(lang);
800 real_current_font.setNumber(Font::OFF);
804 // y is screen coordinate
805 pit_type Text::getPitNearY(BufferView & bv, int y) const
807 BOOST_ASSERT(!paragraphs().empty());
808 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
809 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
811 << BOOST_CURRENT_FUNCTION
812 << ": y: " << y << " cache size: " << cc.size()
815 // look for highest numbered paragraph with y coordinate less than given y
818 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
819 CoordCache::InnerParPosCache::const_iterator et = cc.end();
820 CoordCache::InnerParPosCache::const_iterator last = et; last--;
822 TextMetrics & tm = bv.textMetrics(this);
823 ParagraphMetrics const & pm = tm.parMetrics(it->first);
825 // If we are off-screen (before the visible part)
827 // and even before the first paragraph in the cache.
828 && y < it->second.y_ - int(pm.ascent())) {
829 // and we are not at the first paragraph in the inset.
832 // then this is the paragraph we are looking for.
834 // rebreak it and update the CoordCache.
835 tm.redoParagraph(pit);
836 bv.coordCache().parPos()[this][pit] =
837 Point(0, it->second.y_ - pm.descent());
841 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
843 // If we are off-screen (after the visible part)
844 if (y > bv.workHeight()
845 // and even after the first paragraph in the cache.
846 && y >= last->second.y_ + int(pm_last.descent())) {
847 pit = last->first + 1;
848 // and we are not at the last paragraph in the inset.
849 if (pit == int(pars_.size()))
851 // then this is the paragraph we are looking for.
852 // rebreak it and update the CoordCache.
853 tm.redoParagraph(pit);
854 bv.coordCache().parPos()[this][pit] =
855 Point(0, last->second.y_ + pm_last.ascent());
859 for (; it != et; ++it) {
861 << BOOST_CURRENT_FUNCTION
862 << " examining: pit: " << it->first
863 << " y: " << it->second.y_
866 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
868 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
875 << BOOST_CURRENT_FUNCTION
876 << ": found best y: " << yy << " for pit: " << pit
883 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
885 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
887 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
888 BOOST_ASSERT(!pm.rows().empty());
889 RowList::const_iterator rit = pm.rows().begin();
890 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
891 for (; rit != rlast; yy += rit->height(), ++rit)
892 if (yy + rit->height() > y)
898 // x,y are absolute screen coordinates
899 // sets cursor recursively descending into nested editable insets
900 Inset * Text::editXY(Cursor & cur, int x, int y)
902 if (lyxerr.debugging(Debug::WORKAREA)) {
903 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
904 cur.bv().coordCache().dump();
906 pit_type pit = getPitNearY(cur.bv(), y);
907 BOOST_ASSERT(pit != -1);
909 Row const & row = getRowNearY(cur.bv(), y, pit);
912 TextMetrics const & tm = cur.bv().textMetrics(this);
913 int xx = x; // is modified by getColumnNearX
914 pos_type const pos = row.pos()
915 + tm.getColumnNearX(pit, row, xx, bound);
921 // try to descend into nested insets
922 Inset * inset = checkInsetHit(cur.bv(), x, y);
923 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
925 // Either we deconst editXY or better we move current_font
926 // and real_current_font to Cursor
931 Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
932 //Inset * insetBehind = pars_[pit].getInset(pos);
934 // This should be just before or just behind the
935 // cursor position set above.
936 BOOST_ASSERT((pos != 0 && inset == insetBefore)
937 || inset == pars_[pit].getInset(pos));
939 // Make sure the cursor points to the position before
941 if (inset == insetBefore)
944 // Try to descend recursively inside the inset.
945 inset = inset->editXY(cur, x, y);
947 if (cur.top().text() == this)
953 bool Text::checkAndActivateInset(Cursor & cur, bool front)
957 if (front && cur.pos() == cur.lastpos())
959 if (!front && cur.pos() == 0)
961 Inset * inset = front ? cur.nextInset() : cur.prevInset();
962 if (!isHighlyEditableInset(inset))
965 * Apparently, when entering an inset we are expected to be positioned
966 * *before* it in the containing paragraph, regardless of the direction
967 * from which we are entering. Otherwise, cursor placement goes awry,
968 * and when we exit from the beginning, we'll be placed *after* the
973 inset->edit(cur, front);
978 bool Text::cursorLeft(Cursor & cur)
980 // Tell BufferView to test for FitCursor in any case!
981 cur.updateFlags(Update::FitCursor);
985 return setCursor(cur, cur.pit(), cur.pos(), true, false);
987 bool updateNeeded = false;
988 // If checkAndActivateInset returns true, that means that
989 // the cursor was placed inside it, so we're done
990 if (!checkAndActivateInset(cur, false)) {
991 if (!cur.boundary() &&
992 cur.textRow().pos() == cur.pos()
993 // FIXME: the following two conditions are copied
994 // from cursorRight; however, isLineSeparator()
995 // is definitely wrong here, isNewline I'm not sure
996 // about. I'm leaving them as comments for now,
997 // until we understand why they should or shouldn't
1000 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1001 !cur.paragraph().isNewline(cur.pos() - 1)*/) {
1002 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(),
1005 updateNeeded |= setCursor(cur, cur.pit(),cur.pos() - 1,
1008 return updateNeeded;
1011 if (cur.pit() > 0) {
1012 // Steps into the paragraph above
1013 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1019 bool Text::cursorRight(Cursor & cur)
1021 // Tell BufferView to test for FitCursor in any case!
1022 cur.updateFlags(Update::FitCursor);
1024 if (cur.pos() != cur.lastpos()) {
1026 return setCursor(cur, cur.pit(), cur.pos(),
1029 bool updateNeeded = false;
1030 // If checkAndActivateInset returns true, that means that
1031 // the cursor was placed inside it, so we're done
1032 if (!checkAndActivateInset(cur, true)) {
1033 if (cur.textRow().endpos() == cur.pos() + 1 &&
1034 cur.textRow().endpos() != cur.lastpos() &&
1035 !cur.paragraph().isLineSeparator(cur.pos()) &&
1036 !cur.paragraph().isNewline(cur.pos())) {
1039 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1041 return updateNeeded;
1044 if (cur.pit() != cur.lastpit())
1045 return setCursor(cur, cur.pit() + 1, 0);
1050 bool Text::cursorUp(Cursor & cur)
1052 // Tell BufferView to test for FitCursor in any case!
1053 cur.updateFlags(Update::FitCursor);
1055 TextMetrics const & tm = cur.bv().textMetrics(this);
1056 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1059 if (cur.pos() && cur.boundary())
1060 row = pm.pos2row(cur.pos()-1);
1062 row = pm.pos2row(cur.pos());
1064 int x = cur.targetX();
1066 // We want to keep the x-target on subsequent up movements
1067 // that cross beyond the end of short lines. Thus a special
1068 // handling when the cursor is at the end of line: Use the new
1069 // x-target only if the old one was before the end of line.
1070 if (cur.pos() != pm.rows()[row].endpos()
1071 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1072 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1077 if (!cur.selection()) {
1078 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1080 // Go to middle of previous row. 16 found to work OK;
1081 // 12 = top/bottom margin of display math
1082 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1083 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1084 cur.clearSelection();
1086 // This happens when you move out of an inset.
1087 // And to give the DEPM the possibility of doing
1088 // something we must provide it with two different
1094 cur.bv().checkDepm(dummy, old);
1098 bool updateNeeded = false;
1101 updateNeeded |= setCursor(cur, cur.pit(),
1102 tm.x2pos(cur.pit(), row - 1, x));
1103 } else if (cur.pit() > 0) {
1105 //cannot use 'par' now
1106 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1107 updateNeeded |= setCursor(cur, cur.pit(),
1108 tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1113 return updateNeeded;
1117 bool Text::cursorDown(Cursor & cur)
1119 // Tell BufferView to test for FitCursor in any case!
1120 cur.updateFlags(Update::FitCursor);
1122 TextMetrics const & tm = cur.bv().textMetrics(this);
1123 ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1126 if (cur.pos() && cur.boundary())
1127 row = pm.pos2row(cur.pos()-1);
1129 row = pm.pos2row(cur.pos());
1131 int x = cur.targetX();
1133 // We want to keep the x-target on subsequent down movements
1134 // that cross beyond the end of short lines. Thus a special
1135 // handling when the cursor is at the end of line: Use the new
1136 // x-target only if the old one was before the end of line.
1137 if (cur.pos() != pm.rows()[row].endpos()
1138 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1139 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1144 if (!cur.selection()) {
1145 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1147 // To middle of next row
1148 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1149 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1150 cur.clearSelection();
1152 // This happens when you move out of an inset.
1153 // And to give the DEPM the possibility of doing
1154 // something we must provide it with two different
1160 bool const changed = cur.bv().checkDepm(dummy, old);
1162 // Make sure that cur gets back whatever happened to dummy(Lgb)
1169 bool updateNeeded = false;
1171 if (row + 1 < int(pm.rows().size())) {
1172 updateNeeded |= setCursor(cur, cur.pit(),
1173 tm.x2pos(cur.pit(), row + 1, x));
1174 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1176 updateNeeded |= setCursor(cur, cur.pit(),
1177 tm.x2pos(cur.pit(), 0, x));
1182 return updateNeeded;
1186 bool Text::cursorUpParagraph(Cursor & cur)
1188 bool updated = false;
1190 updated = setCursor(cur, cur.pit(), 0);
1191 else if (cur.pit() != 0)
1192 updated = setCursor(cur, cur.pit() - 1, 0);
1197 bool Text::cursorDownParagraph(Cursor & cur)
1199 bool updated = false;
1200 if (cur.pit() != cur.lastpit())
1201 updated = setCursor(cur, cur.pit() + 1, 0);
1203 updated = setCursor(cur, cur.pit(), cur.lastpos());
1208 // fix the cursor `cur' after a characters has been deleted at `where'
1209 // position. Called by deleteEmptyParagraphMechanism
1210 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1212 // Do nothing if cursor is not in the paragraph where the
1213 // deletion occured,
1214 if (cur.pit() != where.pit())
1217 // If cursor position is after the deletion place update it
1218 if (cur.pos() > where.pos())
1221 // Check also if we don't want to set the cursor on a spot behind the
1222 // pagragraph because we erased the last character.
1223 if (cur.pos() > cur.lastpos())
1224 cur.pos() = cur.lastpos();
1228 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1229 Cursor & old, bool & need_anchor_change)
1231 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1233 Paragraph & oldpar = old.paragraph();
1235 // We allow all kinds of "mumbo-jumbo" when freespacing.
1236 if (oldpar.isFreeSpacing())
1239 /* Ok I'll put some comments here about what is missing.
1240 There are still some small problems that can lead to
1241 double spaces stored in the document file or space at
1242 the beginning of paragraphs(). This happens if you have
1243 the cursor between to spaces and then save. Or if you
1244 cut and paste and the selection have a space at the
1245 beginning and then save right after the paste. (Lgb)
1248 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1249 // delete the LineSeparator.
1252 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1253 // delete the LineSeparator.
1256 bool const same_inset = &old.inset() == &cur.inset();
1257 bool const same_par = same_inset && old.pit() == cur.pit();
1258 bool const same_par_pos = same_par && old.pos() == cur.pos();
1260 // If the chars around the old cursor were spaces, delete one of them.
1261 if (!same_par_pos) {
1262 // Only if the cursor has really moved.
1264 && old.pos() < oldpar.size()
1265 && oldpar.isLineSeparator(old.pos())
1266 && oldpar.isLineSeparator(old.pos() - 1)
1267 && !oldpar.isDeleted(old.pos() - 1)) {
1268 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1269 #ifdef WITH_WARNINGS
1270 #warning This will not work anymore when we have multiple views of the same buffer
1271 // In this case, we will have to correct also the cursors held by
1272 // other bufferviews. It will probably be easier to do that in a more
1273 // automated way in CursorSlice code. (JMarc 26/09/2001)
1275 // correct all cursor parts
1277 fixCursorAfterDelete(cur.top(), old.top());
1278 need_anchor_change = true;
1284 // only do our magic if we changed paragraph
1288 // don't delete anything if this is the ONLY paragraph!
1289 if (old.lastpit() == 0)
1292 // Do not delete empty paragraphs with keepempty set.
1293 if (oldpar.allowEmpty())
1296 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1298 recordUndo(old, Undo::ATOMIC,
1299 max(old.pit() - 1, pit_type(0)),
1300 min(old.pit() + 1, old.lastpit()));
1301 ParagraphList & plist = old.text()->paragraphs();
1302 plist.erase(boost::next(plist.begin(), old.pit()));
1304 // see #warning above
1305 if (cur.depth() >= old.depth()) {
1306 CursorSlice & curslice = cur[old.depth() - 1];
1307 if (&curslice.inset() == &old.inset()
1308 && curslice.pit() > old.pit()) {
1310 // since a paragraph has been deleted, all the
1311 // insets after `old' have been copied and
1312 // their address has changed. Therefore we
1313 // need to `regenerate' cur. (JMarc)
1314 cur.updateInsets(&(cur.bottom().inset()));
1315 need_anchor_change = true;
1321 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1322 need_anchor_change = true;
1323 // We return true here because the Paragraph contents changed and
1324 // we need a redraw before further action is processed.
1332 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1334 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1336 for (pit_type pit = first; pit <= last; ++pit) {
1337 Paragraph & par = pars_[pit];
1339 // We allow all kinds of "mumbo-jumbo" when freespacing.
1340 if (par.isFreeSpacing())
1343 for (pos_type pos = 1; pos < par.size(); ++pos) {
1344 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1345 && !par.isDeleted(pos - 1)) {
1346 if (par.eraseChar(pos - 1, trackChanges)) {
1352 // don't delete anything if this is the only remaining paragraph within the given range
1353 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1357 // don't delete empty paragraphs with keepempty set
1358 if (par.allowEmpty())
1361 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1362 pars_.erase(boost::next(pars_.begin(), pit));
1368 par.stripLeadingSpaces(trackChanges);
1373 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1375 recordUndo(cur, Undo::ATOMIC, first, last);
1379 void Text::recUndo(Cursor & cur, pit_type par) const
1381 recordUndo(cur, Undo::ATOMIC, par, par);