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 "paragraph.h"
44 #include "paragraph_funcs.h"
45 #include "ParagraphParameters.h"
46 #include "pariterator.h"
47 #include "lyxserver.h"
48 #include "lyxsocket.h"
52 #include "frontends/FontMetrics.h"
54 #include "insets/insetenv.h"
56 #include "mathed/InsetMathHull.h"
58 #include "support/textutils.h"
60 #include <boost/current_function.hpp>
68 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv)
75 : maxwidth_(bv ? bv->workWidth() : 100),
76 current_font(LyXFont::ALL_INHERIT),
77 background_color_(LColor::background),
82 void LyXText::init(BufferView * bv)
85 maxwidth_ = bv->workWidth();
90 pit_type const end = paragraphs().size();
91 for (pit_type pit = 0; pit != end; ++pit)
92 pars_[pit].rows().clear();
94 updateLabels(*bv->buffer());
98 bool LyXText::isMainText(Buffer const & buffer) const
100 return &buffer.text() == this;
104 //takes screen x,y coordinates
105 InsetBase * LyXText::checkInsetHit(BufferView & bv, int x, int y)
107 pit_type pit = getPitNearY(bv, y);
108 BOOST_ASSERT(pit != -1);
110 Paragraph const & par = pars_[pit];
113 << BOOST_CURRENT_FUNCTION
118 InsetList::const_iterator iit = par.insetlist.begin();
119 InsetList::const_iterator iend = par.insetlist.end();
120 for (; iit != iend; ++iit) {
121 InsetBase * inset = iit->inset;
124 << BOOST_CURRENT_FUNCTION
125 << ": examining inset " << inset << endl;
127 if (bv.coordCache().getInsets().has(inset))
129 << BOOST_CURRENT_FUNCTION
130 << ": xo: " << inset->xo(bv) << "..."
131 << inset->xo(bv) + inset->width()
132 << " yo: " << inset->yo(bv) - inset->ascent()
134 << inset->yo(bv) + inset->descent()
138 << BOOST_CURRENT_FUNCTION
139 << ": inset has no cached position" << endl;
141 if (inset->covers(bv, x, y)) {
143 << BOOST_CURRENT_FUNCTION
144 << ": Hit inset: " << inset << endl;
149 << BOOST_CURRENT_FUNCTION
150 << ": No inset hit. " << endl;
156 // Gets the fully instantiated font at a given position in a paragraph
157 // Basically the same routine as Paragraph::getFont() in paragraph.C.
158 // The difference is that this one is used for displaying, and thus we
159 // are allowed to make cosmetic improvements. For instance make footnotes
161 LyXFont LyXText::getFont(Buffer const & buffer, Paragraph const & par,
162 pos_type const pos) const
164 BOOST_ASSERT(pos >= 0);
166 LyXLayout_ptr const & layout = par.layout();
170 BufferParams const & params = buffer.params();
171 pos_type const body_pos = par.beginOfBody();
173 // We specialize the 95% common case:
174 if (!par.getDepth()) {
175 LyXFont f = par.getFontSettings(params, pos);
176 if (!isMainText(buffer))
177 applyOuterFont(buffer, f);
180 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
181 lf = layout->labelfont;
182 rlf = layout->reslabelfont;
185 rlf = layout->resfont;
187 // In case the default family has been customized
188 if (lf.family() == LyXFont::INHERIT_FAMILY)
189 rlf.setFamily(params.getFont().family());
190 return f.realize(rlf);
193 // The uncommon case need not be optimized as much
196 layoutfont = layout->labelfont;
198 layoutfont = layout->font;
200 LyXFont font = par.getFontSettings(params, pos);
201 font.realize(layoutfont);
203 if (!isMainText(buffer))
204 applyOuterFont(buffer, font);
206 // Find the pit value belonging to paragraph. This will not break
207 // even if pars_ would not be a vector anymore.
208 // Performance appears acceptable.
210 pit_type pit = pars_.size();
211 for (pit_type it = 0; it < pit; ++it)
212 if (&pars_[it] == &par) {
216 // Realize against environment font information
217 // NOTE: the cast to pit_type should be removed when pit_type
218 // changes to a unsigned integer.
219 if (pit < pit_type(pars_.size()))
220 font.realize(outerFont(pit, pars_));
222 // Realize with the fonts of lesser depth.
223 font.realize(params.getFont());
228 // There are currently two font mechanisms in LyX:
229 // 1. The font attributes in a lyxtext, and
230 // 2. The inset-specific font properties, defined in an inset's
231 // metrics() and draw() methods and handed down the inset chain through
232 // the pi/mi parameters, and stored locally in a lyxtext in font_.
233 // This is where the two are integrated in the final fully realized
235 void LyXText::applyOuterFont(Buffer const & buffer, LyXFont & font) const {
237 lf.reduce(buffer.params().getFont());
239 lf.setLanguage(font.language());
244 LyXFont LyXText::getLayoutFont(Buffer const & buffer, pit_type const pit) const
246 LyXLayout_ptr const & layout = pars_[pit].layout();
248 if (!pars_[pit].getDepth()) {
249 LyXFont lf = layout->resfont;
250 // In case the default family has been customized
251 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
252 lf.setFamily(buffer.params().getFont().family());
256 LyXFont font = layout->font;
257 // Realize with the fonts of lesser depth.
258 //font.realize(outerFont(pit, paragraphs()));
259 font.realize(buffer.params().getFont());
265 LyXFont LyXText::getLabelFont(Buffer const & buffer, Paragraph const & par) const
267 LyXLayout_ptr const & layout = par.layout();
269 if (!par.getDepth()) {
270 LyXFont lf = layout->reslabelfont;
271 // In case the default family has been customized
272 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
273 lf.setFamily(buffer.params().getFont().family());
277 LyXFont font = layout->labelfont;
278 // Realize with the fonts of lesser depth.
279 font.realize(buffer.params().getFont());
285 void LyXText::setCharFont(Buffer const & buffer, pit_type pit,
286 pos_type pos, LyXFont const & fnt)
289 LyXLayout_ptr const & layout = pars_[pit].layout();
291 // Get concrete layout font to reduce against
294 if (pos < pars_[pit].beginOfBody())
295 layoutfont = layout->labelfont;
297 layoutfont = layout->font;
299 // Realize against environment font information
300 if (pars_[pit].getDepth()) {
302 while (!layoutfont.resolved() &&
303 tp != pit_type(paragraphs().size()) &&
304 pars_[tp].getDepth()) {
305 tp = outerHook(tp, paragraphs());
306 if (tp != pit_type(paragraphs().size()))
307 layoutfont.realize(pars_[tp].layout()->font);
311 // Inside inset, apply the inset's font attributes if any
313 if (!isMainText(buffer))
314 layoutfont.realize(font_);
316 layoutfont.realize(buffer.params().getFont());
318 // Now, reduce font against full layout font
319 font.reduce(layoutfont);
321 pars_[pit].setFont(pos, font);
325 // return past-the-last paragraph influenced by a layout change on pit
326 pit_type LyXText::undoSpan(pit_type pit)
328 pit_type end = paragraphs().size();
329 pit_type nextpit = pit + 1;
332 //because of parindents
333 if (!pars_[pit].getDepth())
334 return boost::next(nextpit);
335 //because of depth constrains
336 for (; nextpit != end; ++pit, ++nextpit) {
337 if (!pars_[pit].getDepth())
344 void LyXText::setLayout(Buffer const & buffer, pit_type start, pit_type end,
345 string const & layout)
347 BOOST_ASSERT(start != end);
349 BufferParams const & bufparams = buffer.params();
350 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
352 for (pit_type pit = start; pit != end; ++pit) {
353 pars_[pit].applyLayout(lyxlayout);
354 if (lyxlayout->margintype == MARGIN_MANUAL)
355 pars_[pit].setLabelWidthString(buffer.translateLabel(lyxlayout->labelstring()));
360 // set layout over selection and make a total rebreak of those paragraphs
361 void LyXText::setLayout(LCursor & cur, string const & layout)
363 BOOST_ASSERT(this == cur.text());
364 // special handling of new environment insets
365 BufferView & bv = cur.bv();
366 BufferParams const & params = bv.buffer()->params();
367 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
368 if (lyxlayout->is_environment) {
369 // move everything in a new environment inset
370 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
371 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
372 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
373 lyx::dispatch(FuncRequest(LFUN_CUT));
374 InsetBase * inset = new InsetEnvironment(params, layout);
375 insertInset(cur, inset);
376 //inset->edit(cur, true);
377 //lyx::dispatch(FuncRequest(LFUN_PASTE));
381 pit_type start = cur.selBegin().pit();
382 pit_type end = cur.selEnd().pit() + 1;
383 pit_type undopit = undoSpan(end - 1);
384 recUndo(cur, start, undopit - 1);
385 setLayout(cur.buffer(), start, end, layout);
386 updateLabels(cur.buffer());
390 static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
391 Paragraph const & par, int max_depth)
393 if (par.layout()->labeltype == LABEL_BIBLIO)
395 int const depth = par.params().depth();
396 if (type == LyXText::INC_DEPTH && depth < max_depth)
398 if (type == LyXText::DEC_DEPTH && depth > 0)
404 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
406 BOOST_ASSERT(this == cur.text());
407 // this happens when selecting several cells in tabular (bug 2630)
408 if (cur.selBegin().idx() != cur.selEnd().idx())
411 pit_type const beg = cur.selBegin().pit();
412 pit_type const end = cur.selEnd().pit() + 1;
413 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
415 for (pit_type pit = beg; pit != end; ++pit) {
416 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
418 max_depth = pars_[pit].getMaxDepthAfter();
424 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
426 BOOST_ASSERT(this == cur.text());
427 pit_type const beg = cur.selBegin().pit();
428 pit_type const end = cur.selEnd().pit() + 1;
429 recordUndoSelection(cur);
430 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
432 for (pit_type pit = beg; pit != end; ++pit) {
433 Paragraph & par = pars_[pit];
434 if (lyx::changeDepthAllowed(type, par, max_depth)) {
435 int const depth = par.params().depth();
436 if (type == INC_DEPTH)
437 par.params().depth(depth + 1);
439 par.params().depth(depth - 1);
441 max_depth = par.getMaxDepthAfter();
443 // this handles the counter labels, and also fixes up
444 // depth values for follow-on (child) paragraphs
445 updateLabels(cur.buffer());
449 // set font over selection
450 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
452 BOOST_ASSERT(this == cur.text());
453 // if there is no selection just set the current_font
454 if (!cur.selection()) {
455 // Determine basis font
457 pit_type pit = cur.pit();
458 if (cur.pos() < pars_[pit].beginOfBody())
459 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
461 layoutfont = getLayoutFont(cur.buffer(), pit);
463 // Update current font
464 real_current_font.update(font,
465 cur.buffer().params().language,
468 // Reduce to implicit settings
469 current_font = real_current_font;
470 current_font.reduce(layoutfont);
471 // And resolve it completely
472 real_current_font.realize(layoutfont);
477 // Ok, we have a selection.
478 recordUndoSelection(cur);
480 DocIterator dit = cur.selectionBegin();
481 DocIterator ditend = cur.selectionEnd();
483 BufferParams const & params = cur.buffer().params();
485 // Don't use forwardChar here as ditend might have
486 // pos() == lastpos() and forwardChar would miss it.
487 // Can't use forwardPos either as this descends into
489 for (; dit != ditend; dit.forwardPosNoDescend()) {
490 if (dit.pos() != dit.lastpos()) {
491 LyXFont f = getFont(cur.buffer(), dit.paragraph(), dit.pos());
492 f.update(font, params.language, toggleall);
493 setCharFont(cur.buffer(), dit.pit(), dit.pos(), f);
499 // the cursor set functions have a special mechanism. When they
500 // realize you left an empty paragraph, they will delete it.
502 bool LyXText::cursorHome(LCursor & cur)
504 BOOST_ASSERT(this == cur.text());
505 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
506 return setCursor(cur, cur.pit(), row.pos());
510 bool LyXText::cursorEnd(LCursor & cur)
512 BOOST_ASSERT(this == cur.text());
513 // if not on the last row of the par, put the cursor before
514 // the final space exept if I have a spanning inset or one string
515 // is so long that we force a break.
516 pos_type end = cur.textRow().endpos();
518 // empty text, end-1 is no valid position
520 bool boundary = false;
521 if (end != cur.lastpos()) {
522 if (!cur.paragraph().isLineSeparator(end-1)
523 && !cur.paragraph().isNewline(end-1))
528 return setCursor(cur, cur.pit(), end, true, boundary);
532 bool LyXText::cursorTop(LCursor & cur)
534 BOOST_ASSERT(this == cur.text());
535 return setCursor(cur, 0, 0);
539 bool LyXText::cursorBottom(LCursor & cur)
541 BOOST_ASSERT(this == cur.text());
542 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
546 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
548 BOOST_ASSERT(this == cur.text());
549 // If the mask is completely neutral, tell user
550 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
551 // Could only happen with user style
552 cur.message(_("No font change defined. "
553 "Use Character under the Layout menu to define font change."));
557 // Try implicit word selection
558 // If there is a change in the language the implicit word selection
560 CursorSlice resetCursor = cur.top();
561 bool implicitSelection =
562 font.language() == ignore_language
563 && font.number() == LyXFont::IGNORE
564 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
567 setFont(cur, font, toggleall);
569 // Implicit selections are cleared afterwards
570 // and cursor is set to the original position.
571 if (implicitSelection) {
572 cur.clearSelection();
573 cur.top() = resetCursor;
579 docstring LyXText::getStringToIndex(LCursor const & cur)
581 BOOST_ASSERT(this == cur.text());
585 idxstring = cur.selectionAsString(false);
587 // Try implicit word selection. If there is a change
588 // in the language the implicit word selection is
590 LCursor tmpcur = cur;
591 selectWord(tmpcur, PREVIOUS_WORD);
593 if (!tmpcur.selection())
594 cur.message(_("Nothing to index!"));
595 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
596 cur.message(_("Cannot index more than one paragraph!"));
598 idxstring = tmpcur.selectionAsString(false);
605 void LyXText::setParagraph(LCursor & cur,
606 Spacing const & spacing, LyXAlignment align,
607 docstring const & labelwidthstring, bool noindent)
609 BOOST_ASSERT(cur.text());
610 // make sure that the depth behind the selection are restored, too
611 pit_type undopit = undoSpan(cur.selEnd().pit());
612 recUndo(cur, cur.selBegin().pit(), undopit - 1);
614 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
616 Paragraph & par = pars_[pit];
617 ParagraphParameters & params = par.params();
618 params.spacing(spacing);
620 // does the layout allow the new alignment?
621 LyXLayout_ptr const & layout = par.layout();
623 if (align == LYX_ALIGN_LAYOUT)
624 align = layout->align;
625 if (align & layout->alignpossible) {
626 if (align == layout->align)
627 params.align(LYX_ALIGN_LAYOUT);
631 par.setLabelWidthString(labelwidthstring);
632 params.noindent(noindent);
637 // this really should just insert the inset and not move the cursor.
638 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
640 BOOST_ASSERT(this == cur.text());
642 cur.paragraph().insertInset(cur.pos(), inset,
643 Change(cur.buffer().params().trackChanges ?
644 Change::INSERTED : Change::UNCHANGED));
648 // needed to insert the selection
649 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
651 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
652 current_font, str, autoBreakRows_);
656 // turn double CR to single CR, others are converted into one
657 // blank. Then insertStringAsLines is called
658 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
660 docstring linestr = str;
661 bool newline_inserted = false;
663 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
664 if (linestr[i] == '\n') {
665 if (newline_inserted) {
666 // we know that \r will be ignored by
667 // insertStringAsLines. Of course, it is a dirty
668 // trick, but it works...
669 linestr[i - 1] = '\r';
673 newline_inserted = true;
675 } else if (isPrintable(linestr[i])) {
676 newline_inserted = false;
679 insertStringAsLines(cur, linestr);
683 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
684 bool setfont, bool boundary)
687 setCursorIntern(cur, par, pos, setfont, boundary);
688 return deleteEmptyParagraphMechanism(cur, old);
692 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
694 BOOST_ASSERT(par != int(paragraphs().size()));
698 // now some strict checking
699 Paragraph & para = getPar(par);
701 // None of these should happen, but we're scaredy-cats
703 lyxerr << "dont like -1" << endl;
707 if (pos > para.size()) {
708 lyxerr << "dont like 1, pos: " << pos
709 << " size: " << para.size()
710 << " par: " << par << endl;
716 void LyXText::setCursorIntern(LCursor & cur,
717 pit_type par, pos_type pos, bool setfont, bool boundary)
719 BOOST_ASSERT(this == cur.text());
720 cur.boundary(boundary);
721 setCursor(cur.top(), par, pos);
728 void LyXText::setCurrentFont(LCursor & cur)
730 BOOST_ASSERT(this == cur.text());
731 pos_type pos = cur.pos();
732 Paragraph & par = cur.paragraph();
734 if (cur.boundary() && pos > 0)
738 if (pos == cur.lastpos())
740 else // potentional bug... BUG (Lgb)
741 if (par.isSeparator(pos)) {
742 if (pos > cur.textRow().pos() &&
743 bidi.level(pos) % 2 ==
744 bidi.level(pos - 1) % 2)
746 else if (pos + 1 < cur.lastpos())
751 BufferParams const & bufparams = cur.buffer().params();
752 current_font = par.getFontSettings(bufparams, pos);
753 real_current_font = getFont(cur.buffer(), par, pos);
755 if (cur.pos() == cur.lastpos()
756 && bidi.isBoundary(cur.buffer(), par, cur.pos())
757 && !cur.boundary()) {
758 Language const * lang = par.getParLanguage(bufparams);
759 current_font.setLanguage(lang);
760 current_font.setNumber(LyXFont::OFF);
761 real_current_font.setLanguage(lang);
762 real_current_font.setNumber(LyXFont::OFF);
767 // x is an absolute screen coord
768 // returns the column near the specified x-coordinate of the row
769 // x is set to the real beginning of this column
770 pos_type LyXText::getColumnNearX(BufferView const & bv, pit_type const pit,
771 Row const & row, int & x, bool & boundary) const
773 Buffer const & buffer = *bv.buffer();
774 int const xo = bv.coordCache().get(this, pit).x_;
776 RowMetrics const r = computeRowMetrics(buffer, pit, row);
777 Paragraph const & par = pars_[pit];
779 pos_type vc = row.pos();
780 pos_type end = row.endpos();
782 LyXLayout_ptr const & layout = par.layout();
784 bool left_side = false;
786 pos_type body_pos = par.beginOfBody();
789 double last_tmpx = tmpx;
792 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
795 // check for empty row
801 frontend::FontMetrics const & fm
802 = theFontMetrics(getLabelFont(buffer, par));
804 while (vc < end && tmpx <= x) {
805 c = bidi.vis2log(vc);
807 if (body_pos > 0 && c == body_pos - 1) {
809 docstring const lsep = from_utf8(layout->labelsep);
810 tmpx += r.label_hfill + fm.width(lsep);
811 if (par.isLineSeparator(body_pos - 1))
812 tmpx -= singleWidth(buffer, par, body_pos - 1);
815 if (par.hfillExpansion(row, c)) {
816 tmpx += singleWidth(buffer, par, c);
820 tmpx += r.label_hfill;
821 } else if (par.isSeparator(c)) {
822 tmpx += singleWidth(buffer, par, c);
826 tmpx += singleWidth(buffer, par, c);
831 if ((tmpx + last_tmpx) / 2 > x) {
836 BOOST_ASSERT(vc <= end); // This shouldn't happen.
839 // This (rtl_support test) is not needed, but gives
840 // some speedup if rtl_support == false
841 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
843 // If lastrow is false, we don't need to compute
845 bool const rtl = lastrow ? isRTL(buffer, par) : false;
847 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
848 (!rtl && !left_side && vc == end && x > tmpx + 5)))
850 else if (vc == row.pos()) {
851 c = bidi.vis2log(vc);
852 if (bidi.level(c) % 2 == 1)
855 c = bidi.vis2log(vc - 1);
856 bool const rtl = (bidi.level(c) % 2 == 1);
857 if (left_side == rtl) {
859 boundary = bidi.isBoundary(buffer, par, c);
863 // I believe this code is not needed anymore (Jug 20050717)
865 // The following code is necessary because the cursor position past
866 // the last char in a row is logically equivalent to that before
867 // the first char in the next row. That's why insets causing row
868 // divisions -- Newline and display-style insets -- must be treated
869 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
870 // Newline inset, air gap below:
871 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
872 if (bidi.level(end -1) % 2 == 0)
873 tmpx -= singleWidth(buffer, par, end - 1);
875 tmpx += singleWidth(buffer, par, end - 1);
879 // Air gap above display inset:
880 if (row.pos() < end && c >= end && end < par.size()
881 && par.isInset(end) && par.getInset(end)->display()) {
884 // Air gap below display inset:
885 if (row.pos() < end && c >= end && par.isInset(end - 1)
886 && par.getInset(end - 1)->display()) {
892 pos_type const col = c - row.pos();
894 if (!c || end == par.size())
897 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
902 return min(col, end - 1 - row.pos());
906 // y is screen coordinate
907 pit_type LyXText::getPitNearY(BufferView & bv, int y)
909 BOOST_ASSERT(!paragraphs().empty());
910 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
911 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
913 << BOOST_CURRENT_FUNCTION
914 << ": y: " << y << " cache size: " << cc.size()
917 // look for highest numbered paragraph with y coordinate less than given y
920 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
921 CoordCache::InnerParPosCache::const_iterator et = cc.end();
922 CoordCache::InnerParPosCache::const_iterator last = et; last--;
924 // If we are off-screen (before the visible part)
926 // and even before the first paragraph in the cache.
927 && y < it->second.y_ - int(pars_[it->first].ascent())) {
928 // and we are not at the first paragraph in the inset.
931 // then this is the paragraph we are looking for.
933 // rebreak it and update the CoordCache.
934 redoParagraph(bv, pit);
935 bv.coordCache().parPos()[this][pit] =
936 Point(0, it->second.y_ - pars_[it->first].descent());
940 // If we are off-screen (after the visible part)
941 if (y > bv.workHeight()
942 // and even after the first paragraph in the cache.
943 && y >= last->second.y_ + int(pars_[last->first].descent())) {
944 pit = last->first + 1;
945 // and we are not at the last paragraph in the inset.
946 if (pit == pars_.size())
948 // then this is the paragraph we are looking for.
949 // rebreak it and update the CoordCache.
950 redoParagraph(bv, pit);
951 bv.coordCache().parPos()[this][pit] =
952 Point(0, last->second.y_ + pars_[last->first].ascent());
956 for (; it != et; ++it) {
958 << BOOST_CURRENT_FUNCTION
959 << " examining: pit: " << it->first
960 << " y: " << it->second.y_
963 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
970 << BOOST_CURRENT_FUNCTION
971 << ": found best y: " << yy << " for pit: " << pit
978 Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const
980 Paragraph const & par = pars_[pit];
981 int yy = bv.coordCache().get(this, pit).y_ - par.ascent();
982 BOOST_ASSERT(!par.rows().empty());
983 RowList::const_iterator rit = par.rows().begin();
984 RowList::const_iterator const rlast = boost::prior(par.rows().end());
985 for (; rit != rlast; yy += rit->height(), ++rit)
986 if (yy + rit->height() > y)
992 // x,y are absolute screen coordinates
993 // sets cursor recursively descending into nested editable insets
994 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
996 if (lyxerr.debugging(Debug::WORKAREA)) {
997 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
998 cur.bv().coordCache().dump();
1000 pit_type pit = getPitNearY(cur.bv(), y);
1001 BOOST_ASSERT(pit != -1);
1002 // When another window is opened with the same document, rows()
1003 // will be cleared so pars_[pit].rows() might be empty when switching
1004 // between windwos. A better solution is that each buffer view
1005 // has its own rows() for the same buffer.
1006 if (pars_[pit].rows().empty())
1007 redoParagraph(cur.bv(), pit);
1008 Row const & row = getRowNearY(cur.bv(), y, pit);
1011 int xx = x; // is modified by getColumnNearX
1012 pos_type const pos = row.pos()
1013 + getColumnNearX(cur.bv(), pit, row, xx, bound);
1016 cur.boundary(bound);
1019 // try to descend into nested insets
1020 InsetBase * inset = checkInsetHit(cur.bv(), x, y);
1021 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1023 // Either we deconst editXY or better we move current_font
1024 // and real_current_font to LCursor
1025 setCurrentFont(cur);
1029 InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
1030 //InsetBase * insetBehind = pars_[pit].getInset(pos);
1032 // This should be just before or just behind the
1033 // cursor position set above.
1034 BOOST_ASSERT((pos != 0 && inset == insetBefore)
1035 || inset == pars_[pit].getInset(pos));
1037 // Make sure the cursor points to the position before
1039 if (inset == insetBefore)
1042 // Try to descend recursively inside the inset.
1043 inset = inset->editXY(cur, x, y);
1045 if (cur.top().text() == this)
1046 setCurrentFont(cur);
1051 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1053 if (cur.selection())
1055 if (cur.pos() == cur.lastpos())
1057 InsetBase * inset = cur.nextInset();
1058 if (!isHighlyEditableInset(inset))
1060 inset->edit(cur, front);
1065 bool LyXText::cursorLeft(LCursor & cur)
1067 // Tell BufferView to test for FitCursor in any case!
1068 cur.updateFlags(Update::FitCursor);
1070 if (!cur.boundary() && cur.pos() > 0 &&
1071 cur.textRow().pos() == cur.pos() &&
1072 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1073 !cur.paragraph().isNewline(cur.pos()-1)) {
1074 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1076 if (cur.pos() != 0) {
1077 bool boundary = cur.boundary();
1078 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1079 if (!checkAndActivateInset(cur, false)) {
1080 if (false && !boundary &&
1081 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1083 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1085 return updateNeeded;
1088 if (cur.pit() != 0) {
1089 // Steps into the paragraph above
1090 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1096 bool LyXText::cursorRight(LCursor & cur)
1098 // Tell BufferView to test for FitCursor in any case!
1099 cur.updateFlags(Update::FitCursor);
1101 if (cur.pos() != cur.lastpos()) {
1103 return setCursor(cur, cur.pit(), cur.pos(),
1106 bool updateNeeded = false;
1107 if (!checkAndActivateInset(cur, true)) {
1108 if (cur.textRow().endpos() == cur.pos() + 1 &&
1109 cur.textRow().endpos() != cur.lastpos() &&
1110 !cur.paragraph().isLineSeparator(cur.pos()) &&
1111 !cur.paragraph().isNewline(cur.pos())) {
1114 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1115 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1117 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1119 return updateNeeded;
1122 if (cur.pit() != cur.lastpit())
1123 return setCursor(cur, cur.pit() + 1, 0);
1128 bool LyXText::cursorUp(LCursor & cur)
1130 // Tell BufferView to test for FitCursor in any case!
1131 cur.updateFlags(Update::FitCursor);
1133 Paragraph const & par = cur.paragraph();
1135 int const x = cur.targetX();
1137 if (cur.pos() && cur.boundary())
1138 row = par.pos2row(cur.pos()-1);
1140 row = par.pos2row(cur.pos());
1142 if (!cur.selection()) {
1143 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1145 // Go to middle of previous row. 16 found to work OK;
1146 // 12 = top/bottom margin of display math
1147 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1148 editXY(cur, x, y - par.rows()[row].ascent() - 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 return deleteEmptyParagraphMechanism(dummy, old);
1162 bool updateNeeded = false;
1165 updateNeeded |= setCursor(cur, cur.pit(),
1166 x2pos(cur.bv(), cur.pit(), row - 1, x));
1167 } else if (cur.pit() > 0) {
1169 //cannot use 'par' now
1170 updateNeeded |= setCursor(cur, cur.pit(),
1171 x2pos(cur.bv(), cur.pit(), cur.paragraph().rows().size() - 1, x));
1176 return updateNeeded;
1180 bool LyXText::cursorDown(LCursor & cur)
1182 // Tell BufferView to test for FitCursor in any case!
1183 cur.updateFlags(Update::FitCursor);
1185 Paragraph const & par = cur.paragraph();
1187 int const x = cur.targetX();
1189 if (cur.pos() && cur.boundary())
1190 row = par.pos2row(cur.pos()-1);
1192 row = par.pos2row(cur.pos());
1194 if (!cur.selection()) {
1195 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1197 // To middle of next row
1198 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1199 editXY(cur, x, y + par.rows()[row].descent() + margin);
1200 cur.clearSelection();
1202 // This happens when you move out of an inset.
1203 // And to give the DEPM the possibility of doing
1204 // something we must provide it with two different
1206 LCursor dummy = cur;
1210 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1212 // Make sure that cur gets back whatever happened to dummy(Lgb)
1219 bool updateNeeded = false;
1221 if (row + 1 < int(par.rows().size())) {
1222 updateNeeded |= setCursor(cur, cur.pit(),
1223 x2pos(cur.bv(), cur.pit(), row + 1, x));
1224 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1226 updateNeeded |= setCursor(cur, cur.pit(),
1227 x2pos(cur.bv(), cur.pit(), 0, x));
1232 return updateNeeded;
1236 bool LyXText::cursorUpParagraph(LCursor & cur)
1238 bool updated = false;
1240 updated = setCursor(cur, cur.pit(), 0);
1241 else if (cur.pit() != 0)
1242 updated = setCursor(cur, cur.pit() - 1, 0);
1247 bool LyXText::cursorDownParagraph(LCursor & cur)
1249 bool updated = false;
1250 if (cur.pit() != cur.lastpit())
1251 updated = setCursor(cur, cur.pit() + 1, 0);
1253 updated = setCursor(cur, cur.pit(), cur.lastpos());
1258 // fix the cursor `cur' after a characters has been deleted at `where'
1259 // position. Called by deleteEmptyParagraphMechanism
1260 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1262 // Do nothing if cursor is not in the paragraph where the
1263 // deletion occured,
1264 if (cur.pit() != where.pit())
1267 // If cursor position is after the deletion place update it
1268 if (cur.pos() > where.pos())
1271 // Check also if we don't want to set the cursor on a spot behind the
1272 // pagragraph because we erased the last character.
1273 if (cur.pos() > cur.lastpos())
1274 cur.pos() = cur.lastpos();
1278 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1280 // Would be wrong to delete anything if we have a selection.
1281 if (cur.selection())
1284 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1285 // old should point to us
1286 BOOST_ASSERT(old.text() == this);
1288 Paragraph & oldpar = old.paragraph();
1290 // We allow all kinds of "mumbo-jumbo" when freespacing.
1291 if (oldpar.isFreeSpacing())
1294 /* Ok I'll put some comments here about what is missing.
1295 There are still some small problems that can lead to
1296 double spaces stored in the document file or space at
1297 the beginning of paragraphs(). This happens if you have
1298 the cursor between to spaces and then save. Or if you
1299 cut and paste and the selection have a space at the
1300 beginning and then save right after the paste. (Lgb)
1303 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1304 // delete the LineSeparator.
1307 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1308 // delete the LineSeparator.
1311 bool const same_inset = &old.inset() == &cur.inset();
1312 bool const same_par = same_inset && old.pit() == cur.pit();
1313 bool const same_par_pos = same_par && old.pos() == cur.pos();
1315 // If the chars around the old cursor were spaces, delete one of them.
1316 if (!same_par_pos) {
1317 // Only if the cursor has really moved.
1319 && old.pos() < oldpar.size()
1320 && oldpar.isLineSeparator(old.pos())
1321 && oldpar.isLineSeparator(old.pos() - 1)
1322 && !oldpar.isDeleted(old.pos() - 1)) {
1323 oldpar.eraseChar(old.pos() - 1, false); // do not track changes in DEPM
1324 // rebreak it and update the CoordCache.
1325 redoParagraph(cur.bv(), old.pit());
1326 #ifdef WITH_WARNINGS
1327 #warning This will not work anymore when we have multiple views of the same buffer
1328 // In this case, we will have to correct also the cursors held by
1329 // other bufferviews. It will probably be easier to do that in a more
1330 // automated way in CursorSlice code. (JMarc 26/09/2001)
1332 // correct all cursor parts
1334 fixCursorAfterDelete(cur.top(), old.top());
1341 // only do our magic if we changed paragraph
1345 // don't delete anything if this is the ONLY paragraph!
1346 if (old.lastpit() == 0)
1349 // Do not delete empty paragraphs with keepempty set.
1350 if (oldpar.allowEmpty())
1353 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1355 recordUndo(old, Undo::ATOMIC,
1356 max(old.pit() - 1, pit_type(0)),
1357 min(old.pit() + 1, old.lastpit()));
1358 ParagraphList & plist = old.text()->paragraphs();
1359 plist.erase(boost::next(plist.begin(), old.pit()));
1361 // see #warning above
1362 if (cur.depth() >= old.depth()) {
1363 CursorSlice & curslice = cur[old.depth() - 1];
1364 if (&curslice.inset() == &old.inset()
1365 && curslice.pit() > old.pit()) {
1367 // since a paragraph has been deleted, all the
1368 // insets after `old' have been copied and
1369 // their address has changed. Therefore we
1370 // need to `regenerate' cur. (JMarc)
1371 cur.updateInsets(&(cur.bottom().inset()));
1375 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1376 //ParIterator par_it(old);
1377 //updateLabels(old.buffer(), par_it);
1378 // So for now we do the full update:
1379 updateLabels(old.buffer());
1383 if (oldpar.stripLeadingSpaces())
1390 void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const
1392 recordUndo(cur, Undo::ATOMIC, first, last);
1396 void LyXText::recUndo(LCursor & cur, pit_type par) const
1398 recordUndo(cur, Undo::ATOMIC, par, par);
1402 int defaultRowHeight()
1404 return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);