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>
65 using lyx::CoordCache;
71 using std::ostringstream;
77 LyXText::LyXText(BufferView * bv)
78 : maxwidth_(bv ? bv->workWidth() : 100),
79 current_font(LyXFont::ALL_INHERIT),
80 background_color_(LColor::background),
86 void LyXText::init(BufferView * bv)
90 maxwidth_ = bv->workWidth();
95 pit_type const end = paragraphs().size();
96 for (pit_type pit = 0; pit != end; ++pit)
97 pars_[pit].rows().clear();
99 updateLabels(*bv->buffer());
103 bool LyXText::isMainText() const
105 return &bv()->buffer()->text() == this;
109 //takes screen x,y coordinates
110 InsetBase * LyXText::checkInsetHit(int x, int y) const
112 pit_type pit = getPitNearY(y);
113 BOOST_ASSERT(pit != -1);
115 Paragraph const & par = pars_[pit];
118 << BOOST_CURRENT_FUNCTION
123 InsetList::const_iterator iit = par.insetlist.begin();
124 InsetList::const_iterator iend = par.insetlist.end();
125 for (; iit != iend; ++iit) {
126 InsetBase * inset = iit->inset;
129 << BOOST_CURRENT_FUNCTION
130 << ": examining inset " << inset << endl;
132 if (bv()->coordCache().getInsets().has(inset))
134 << BOOST_CURRENT_FUNCTION
135 << ": xo: " << inset->xo(*bv()) << "..."
136 << inset->xo(*bv()) + inset->width()
137 << " yo: " << inset->yo(*bv()) - inset->ascent()
139 << inset->yo(*bv()) + inset->descent()
143 << BOOST_CURRENT_FUNCTION
144 << ": inset has no cached position" << endl;
146 if (inset->covers(*bv(), x, y)) {
148 << BOOST_CURRENT_FUNCTION
149 << ": Hit inset: " << inset << endl;
154 << BOOST_CURRENT_FUNCTION
155 << ": No inset hit. " << endl;
161 // Gets the fully instantiated font at a given position in a paragraph
162 // Basically the same routine as Paragraph::getFont() in paragraph.C.
163 // The difference is that this one is used for displaying, and thus we
164 // are allowed to make cosmetic improvements. For instance make footnotes
166 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
168 BOOST_ASSERT(pos >= 0);
170 LyXLayout_ptr const & layout = par.layout();
174 BufferParams const & params = bv()->buffer()->params();
175 pos_type const body_pos = par.beginOfBody();
177 // We specialize the 95% common case:
178 if (!par.getDepth()) {
179 LyXFont f = par.getFontSettings(params, pos);
184 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
185 lf = layout->labelfont;
186 rlf = layout->reslabelfont;
189 rlf = layout->resfont;
191 // In case the default family has been customized
192 if (lf.family() == LyXFont::INHERIT_FAMILY)
193 rlf.setFamily(params.getFont().family());
194 return f.realize(rlf);
197 // The uncommon case need not be optimized as much
200 layoutfont = layout->labelfont;
202 layoutfont = layout->font;
204 LyXFont font = par.getFontSettings(params, pos);
205 font.realize(layoutfont);
208 applyOuterFont(font);
210 // Find the pit value belonging to paragraph. This will not break
211 // even if pars_ would not be a vector anymore.
212 // Performance appears acceptable.
214 pit_type pit = pars_.size();
215 for (pit_type it = 0; it < pit; ++it)
216 if (&pars_[it] == &par) {
220 // Realize against environment font information
221 // NOTE: the cast to pit_type should be removed when pit_type
222 // changes to a unsigned integer.
223 if (pit < pit_type(pars_.size()))
224 font.realize(outerFont(pit, pars_));
226 // Realize with the fonts of lesser depth.
227 font.realize(params.getFont());
232 // There are currently two font mechanisms in LyX:
233 // 1. The font attributes in a lyxtext, and
234 // 2. The inset-specific font properties, defined in an inset's
235 // metrics() and draw() methods and handed down the inset chain through
236 // the pi/mi parameters, and stored locally in a lyxtext in font_.
237 // This is where the two are integrated in the final fully realized
239 void LyXText::applyOuterFont(LyXFont & font) const {
241 lf.reduce(bv()->buffer()->params().getFont());
243 lf.setLanguage(font.language());
248 LyXFont LyXText::getLayoutFont(pit_type const pit) const
250 LyXLayout_ptr const & layout = pars_[pit].layout();
252 if (!pars_[pit].getDepth()) {
253 LyXFont lf = layout->resfont;
254 // In case the default family has been customized
255 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
256 lf.setFamily(bv()->buffer()->params().getFont().family());
260 LyXFont font = layout->font;
261 // Realize with the fonts of lesser depth.
262 //font.realize(outerFont(pit, paragraphs()));
263 font.realize(bv()->buffer()->params().getFont());
269 LyXFont LyXText::getLabelFont(Paragraph const & par) const
271 LyXLayout_ptr const & layout = par.layout();
273 if (!par.getDepth()) {
274 LyXFont lf = layout->reslabelfont;
275 // In case the default family has been customized
276 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
277 lf.setFamily(bv()->buffer()->params().getFont().family());
281 LyXFont font = layout->labelfont;
282 // Realize with the fonts of lesser depth.
283 font.realize(bv()->buffer()->params().getFont());
289 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
292 LyXLayout_ptr const & layout = pars_[pit].layout();
294 // Get concrete layout font to reduce against
297 if (pos < pars_[pit].beginOfBody())
298 layoutfont = layout->labelfont;
300 layoutfont = layout->font;
302 // Realize against environment font information
303 if (pars_[pit].getDepth()) {
305 while (!layoutfont.resolved() &&
306 tp != pit_type(paragraphs().size()) &&
307 pars_[tp].getDepth()) {
308 tp = outerHook(tp, paragraphs());
309 if (tp != pit_type(paragraphs().size()))
310 layoutfont.realize(pars_[tp].layout()->font);
314 // Inside inset, apply the inset's font attributes if any
317 layoutfont.realize(font_);
319 layoutfont.realize(bv()->buffer()->params().getFont());
321 // Now, reduce font against full layout font
322 font.reduce(layoutfont);
324 pars_[pit].setFont(pos, font);
328 // return past-the-last paragraph influenced by a layout change on pit
329 pit_type LyXText::undoSpan(pit_type pit)
331 pit_type end = paragraphs().size();
332 pit_type nextpit = pit + 1;
335 //because of parindents
336 if (!pars_[pit].getDepth())
337 return boost::next(nextpit);
338 //because of depth constrains
339 for (; nextpit != end; ++pit, ++nextpit) {
340 if (!pars_[pit].getDepth())
347 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
349 BOOST_ASSERT(start != end);
351 BufferParams const & bufparams = bv()->buffer()->params();
352 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
354 for (pit_type pit = start; pit != end; ++pit) {
355 pars_[pit].applyLayout(lyxlayout);
356 if (lyxlayout->margintype == MARGIN_MANUAL)
358 pars_[pit].setLabelWidthString(lyx::from_ascii(lyxlayout->labelstring()));
363 // set layout over selection and make a total rebreak of those paragraphs
364 void LyXText::setLayout(LCursor & cur, string const & layout)
366 BOOST_ASSERT(this == cur.text());
367 // special handling of new environment insets
368 BufferView & bv = cur.bv();
369 BufferParams const & params = bv.buffer()->params();
370 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
371 if (lyxlayout->is_environment) {
372 // move everything in a new environment inset
373 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
374 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
375 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
376 lyx::dispatch(FuncRequest(LFUN_CUT));
377 InsetBase * inset = new InsetEnvironment(params, layout);
378 insertInset(cur, inset);
379 //inset->edit(cur, true);
380 //lyx::dispatch(FuncRequest(LFUN_PASTE));
384 pit_type start = cur.selBegin().pit();
385 pit_type end = cur.selEnd().pit() + 1;
386 pit_type undopit = undoSpan(end - 1);
387 recUndo(start, undopit - 1);
388 setLayout(start, end, layout);
389 updateLabels(cur.buffer());
396 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
397 Paragraph const & par, int max_depth)
399 if (par.layout()->labeltype == LABEL_BIBLIO)
401 int const depth = par.params().depth();
402 if (type == LyXText::INC_DEPTH && depth < max_depth)
404 if (type == LyXText::DEC_DEPTH && depth > 0)
413 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
415 BOOST_ASSERT(this == cur.text());
416 // this happens when selecting several cells in tabular (bug 2630)
417 if (cur.selBegin().idx() != cur.selEnd().idx())
420 pit_type const beg = cur.selBegin().pit();
421 pit_type const end = cur.selEnd().pit() + 1;
422 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
424 for (pit_type pit = beg; pit != end; ++pit) {
425 if (::changeDepthAllowed(type, pars_[pit], max_depth))
427 max_depth = pars_[pit].getMaxDepthAfter();
433 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
435 BOOST_ASSERT(this == cur.text());
436 pit_type const beg = cur.selBegin().pit();
437 pit_type const end = cur.selEnd().pit() + 1;
438 recordUndoSelection(cur);
439 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
441 for (pit_type pit = beg; pit != end; ++pit) {
442 Paragraph & par = pars_[pit];
443 if (::changeDepthAllowed(type, par, max_depth)) {
444 int const depth = par.params().depth();
445 if (type == INC_DEPTH)
446 par.params().depth(depth + 1);
448 par.params().depth(depth - 1);
450 max_depth = par.getMaxDepthAfter();
452 // this handles the counter labels, and also fixes up
453 // depth values for follow-on (child) paragraphs
454 updateLabels(cur.buffer());
458 // set font over selection
459 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
461 BOOST_ASSERT(this == cur.text());
462 // if there is no selection just set the current_font
463 if (!cur.selection()) {
464 // Determine basis font
466 pit_type pit = cur.pit();
467 if (cur.pos() < pars_[pit].beginOfBody())
468 layoutfont = getLabelFont(pars_[pit]);
470 layoutfont = getLayoutFont(pit);
472 // Update current font
473 real_current_font.update(font,
474 cur.buffer().params().language,
477 // Reduce to implicit settings
478 current_font = real_current_font;
479 current_font.reduce(layoutfont);
480 // And resolve it completely
481 real_current_font.realize(layoutfont);
486 // Ok, we have a selection.
487 recordUndoSelection(cur);
489 DocIterator dit = cur.selectionBegin();
490 DocIterator ditend = cur.selectionEnd();
492 BufferParams const & params = cur.buffer().params();
494 // Don't use forwardChar here as ditend might have
495 // pos() == lastpos() and forwardChar would miss it.
496 // Can't use forwardPos either as this descends into
498 for (; dit != ditend; dit.forwardPosNoDescend()) {
499 if (dit.pos() != dit.lastpos()) {
500 LyXFont f = getFont(dit.paragraph(), dit.pos());
501 f.update(font, params.language, toggleall);
502 setCharFont(dit.pit(), dit.pos(), f);
508 // the cursor set functions have a special mechanism. When they
509 // realize you left an empty paragraph, they will delete it.
511 bool LyXText::cursorHome(LCursor & cur)
513 BOOST_ASSERT(this == cur.text());
514 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
516 return setCursor(cur, cur.pit(), row.pos());
520 bool LyXText::cursorEnd(LCursor & cur)
522 BOOST_ASSERT(this == cur.text());
523 // if not on the last row of the par, put the cursor before
524 // the final space exept if I have a spanning inset or one string
525 // is so long that we force a break.
526 pos_type end = cur.textRow().endpos();
528 // empty text, end-1 is no valid position
530 bool boundary = false;
531 if (end != cur.lastpos()) {
532 if (!cur.paragraph().isLineSeparator(end-1)
533 && !cur.paragraph().isNewline(end-1))
538 return setCursor(cur, cur.pit(), end, true, boundary);
542 bool LyXText::cursorTop(LCursor & cur)
544 BOOST_ASSERT(this == cur.text());
545 return setCursor(cur, 0, 0);
549 bool LyXText::cursorBottom(LCursor & cur)
551 BOOST_ASSERT(this == cur.text());
552 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
556 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
558 BOOST_ASSERT(this == cur.text());
559 // If the mask is completely neutral, tell user
560 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
561 // Could only happen with user style
562 cur.message(_("No font change defined. "
563 "Use Character under the Layout menu to define font change."));
567 // Try implicit word selection
568 // If there is a change in the language the implicit word selection
570 CursorSlice resetCursor = cur.top();
571 bool implicitSelection =
572 font.language() == ignore_language
573 && font.number() == LyXFont::IGNORE
574 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
577 setFont(cur, font, toggleall);
579 // Implicit selections are cleared afterwards
580 // and cursor is set to the original position.
581 if (implicitSelection) {
582 cur.clearSelection();
583 cur.top() = resetCursor;
589 string LyXText::getStringToIndex(LCursor const & cur)
591 BOOST_ASSERT(this == cur.text());
595 idxstring = cur.selectionAsString(false);
597 // Try implicit word selection. If there is a change
598 // in the language the implicit word selection is
600 LCursor tmpcur = cur;
601 selectWord(tmpcur, lyx::PREVIOUS_WORD);
603 if (!tmpcur.selection())
604 cur.message(_("Nothing to index!"));
605 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
606 cur.message(_("Cannot index more than one paragraph!"));
608 idxstring = tmpcur.selectionAsString(false);
611 return lyx::to_utf8(idxstring);
615 void LyXText::setParagraph(LCursor & cur,
616 Spacing const & spacing, LyXAlignment align,
617 string const & labelwidthstring, bool noindent)
619 BOOST_ASSERT(cur.text());
620 // make sure that the depth behind the selection are restored, too
621 pit_type undopit = undoSpan(cur.selEnd().pit());
622 recUndo(cur.selBegin().pit(), undopit - 1);
624 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
626 Paragraph & par = pars_[pit];
627 ParagraphParameters & params = par.params();
628 params.spacing(spacing);
630 // does the layout allow the new alignment?
631 LyXLayout_ptr const & layout = par.layout();
633 if (align == LYX_ALIGN_LAYOUT)
634 align = layout->align;
635 if (align & layout->alignpossible) {
636 if (align == layout->align)
637 params.align(LYX_ALIGN_LAYOUT);
642 par.setLabelWidthString(lyx::from_ascii(labelwidthstring));
643 params.noindent(noindent);
648 // this really should just insert the inset and not move the cursor.
649 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
651 BOOST_ASSERT(this == cur.text());
653 cur.paragraph().insertInset(cur.pos(), inset,
654 Change(cur.buffer().params().trackChanges ?
655 Change::INSERTED : Change::UNCHANGED));
659 // needed to insert the selection
660 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
662 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
663 current_font, str, autoBreakRows_);
667 // turn double CR to single CR, others are converted into one
668 // blank. Then insertStringAsLines is called
669 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
671 docstring linestr = str;
672 bool newline_inserted = false;
674 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
675 if (linestr[i] == '\n') {
676 if (newline_inserted) {
677 // we know that \r will be ignored by
678 // insertStringAsLines. Of course, it is a dirty
679 // trick, but it works...
680 linestr[i - 1] = '\r';
684 newline_inserted = true;
686 } else if (isPrintable(linestr[i])) {
687 newline_inserted = false;
690 insertStringAsLines(cur, linestr);
694 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
695 bool setfont, bool boundary)
698 setCursorIntern(cur, par, pos, setfont, boundary);
699 return deleteEmptyParagraphMechanism(cur, old);
703 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
705 BOOST_ASSERT(par != int(paragraphs().size()));
709 // now some strict checking
710 Paragraph & para = getPar(par);
712 // None of these should happen, but we're scaredy-cats
714 lyxerr << "dont like -1" << endl;
718 if (pos > para.size()) {
719 lyxerr << "dont like 1, pos: " << pos
720 << " size: " << para.size()
721 << " par: " << par << endl;
727 void LyXText::setCursorIntern(LCursor & cur,
728 pit_type par, pos_type pos, bool setfont, bool boundary)
730 BOOST_ASSERT(this == cur.text());
731 cur.boundary(boundary);
732 setCursor(cur.top(), par, pos);
739 void LyXText::setCurrentFont(LCursor & cur)
741 BOOST_ASSERT(this == cur.text());
742 pos_type pos = cur.pos();
743 Paragraph & par = cur.paragraph();
745 if (cur.boundary() && pos > 0)
749 if (pos == cur.lastpos())
751 else // potentional bug... BUG (Lgb)
752 if (par.isSeparator(pos)) {
753 if (pos > cur.textRow().pos() &&
754 bidi.level(pos) % 2 ==
755 bidi.level(pos - 1) % 2)
757 else if (pos + 1 < cur.lastpos())
762 BufferParams const & bufparams = cur.buffer().params();
763 current_font = par.getFontSettings(bufparams, pos);
764 real_current_font = getFont(par, pos);
766 if (cur.pos() == cur.lastpos()
767 && bidi.isBoundary(cur.buffer(), par, cur.pos())
768 && !cur.boundary()) {
769 Language const * lang = par.getParLanguage(bufparams);
770 current_font.setLanguage(lang);
771 current_font.setNumber(LyXFont::OFF);
772 real_current_font.setLanguage(lang);
773 real_current_font.setNumber(LyXFont::OFF);
778 // x is an absolute screen coord
779 // returns the column near the specified x-coordinate of the row
780 // x is set to the real beginning of this column
781 pos_type LyXText::getColumnNearX(pit_type const pit,
782 Row const & row, int & x, bool & boundary) const
784 int const xo = bv()->coordCache().get(this, pit).x_;
786 RowMetrics const r = computeRowMetrics(pit, row);
787 Paragraph const & par = pars_[pit];
789 pos_type vc = row.pos();
790 pos_type end = row.endpos();
792 LyXLayout_ptr const & layout = par.layout();
794 bool left_side = false;
796 pos_type body_pos = par.beginOfBody();
799 double last_tmpx = tmpx;
802 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
805 // check for empty row
811 lyx::frontend::FontMetrics const & fm
812 = theFontMetrics(getLabelFont(par));
814 while (vc < end && tmpx <= x) {
815 c = bidi.vis2log(vc);
817 if (body_pos > 0 && c == body_pos - 1) {
819 docstring const lsep = lyx::from_utf8(layout->labelsep);
820 tmpx += r.label_hfill + fm.width(lsep);
821 if (par.isLineSeparator(body_pos - 1))
822 tmpx -= singleWidth(par, body_pos - 1);
825 if (hfillExpansion(par, row, c)) {
826 tmpx += singleWidth(par, c);
830 tmpx += r.label_hfill;
831 } else if (par.isSeparator(c)) {
832 tmpx += singleWidth(par, c);
836 tmpx += singleWidth(par, c);
841 if ((tmpx + last_tmpx) / 2 > x) {
846 BOOST_ASSERT(vc <= end); // This shouldn't happen.
849 // This (rtl_support test) is not needed, but gives
850 // some speedup if rtl_support == false
851 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
853 // If lastrow is false, we don't need to compute
855 bool const rtl = lastrow ? isRTL(par) : false;
857 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
858 (!rtl && !left_side && vc == end && x > tmpx + 5)))
860 else if (vc == row.pos()) {
861 c = bidi.vis2log(vc);
862 if (bidi.level(c) % 2 == 1)
865 c = bidi.vis2log(vc - 1);
866 bool const rtl = (bidi.level(c) % 2 == 1);
867 if (left_side == rtl) {
869 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
873 // I believe this code is not needed anymore (Jug 20050717)
875 // The following code is necessary because the cursor position past
876 // the last char in a row is logically equivalent to that before
877 // the first char in the next row. That's why insets causing row
878 // divisions -- Newline and display-style insets -- must be treated
879 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
880 // Newline inset, air gap below:
881 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
882 if (bidi.level(end -1) % 2 == 0)
883 tmpx -= singleWidth(par, end - 1);
885 tmpx += singleWidth(par, end - 1);
889 // Air gap above display inset:
890 if (row.pos() < end && c >= end && end < par.size()
891 && par.isInset(end) && par.getInset(end)->display()) {
894 // Air gap below display inset:
895 if (row.pos() < end && c >= end && par.isInset(end - 1)
896 && par.getInset(end - 1)->display()) {
902 pos_type const col = c - row.pos();
904 if (!c || end == par.size())
907 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
912 return min(col, end - 1 - row.pos());
916 // y is screen coordinate
917 pit_type LyXText::getPitNearY(int y) const
919 BOOST_ASSERT(!paragraphs().empty());
920 BOOST_ASSERT(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
921 CoordCache::InnerParPosCache const & cc = bv()->coordCache().getParPos().find(this)->second;
923 << BOOST_CURRENT_FUNCTION
924 << ": y: " << y << " cache size: " << cc.size()
927 // look for highest numbered paragraph with y coordinate less than given y
930 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
931 CoordCache::InnerParPosCache::const_iterator et = cc.end();
932 for (; it != et; ++it) {
934 << BOOST_CURRENT_FUNCTION
935 << " examining: pit: " << it->first
936 << " y: " << it->second.y_
939 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
946 << BOOST_CURRENT_FUNCTION
947 << ": found best y: " << yy << " for pit: " << pit
954 Row const & LyXText::getRowNearY(int y, pit_type pit) const
956 Paragraph const & par = pars_[pit];
957 int yy = bv()->coordCache().get(this, pit).y_ - par.ascent();
958 BOOST_ASSERT(!par.rows().empty());
959 RowList::const_iterator rit = par.rows().begin();
960 RowList::const_iterator const rlast = boost::prior(par.rows().end());
961 for (; rit != rlast; yy += rit->height(), ++rit)
962 if (yy + rit->height() > y)
968 // x,y are absolute screen coordinates
969 // sets cursor recursively descending into nested editable insets
970 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
972 pit_type pit = getPitNearY(y);
973 BOOST_ASSERT(pit != -1);
974 Row const & row = getRowNearY(y, pit);
977 int xx = x; // is modified by getColumnNearX
978 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
984 // try to descend into nested insets
985 InsetBase * inset = checkInsetHit(x, y);
986 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
988 // Either we deconst editXY or better we move current_font
989 // and real_current_font to LCursor
994 // This should be just before or just behind the
995 // cursor position set above.
996 InsetBase * inset2 = pars_[pit].getInset(pos - 1);
997 InsetBase * inset3 = pars_[pit].getInset(pos);
999 BOOST_ASSERT((pos != 0 && inset == inset2)
1000 || inset == inset3);
1001 // Make sure the cursor points to the position before
1003 if (inset == pars_[pit].getInset(pos - 1))
1005 inset = inset->editXY(cur, x, y);
1006 if (cur.top().text() == this)
1007 setCurrentFont(cur);
1012 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1014 if (cur.selection())
1016 if (cur.pos() == cur.lastpos())
1018 InsetBase * inset = cur.nextInset();
1019 if (!isHighlyEditableInset(inset))
1021 inset->edit(cur, front);
1026 bool LyXText::cursorLeft(LCursor & cur)
1028 if (!cur.boundary() && cur.pos() > 0 &&
1029 cur.textRow().pos() == cur.pos() &&
1030 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1031 !cur.paragraph().isNewline(cur.pos()-1)) {
1032 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1034 if (cur.pos() != 0) {
1035 bool boundary = cur.boundary();
1036 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1037 if (!checkAndActivateInset(cur, false)) {
1038 if (false && !boundary &&
1039 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1041 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1043 return updateNeeded;
1046 if (cur.pit() != 0) {
1047 // Steps into the paragraph above
1048 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1054 bool LyXText::cursorRight(LCursor & cur)
1056 if (cur.pos() != cur.lastpos()) {
1058 return setCursor(cur, cur.pit(), cur.pos(),
1061 bool updateNeeded = false;
1062 if (!checkAndActivateInset(cur, true)) {
1063 if (cur.textRow().endpos() == cur.pos() + 1 &&
1064 cur.textRow().endpos() != cur.lastpos() &&
1065 !cur.paragraph().isLineSeparator(cur.pos()) &&
1066 !cur.paragraph().isNewline(cur.pos())) {
1069 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1070 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1072 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1074 return updateNeeded;
1077 if (cur.pit() != cur.lastpit())
1078 return setCursor(cur, cur.pit() + 1, 0);
1083 bool LyXText::cursorUp(LCursor & cur)
1085 Paragraph const & par = cur.paragraph();
1087 int const x = cur.targetX();
1089 if (cur.pos() && cur.boundary())
1090 row = par.pos2row(cur.pos()-1);
1092 row = par.pos2row(cur.pos());
1094 if (!cur.selection()) {
1095 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1097 // Go to middle of previous row. 16 found to work OK;
1098 // 12 = top/bottom margin of display math
1099 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1100 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1101 cur.clearSelection();
1103 // This happens when you move out of an inset.
1104 // And to give the DEPM the possibility of doing
1105 // something we must provide it with two different
1107 LCursor dummy = cur;
1111 return deleteEmptyParagraphMechanism(dummy, old);
1114 bool updateNeeded = false;
1117 updateNeeded |= setCursor(cur, cur.pit(),
1118 x2pos(cur.pit(), row - 1, x));
1119 } else if (cur.pit() > 0) {
1121 //cannot use 'par' now
1122 updateNeeded |= setCursor(cur, cur.pit(),
1123 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1128 return updateNeeded;
1132 bool LyXText::cursorDown(LCursor & cur)
1134 Paragraph const & par = cur.paragraph();
1136 int const x = cur.targetX();
1138 if (cur.pos() && cur.boundary())
1139 row = par.pos2row(cur.pos()-1);
1141 row = par.pos2row(cur.pos());
1143 if (!cur.selection()) {
1144 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1146 // To middle of next row
1147 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1148 editXY(cur, x, y + par.rows()[row].descent() + margin);
1149 cur.clearSelection();
1151 // This happens when you move out of an inset.
1152 // And to give the DEPM the possibility of doing
1153 // something we must provide it with two different
1155 LCursor dummy = cur;
1159 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1161 // Make sure that cur gets back whatever happened to dummy(Lgb)
1168 bool updateNeeded = false;
1170 if (row + 1 < int(par.rows().size())) {
1171 updateNeeded |= setCursor(cur, cur.pit(),
1172 x2pos(cur.pit(), row + 1, x));
1173 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1175 updateNeeded |= setCursor(cur, cur.pit(),
1176 x2pos(cur.pit(), 0, x));
1181 return updateNeeded;
1185 bool LyXText::cursorUpParagraph(LCursor & cur)
1187 bool updated = false;
1189 updated = setCursor(cur, cur.pit(), 0);
1190 else if (cur.pit() != 0)
1191 updated = setCursor(cur, cur.pit() - 1, 0);
1196 bool LyXText::cursorDownParagraph(LCursor & cur)
1198 bool updated = false;
1199 if (cur.pit() != cur.lastpit())
1200 updated = setCursor(cur, cur.pit() + 1, 0);
1202 updated = setCursor(cur, cur.pit(), cur.lastpos());
1207 // fix the cursor `cur' after a characters has been deleted at `where'
1208 // position. Called by deleteEmptyParagraphMechanism
1209 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1211 // Do nothing if cursor is not in the paragraph where the
1212 // deletion occured,
1213 if (cur.pit() != where.pit())
1216 // If cursor position is after the deletion place update it
1217 if (cur.pos() > where.pos())
1220 // Check also if we don't want to set the cursor on a spot behind the
1221 // pagragraph because we erased the last character.
1222 if (cur.pos() > cur.lastpos())
1223 cur.pos() = cur.lastpos();
1227 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1229 // Would be wrong to delete anything if we have a selection.
1230 if (cur.selection())
1233 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1234 // old should point to us
1235 BOOST_ASSERT(old.text() == this);
1237 Paragraph & oldpar = old.paragraph();
1239 // We allow all kinds of "mumbo-jumbo" when freespacing.
1240 if (oldpar.isFreeSpacing())
1243 /* Ok I'll put some comments here about what is missing.
1244 There are still some small problems that can lead to
1245 double spaces stored in the document file or space at
1246 the beginning of paragraphs(). This happens if you have
1247 the cursor between to spaces and then save. Or if you
1248 cut and paste and the selection have a space at the
1249 beginning and then save right after the paste. (Lgb)
1252 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1253 // delete the LineSeparator.
1256 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1257 // delete the LineSeparator.
1260 bool const same_inset = &old.inset() == &cur.inset();
1261 bool const same_par = same_inset && old.pit() == cur.pit();
1262 bool const same_par_pos = same_par && old.pos() == cur.pos();
1264 // If the chars around the old cursor were spaces, delete one of them.
1265 if (!same_par_pos) {
1266 // Only if the cursor has really moved.
1268 && old.pos() < oldpar.size()
1269 && oldpar.isLineSeparator(old.pos())
1270 && oldpar.isLineSeparator(old.pos() - 1)
1271 && oldpar.lookupChange(old.pos() - 1).type != Change::DELETED) {
1272 oldpar.erase(old.pos() - 1, false); // do not track changes in DEPM
1273 #ifdef WITH_WARNINGS
1274 #warning This will not work anymore when we have multiple views of the same buffer
1275 // In this case, we will have to correct also the cursors held by
1276 // other bufferviews. It will probably be easier to do that in a more
1277 // automated way in CursorSlice code. (JMarc 26/09/2001)
1279 // correct all cursor parts
1281 fixCursorAfterDelete(cur.top(), old.top());
1288 // only do our magic if we changed paragraph
1292 // don't delete anything if this is the ONLY paragraph!
1293 if (old.lastpit() == 0)
1296 // Do not delete empty paragraphs with keepempty set.
1297 if (oldpar.allowEmpty())
1300 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1302 recordUndo(old, Undo::ATOMIC,
1303 max(old.pit() - 1, pit_type(0)),
1304 min(old.pit() + 1, old.lastpit()));
1305 ParagraphList & plist = old.text()->paragraphs();
1306 plist.erase(boost::next(plist.begin(), old.pit()));
1308 // see #warning above
1309 if (cur.depth() >= old.depth()) {
1310 CursorSlice & curslice = cur[old.depth() - 1];
1311 if (&curslice.inset() == &old.inset()
1312 && curslice.pit() > old.pit()) {
1314 // since a paragraph has been deleted, all the
1315 // insets after `old' have been copied and
1316 // their address has changed. Therefore we
1317 // need to `regenerate' cur. (JMarc)
1318 cur.updateInsets(&(cur.bottom().inset()));
1322 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1323 //ParIterator par_it(old);
1324 //updateLabels(old.buffer(), par_it);
1325 // So for now we do the full update:
1326 updateLabels(old.buffer());
1330 if (oldpar.stripLeadingSpaces())
1337 void LyXText::recUndo(pit_type first, pit_type last) const
1339 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1343 void LyXText::recUndo(pit_type par) const
1345 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1349 int defaultRowHeight()
1351 return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);