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"
30 #include "coordcache.h"
32 #include "CutAndPaste.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
36 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "pariterator.h"
48 #include "lyxserver.h"
49 #include "lyxsocket.h"
53 #include "frontends/FontMetrics.h"
55 #include "insets/insetenv.h"
57 #include "mathed/InsetMathHull.h"
59 #include "support/textutils.h"
61 #include <boost/current_function.hpp>
70 using std::ostringstream;
76 LyXText::LyXText(BufferView * bv)
77 : maxwidth_(bv ? bv->workWidth() : 100),
78 current_font(LyXFont::ALL_INHERIT),
79 background_color_(LColor::background),
85 void LyXText::init(BufferView * bv)
89 maxwidth_ = bv->workWidth();
94 pit_type const end = paragraphs().size();
95 for (pit_type pit = 0; pit != end; ++pit)
96 pars_[pit].rows().clear();
98 updateLabels(*bv->buffer());
102 bool LyXText::isMainText() const
104 return &bv()->buffer()->text() == this;
108 //takes screen x,y coordinates
109 InsetBase * LyXText::checkInsetHit(int x, int y) const
111 pit_type pit = getPitNearY(y);
112 BOOST_ASSERT(pit != -1);
114 Paragraph const & par = pars_[pit];
117 << BOOST_CURRENT_FUNCTION
122 InsetList::const_iterator iit = par.insetlist.begin();
123 InsetList::const_iterator iend = par.insetlist.end();
124 for (; iit != iend; ++iit) {
125 InsetBase * inset = iit->inset;
128 << BOOST_CURRENT_FUNCTION
129 << ": examining inset " << inset << endl;
131 if (bv()->coordCache().getInsets().has(inset))
133 << BOOST_CURRENT_FUNCTION
134 << ": xo: " << inset->xo(*bv()) << "..."
135 << inset->xo(*bv()) + inset->width()
136 << " yo: " << inset->yo(*bv()) - inset->ascent()
138 << inset->yo(*bv()) + inset->descent()
142 << BOOST_CURRENT_FUNCTION
143 << ": inset has no cached position" << endl;
145 if (inset->covers(*bv(), x, y)) {
147 << BOOST_CURRENT_FUNCTION
148 << ": Hit inset: " << inset << endl;
153 << BOOST_CURRENT_FUNCTION
154 << ": No inset hit. " << endl;
160 // Gets the fully instantiated font at a given position in a paragraph
161 // Basically the same routine as Paragraph::getFont() in paragraph.C.
162 // The difference is that this one is used for displaying, and thus we
163 // are allowed to make cosmetic improvements. For instance make footnotes
165 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
167 BOOST_ASSERT(pos >= 0);
169 LyXLayout_ptr const & layout = par.layout();
173 BufferParams const & params = bv()->buffer()->params();
174 pos_type const body_pos = par.beginOfBody();
176 // We specialize the 95% common case:
177 if (!par.getDepth()) {
178 LyXFont f = par.getFontSettings(params, pos);
183 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
184 lf = layout->labelfont;
185 rlf = layout->reslabelfont;
188 rlf = layout->resfont;
190 // In case the default family has been customized
191 if (lf.family() == LyXFont::INHERIT_FAMILY)
192 rlf.setFamily(params.getFont().family());
193 return f.realize(rlf);
196 // The uncommon case need not be optimized as much
199 layoutfont = layout->labelfont;
201 layoutfont = layout->font;
203 LyXFont font = par.getFontSettings(params, pos);
204 font.realize(layoutfont);
207 applyOuterFont(font);
209 // Find the pit value belonging to paragraph. This will not break
210 // even if pars_ would not be a vector anymore.
211 // Performance appears acceptable.
213 pit_type pit = pars_.size();
214 for (pit_type it = 0; it < pit; ++it)
215 if (&pars_[it] == &par) {
219 // Realize against environment font information
220 // NOTE: the cast to pit_type should be removed when pit_type
221 // changes to a unsigned integer.
222 if (pit < pit_type(pars_.size()))
223 font.realize(outerFont(pit, pars_));
225 // Realize with the fonts of lesser depth.
226 font.realize(params.getFont());
231 // There are currently two font mechanisms in LyX:
232 // 1. The font attributes in a lyxtext, and
233 // 2. The inset-specific font properties, defined in an inset's
234 // metrics() and draw() methods and handed down the inset chain through
235 // the pi/mi parameters, and stored locally in a lyxtext in font_.
236 // This is where the two are integrated in the final fully realized
238 void LyXText::applyOuterFont(LyXFont & font) const {
240 lf.reduce(bv()->buffer()->params().getFont());
242 lf.setLanguage(font.language());
247 LyXFont LyXText::getLayoutFont(pit_type const pit) const
249 LyXLayout_ptr const & layout = pars_[pit].layout();
251 if (!pars_[pit].getDepth()) {
252 LyXFont lf = layout->resfont;
253 // In case the default family has been customized
254 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
255 lf.setFamily(bv()->buffer()->params().getFont().family());
259 LyXFont font = layout->font;
260 // Realize with the fonts of lesser depth.
261 //font.realize(outerFont(pit, paragraphs()));
262 font.realize(bv()->buffer()->params().getFont());
268 LyXFont LyXText::getLabelFont(Paragraph const & par) const
270 LyXLayout_ptr const & layout = par.layout();
272 if (!par.getDepth()) {
273 LyXFont lf = layout->reslabelfont;
274 // In case the default family has been customized
275 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
276 lf.setFamily(bv()->buffer()->params().getFont().family());
280 LyXFont font = layout->labelfont;
281 // Realize with the fonts of lesser depth.
282 font.realize(bv()->buffer()->params().getFont());
288 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
291 LyXLayout_ptr const & layout = pars_[pit].layout();
293 // Get concrete layout font to reduce against
296 if (pos < pars_[pit].beginOfBody())
297 layoutfont = layout->labelfont;
299 layoutfont = layout->font;
301 // Realize against environment font information
302 if (pars_[pit].getDepth()) {
304 while (!layoutfont.resolved() &&
305 tp != pit_type(paragraphs().size()) &&
306 pars_[tp].getDepth()) {
307 tp = outerHook(tp, paragraphs());
308 if (tp != pit_type(paragraphs().size()))
309 layoutfont.realize(pars_[tp].layout()->font);
313 // Inside inset, apply the inset's font attributes if any
316 layoutfont.realize(font_);
318 layoutfont.realize(bv()->buffer()->params().getFont());
320 // Now, reduce font against full layout font
321 font.reduce(layoutfont);
323 pars_[pit].setFont(pos, font);
327 // return past-the-last paragraph influenced by a layout change on pit
328 pit_type LyXText::undoSpan(pit_type pit)
330 pit_type end = paragraphs().size();
331 pit_type nextpit = pit + 1;
334 //because of parindents
335 if (!pars_[pit].getDepth())
336 return boost::next(nextpit);
337 //because of depth constrains
338 for (; nextpit != end; ++pit, ++nextpit) {
339 if (!pars_[pit].getDepth())
346 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
348 BOOST_ASSERT(start != end);
350 BufferParams const & bufparams = bv()->buffer()->params();
351 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
353 for (pit_type pit = start; pit != end; ++pit) {
354 pars_[pit].applyLayout(lyxlayout);
355 if (lyxlayout->margintype == MARGIN_MANUAL)
356 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
361 // set layout over selection and make a total rebreak of those paragraphs
362 void LyXText::setLayout(LCursor & cur, string const & layout)
364 BOOST_ASSERT(this == cur.text());
365 // special handling of new environment insets
366 BufferView & bv = cur.bv();
367 BufferParams const & params = bv.buffer()->params();
368 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
369 if (lyxlayout->is_environment) {
370 // move everything in a new environment inset
371 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
372 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
373 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
374 lyx::dispatch(FuncRequest(LFUN_CUT));
375 InsetBase * inset = new InsetEnvironment(params, layout);
376 insertInset(cur, inset);
377 //inset->edit(cur, true);
378 //lyx::dispatch(FuncRequest(LFUN_PASTE));
382 pit_type start = cur.selBegin().pit();
383 pit_type end = cur.selEnd().pit() + 1;
384 pit_type undopit = undoSpan(end - 1);
385 recUndo(start, undopit - 1);
386 setLayout(start, end, layout);
387 updateLabels(cur.buffer());
394 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
395 Paragraph const & par, int max_depth)
397 if (par.layout()->labeltype == LABEL_BIBLIO)
399 int const depth = par.params().depth();
400 if (type == LyXText::INC_DEPTH && depth < max_depth)
402 if (type == LyXText::DEC_DEPTH && depth > 0)
411 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
413 BOOST_ASSERT(this == cur.text());
414 // this happens when selecting several cells in tabular (bug 2630)
415 if (cur.selBegin().idx() != cur.selEnd().idx())
418 pit_type const beg = cur.selBegin().pit();
419 pit_type const end = cur.selEnd().pit() + 1;
420 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
422 for (pit_type pit = beg; pit != end; ++pit) {
423 if (::changeDepthAllowed(type, pars_[pit], max_depth))
425 max_depth = pars_[pit].getMaxDepthAfter();
431 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
433 BOOST_ASSERT(this == cur.text());
434 pit_type const beg = cur.selBegin().pit();
435 pit_type const end = cur.selEnd().pit() + 1;
436 recordUndoSelection(cur);
437 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
439 for (pit_type pit = beg; pit != end; ++pit) {
440 Paragraph & par = pars_[pit];
441 if (::changeDepthAllowed(type, par, max_depth)) {
442 int const depth = par.params().depth();
443 if (type == INC_DEPTH)
444 par.params().depth(depth + 1);
446 par.params().depth(depth - 1);
448 max_depth = par.getMaxDepthAfter();
450 // this handles the counter labels, and also fixes up
451 // depth values for follow-on (child) paragraphs
452 updateLabels(cur.buffer());
456 // set font over selection
457 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
459 BOOST_ASSERT(this == cur.text());
460 // if there is no selection just set the current_font
461 if (!cur.selection()) {
462 // Determine basis font
464 pit_type pit = cur.pit();
465 if (cur.pos() < pars_[pit].beginOfBody())
466 layoutfont = getLabelFont(pars_[pit]);
468 layoutfont = getLayoutFont(pit);
470 // Update current font
471 real_current_font.update(font,
472 cur.buffer().params().language,
475 // Reduce to implicit settings
476 current_font = real_current_font;
477 current_font.reduce(layoutfont);
478 // And resolve it completely
479 real_current_font.realize(layoutfont);
484 // Ok, we have a selection.
485 recordUndoSelection(cur);
487 DocIterator dit = cur.selectionBegin();
488 DocIterator ditend = cur.selectionEnd();
490 BufferParams const & params = cur.buffer().params();
492 // Don't use forwardChar here as ditend might have
493 // pos() == lastpos() and forwardChar would miss it.
494 // Can't use forwardPos either as this descends into
496 for (; dit != ditend; dit.forwardPosNoDescend()) {
497 if (dit.pos() != dit.lastpos()) {
498 LyXFont f = getFont(dit.paragraph(), dit.pos());
499 f.update(font, params.language, toggleall);
500 setCharFont(dit.pit(), dit.pos(), f);
506 // the cursor set functions have a special mechanism. When they
507 // realize you left an empty paragraph, they will delete it.
509 bool LyXText::cursorHome(LCursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
514 return setCursor(cur, cur.pit(), row.pos());
518 bool LyXText::cursorEnd(LCursor & cur)
520 BOOST_ASSERT(this == cur.text());
521 // if not on the last row of the par, put the cursor before
522 // the final space exept if I have a spanning inset or one string
523 // is so long that we force a break.
524 pos_type end = cur.textRow().endpos();
526 // empty text, end-1 is no valid position
528 bool boundary = false;
529 if (end != cur.lastpos()) {
530 if (!cur.paragraph().isLineSeparator(end-1)
531 && !cur.paragraph().isNewline(end-1))
536 return setCursor(cur, cur.pit(), end, true, boundary);
540 bool LyXText::cursorTop(LCursor & cur)
542 BOOST_ASSERT(this == cur.text());
543 return setCursor(cur, 0, 0);
547 bool LyXText::cursorBottom(LCursor & cur)
549 BOOST_ASSERT(this == cur.text());
550 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
554 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
556 BOOST_ASSERT(this == cur.text());
557 // If the mask is completely neutral, tell user
558 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
559 // Could only happen with user style
560 cur.message(_("No font change defined. "
561 "Use Character under the Layout menu to define font change."));
565 // Try implicit word selection
566 // If there is a change in the language the implicit word selection
568 CursorSlice resetCursor = cur.top();
569 bool implicitSelection =
570 font.language() == ignore_language
571 && font.number() == LyXFont::IGNORE
572 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
575 setFont(cur, font, toggleall);
577 // Implicit selections are cleared afterwards
578 // and cursor is set to the original position.
579 if (implicitSelection) {
580 cur.clearSelection();
581 cur.top() = resetCursor;
587 string LyXText::getStringToIndex(LCursor const & cur)
589 BOOST_ASSERT(this == cur.text());
593 idxstring = cur.selectionAsString(false);
595 // Try implicit word selection. If there is a change
596 // in the language the implicit word selection is
598 LCursor tmpcur = cur;
599 selectWord(tmpcur, lyx::PREVIOUS_WORD);
601 if (!tmpcur.selection())
602 cur.message(_("Nothing to index!"));
603 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
604 cur.message(_("Cannot index more than one paragraph!"));
606 idxstring = tmpcur.selectionAsString(false);
609 return lyx::to_utf8(idxstring);
613 void LyXText::setParagraph(LCursor & cur,
614 Spacing const & spacing, LyXAlignment align,
615 string const & labelwidthstring, bool noindent)
617 BOOST_ASSERT(cur.text());
618 // make sure that the depth behind the selection are restored, too
619 pit_type undopit = undoSpan(cur.selEnd().pit());
620 recUndo(cur.selBegin().pit(), undopit - 1);
622 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
624 Paragraph & par = pars_[pit];
625 ParagraphParameters & params = par.params();
626 params.spacing(spacing);
628 // does the layout allow the new alignment?
629 LyXLayout_ptr const & layout = par.layout();
631 if (align == LYX_ALIGN_LAYOUT)
632 align = layout->align;
633 if (align & layout->alignpossible) {
634 if (align == layout->align)
635 params.align(LYX_ALIGN_LAYOUT);
639 par.setLabelWidthString(labelwidthstring);
640 params.noindent(noindent);
645 // this really should just insert the inset and not move the cursor.
646 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
648 BOOST_ASSERT(this == cur.text());
650 // FIXME: change tracking (MG)
651 cur.paragraph().insertInset(cur.pos(), inset, Change(Change::INSERTED));
655 // needed to insert the selection
656 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
658 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
659 current_font, str, autoBreakRows_);
663 // turn double CR to single CR, others are converted into one
664 // blank. Then insertStringAsLines is called
665 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
667 docstring linestr = str;
668 bool newline_inserted = false;
670 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
671 if (linestr[i] == '\n') {
672 if (newline_inserted) {
673 // we know that \r will be ignored by
674 // insertStringAsLines. Of course, it is a dirty
675 // trick, but it works...
676 linestr[i - 1] = '\r';
680 newline_inserted = true;
682 } else if (isPrintable(linestr[i])) {
683 newline_inserted = false;
686 insertStringAsLines(cur, linestr);
690 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
691 bool setfont, bool boundary)
694 setCursorIntern(cur, par, pos, setfont, boundary);
695 return deleteEmptyParagraphMechanism(cur, old);
699 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
701 BOOST_ASSERT(par != int(paragraphs().size()));
705 // now some strict checking
706 Paragraph & para = getPar(par);
708 // None of these should happen, but we're scaredy-cats
710 lyxerr << "dont like -1" << endl;
714 if (pos > para.size()) {
715 lyxerr << "dont like 1, pos: " << pos
716 << " size: " << para.size()
717 << " par: " << par << endl;
723 void LyXText::setCursorIntern(LCursor & cur,
724 pit_type par, pos_type pos, bool setfont, bool boundary)
726 BOOST_ASSERT(this == cur.text());
727 cur.boundary(boundary);
728 setCursor(cur.top(), par, pos);
735 void LyXText::setCurrentFont(LCursor & cur)
737 BOOST_ASSERT(this == cur.text());
738 pos_type pos = cur.pos();
739 Paragraph & par = cur.paragraph();
741 if (cur.boundary() && pos > 0)
745 if (pos == cur.lastpos())
747 else // potentional bug... BUG (Lgb)
748 if (par.isSeparator(pos)) {
749 if (pos > cur.textRow().pos() &&
750 bidi.level(pos) % 2 ==
751 bidi.level(pos - 1) % 2)
753 else if (pos + 1 < cur.lastpos())
758 BufferParams const & bufparams = cur.buffer().params();
759 current_font = par.getFontSettings(bufparams, pos);
760 real_current_font = getFont(par, pos);
762 if (cur.pos() == cur.lastpos()
763 && bidi.isBoundary(cur.buffer(), par, cur.pos())
764 && !cur.boundary()) {
765 Language const * lang = par.getParLanguage(bufparams);
766 current_font.setLanguage(lang);
767 current_font.setNumber(LyXFont::OFF);
768 real_current_font.setLanguage(lang);
769 real_current_font.setNumber(LyXFont::OFF);
774 // x is an absolute screen coord
775 // returns the column near the specified x-coordinate of the row
776 // x is set to the real beginning of this column
777 pos_type LyXText::getColumnNearX(pit_type const pit,
778 Row const & row, int & x, bool & boundary) const
780 int const xo = bv()->coordCache().get(this, pit).x_;
782 RowMetrics const r = computeRowMetrics(pit, row);
783 Paragraph const & par = pars_[pit];
785 pos_type vc = row.pos();
786 pos_type end = row.endpos();
788 LyXLayout_ptr const & layout = par.layout();
790 bool left_side = false;
792 pos_type body_pos = par.beginOfBody();
795 double last_tmpx = tmpx;
798 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
801 // check for empty row
807 lyx::frontend::FontMetrics const & fm
808 = theFontMetrics(getLabelFont(par));
810 while (vc < end && tmpx <= x) {
811 c = bidi.vis2log(vc);
813 if (body_pos > 0 && c == body_pos - 1) {
814 string lsep = layout->labelsep;
815 docstring dlsep(lsep.begin(), lsep.end());
816 tmpx += r.label_hfill + fm.width(dlsep);
817 if (par.isLineSeparator(body_pos - 1))
818 tmpx -= singleWidth(par, body_pos - 1);
821 if (hfillExpansion(par, row, c)) {
822 tmpx += singleWidth(par, c);
826 tmpx += r.label_hfill;
827 } else if (par.isSeparator(c)) {
828 tmpx += singleWidth(par, c);
832 tmpx += singleWidth(par, c);
837 if ((tmpx + last_tmpx) / 2 > x) {
842 BOOST_ASSERT(vc <= end); // This shouldn't happen.
845 // This (rtl_support test) is not needed, but gives
846 // some speedup if rtl_support == false
847 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
849 // If lastrow is false, we don't need to compute
851 bool const rtl = lastrow ? isRTL(par) : false;
853 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
854 (!rtl && !left_side && vc == end && x > tmpx + 5)))
856 else if (vc == row.pos()) {
857 c = bidi.vis2log(vc);
858 if (bidi.level(c) % 2 == 1)
861 c = bidi.vis2log(vc - 1);
862 bool const rtl = (bidi.level(c) % 2 == 1);
863 if (left_side == rtl) {
865 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
869 // I believe this code is not needed anymore (Jug 20050717)
871 // The following code is necessary because the cursor position past
872 // the last char in a row is logically equivalent to that before
873 // the first char in the next row. That's why insets causing row
874 // divisions -- Newline and display-style insets -- must be treated
875 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
876 // Newline inset, air gap below:
877 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
878 if (bidi.level(end -1) % 2 == 0)
879 tmpx -= singleWidth(par, end - 1);
881 tmpx += singleWidth(par, end - 1);
885 // Air gap above display inset:
886 if (row.pos() < end && c >= end && end < par.size()
887 && par.isInset(end) && par.getInset(end)->display()) {
890 // Air gap below display inset:
891 if (row.pos() < end && c >= end && par.isInset(end - 1)
892 && par.getInset(end - 1)->display()) {
898 pos_type const col = c - row.pos();
900 if (!c || end == par.size())
903 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
908 return min(col, end - 1 - row.pos());
912 // y is screen coordinate
913 pit_type LyXText::getPitNearY(int y) const
915 BOOST_ASSERT(!paragraphs().empty());
916 BOOST_ASSERT(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
917 CoordCache::InnerParPosCache const & cc = bv()->coordCache().getParPos().find(this)->second;
919 << BOOST_CURRENT_FUNCTION
920 << ": y: " << y << " cache size: " << cc.size()
923 // look for highest numbered paragraph with y coordinate less than given y
926 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
927 CoordCache::InnerParPosCache::const_iterator et = cc.end();
928 for (; it != et; ++it) {
930 << BOOST_CURRENT_FUNCTION
931 << " examining: pit: " << it->first
932 << " y: " << it->second.y_
935 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
942 << BOOST_CURRENT_FUNCTION
943 << ": found best y: " << yy << " for pit: " << pit
950 Row const & LyXText::getRowNearY(int y, pit_type pit) const
952 Paragraph const & par = pars_[pit];
953 int yy = bv()->coordCache().get(this, pit).y_ - par.ascent();
954 BOOST_ASSERT(!par.rows().empty());
955 RowList::const_iterator rit = par.rows().begin();
956 RowList::const_iterator const rlast = boost::prior(par.rows().end());
957 for (; rit != rlast; yy += rit->height(), ++rit)
958 if (yy + rit->height() > y)
964 // x,y are absolute screen coordinates
965 // sets cursor recursively descending into nested editable insets
966 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
968 pit_type pit = getPitNearY(y);
969 BOOST_ASSERT(pit != -1);
970 Row const & row = getRowNearY(y, pit);
973 int xx = x; // is modified by getColumnNearX
974 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
980 // try to descend into nested insets
981 InsetBase * inset = checkInsetHit(x, y);
982 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
984 // Either we deconst editXY or better we move current_font
985 // and real_current_font to LCursor
990 // This should be just before or just behind the
991 // cursor position set above.
992 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
993 || inset == pars_[pit].getInset(pos));
994 // Make sure the cursor points to the position before
996 if (inset == pars_[pit].getInset(pos - 1))
998 inset = inset->editXY(cur, x, y);
999 if (cur.top().text() == this)
1000 setCurrentFont(cur);
1005 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1007 if (cur.selection())
1009 if (cur.pos() == cur.lastpos())
1011 InsetBase * inset = cur.nextInset();
1012 if (!isHighlyEditableInset(inset))
1014 inset->edit(cur, front);
1019 bool LyXText::cursorLeft(LCursor & cur)
1021 if (!cur.boundary() && cur.pos() > 0 &&
1022 cur.textRow().pos() == cur.pos() &&
1023 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1024 !cur.paragraph().isNewline(cur.pos()-1)) {
1025 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1027 if (cur.pos() != 0) {
1028 bool boundary = cur.boundary();
1029 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1030 if (!checkAndActivateInset(cur, false)) {
1031 if (false && !boundary &&
1032 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1034 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1036 return updateNeeded;
1039 if (cur.pit() != 0) {
1040 // Steps into the paragraph above
1041 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1047 bool LyXText::cursorRight(LCursor & cur)
1049 if (cur.pos() != cur.lastpos()) {
1051 return setCursor(cur, cur.pit(), cur.pos(),
1054 bool updateNeeded = false;
1055 if (!checkAndActivateInset(cur, true)) {
1056 if (cur.textRow().endpos() == cur.pos() + 1 &&
1057 cur.textRow().endpos() != cur.lastpos() &&
1058 !cur.paragraph().isLineSeparator(cur.pos()) &&
1059 !cur.paragraph().isNewline(cur.pos())) {
1062 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1063 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1065 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1067 return updateNeeded;
1070 if (cur.pit() != cur.lastpit())
1071 return setCursor(cur, cur.pit() + 1, 0);
1076 bool LyXText::cursorUp(LCursor & cur)
1078 Paragraph const & par = cur.paragraph();
1080 int const x = cur.targetX();
1082 if (cur.pos() && cur.boundary())
1083 row = par.pos2row(cur.pos()-1);
1085 row = par.pos2row(cur.pos());
1087 if (!cur.selection()) {
1088 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1090 // Go to middle of previous row. 16 found to work OK;
1091 // 12 = top/bottom margin of display math
1092 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1093 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1094 cur.clearSelection();
1096 // This happens when you move out of an inset.
1097 // And to give the DEPM the possibility of doing
1098 // something we must provide it with two different
1100 LCursor dummy = cur;
1104 return deleteEmptyParagraphMechanism(dummy, old);
1107 bool updateNeeded = false;
1110 updateNeeded |= setCursor(cur, cur.pit(),
1111 x2pos(cur.pit(), row - 1, x));
1112 } else if (cur.pit() > 0) {
1114 //cannot use 'par' now
1115 updateNeeded |= setCursor(cur, cur.pit(),
1116 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1121 return updateNeeded;
1125 bool LyXText::cursorDown(LCursor & cur)
1127 Paragraph const & par = cur.paragraph();
1129 int const x = cur.targetX();
1131 if (cur.pos() && cur.boundary())
1132 row = par.pos2row(cur.pos()-1);
1134 row = par.pos2row(cur.pos());
1136 if (!cur.selection()) {
1137 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1139 // To middle of next row
1140 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1141 editXY(cur, x, y + par.rows()[row].descent() + margin);
1142 cur.clearSelection();
1144 // This happens when you move out of an inset.
1145 // And to give the DEPM the possibility of doing
1146 // something we must provide it with two different
1148 LCursor dummy = cur;
1152 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1154 // Make sure that cur gets back whatever happened to dummy(Lgb)
1161 bool updateNeeded = false;
1163 if (row + 1 < int(par.rows().size())) {
1164 updateNeeded |= setCursor(cur, cur.pit(),
1165 x2pos(cur.pit(), row + 1, x));
1166 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1168 updateNeeded |= setCursor(cur, cur.pit(),
1169 x2pos(cur.pit(), 0, x));
1174 return updateNeeded;
1178 bool LyXText::cursorUpParagraph(LCursor & cur)
1180 bool updated = false;
1182 updated = setCursor(cur, cur.pit(), 0);
1183 else if (cur.pit() != 0)
1184 updated = setCursor(cur, cur.pit() - 1, 0);
1189 bool LyXText::cursorDownParagraph(LCursor & cur)
1191 bool updated = false;
1192 if (cur.pit() != cur.lastpit())
1193 updated = setCursor(cur, cur.pit() + 1, 0);
1195 updated = setCursor(cur, cur.pit(), cur.lastpos());
1200 // fix the cursor `cur' after a characters has been deleted at `where'
1201 // position. Called by deleteEmptyParagraphMechanism
1202 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1204 // Do nothing if cursor is not in the paragraph where the
1205 // deletion occured,
1206 if (cur.pit() != where.pit())
1209 // If cursor position is after the deletion place update it
1210 if (cur.pos() > where.pos())
1213 // Check also if we don't want to set the cursor on a spot behind the
1214 // pagragraph because we erased the last character.
1215 if (cur.pos() > cur.lastpos())
1216 cur.pos() = cur.lastpos();
1220 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1222 // Would be wrong to delete anything if we have a selection.
1223 if (cur.selection())
1226 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1227 // old should point to us
1228 BOOST_ASSERT(old.text() == this);
1230 Paragraph & oldpar = old.paragraph();
1232 // We allow all kinds of "mumbo-jumbo" when freespacing.
1233 if (oldpar.isFreeSpacing())
1236 /* Ok I'll put some comments here about what is missing.
1237 There are still some small problems that can lead to
1238 double spaces stored in the document file or space at
1239 the beginning of paragraphs(). This happens if you have
1240 the cursor between to spaces and then save. Or if you
1241 cut and paste and the selection have a space at the
1242 beginning and then save right after the paste. (Lgb)
1245 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1246 // delete the LineSeparator.
1249 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1250 // delete the LineSeparator.
1253 bool const same_inset = &old.inset() == &cur.inset();
1254 bool const same_par = same_inset && old.pit() == cur.pit();
1255 bool const same_par_pos = same_par && old.pos() == cur.pos();
1257 // If the chars around the old cursor were spaces, delete one of them.
1258 if (!same_par_pos) {
1259 // Only if the cursor has really moved.
1261 && old.pos() < oldpar.size()
1262 && oldpar.isLineSeparator(old.pos())
1263 && oldpar.isLineSeparator(old.pos() - 1)
1264 // FIXME: change tracking (MG)
1265 && oldpar.lookupChange(old.pos() - 1) != Change(Change::DELETED)) {
1266 // We need to set the text to Change::INSERTED to
1267 // get it erased properly
1268 // FIXME: change tracking (MG)
1269 oldpar.setChange(old.pos() -1, Change(Change::INSERTED));
1270 oldpar.erase(old.pos() - 1);
1271 #ifdef WITH_WARNINGS
1272 #warning This will not work anymore when we have multiple views of the same buffer
1273 // In this case, we will have to correct also the cursors held by
1274 // other bufferviews. It will probably be easier to do that in a more
1275 // automated way in CursorSlice code. (JMarc 26/09/2001)
1277 // correct all cursor parts
1279 fixCursorAfterDelete(cur.top(), old.top());
1286 // only do our magic if we changed paragraph
1290 // don't delete anything if this is the ONLY paragraph!
1291 if (old.lastpit() == 0)
1294 // Do not delete empty paragraphs with keepempty set.
1295 if (oldpar.allowEmpty())
1298 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1300 recordUndo(old, Undo::ATOMIC,
1301 max(old.pit() - 1, pit_type(0)),
1302 min(old.pit() + 1, old.lastpit()));
1303 ParagraphList & plist = old.text()->paragraphs();
1304 plist.erase(boost::next(plist.begin(), old.pit()));
1306 // see #warning above
1307 if (cur.depth() >= old.depth()) {
1308 CursorSlice & curslice = cur[old.depth() - 1];
1309 if (&curslice.inset() == &old.inset()
1310 && curslice.pit() > old.pit()) {
1312 // since a paragraph has been deleted, all the
1313 // insets after `old' have been copied and
1314 // their address has changed. Therefore we
1315 // need to `regenerate' cur. (JMarc)
1316 cur.updateInsets(&(cur.bottom().inset()));
1320 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1321 //ParIterator par_it(old);
1322 //updateLabels(old.buffer(), par_it);
1323 // So for now we do the full update:
1324 updateLabels(old.buffer());
1328 if (oldpar.stripLeadingSpaces())
1335 void LyXText::recUndo(pit_type first, pit_type last) const
1337 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1341 void LyXText::recUndo(pit_type par) const
1343 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1347 int defaultRowHeight()
1349 return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);