3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferlist.h"
27 #include "bufferparams.h"
28 #include "BufferView.h"
29 #include "bufferview_funcs.h"
31 #include "coordcache.h"
33 #include "CutAndPaste.h"
35 #include "dispatchresult.h"
36 #include "errorlist.h"
37 #include "funcrequest.h"
44 #include "paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "pariterator.h"
49 #include "lyxserver.h"
50 #include "lyxsocket.h"
54 #include "frontends/FontMetrics.h"
56 #include "insets/insetenv.h"
58 #include "mathed/InsetMathHull.h"
60 #include "support/textutils.h"
62 #include <boost/current_function.hpp>
70 using std::ostringstream;
77 : current_font(LyXFont::ALL_INHERIT),
78 background_color_(LColor::background),
83 bool LyXText::isMainText(Buffer const & buffer) const
85 return &buffer.text() == this;
89 //takes screen x,y coordinates
90 InsetBase * LyXText::checkInsetHit(BufferView & bv, int x, int y)
92 pit_type pit = getPitNearY(bv, y);
93 BOOST_ASSERT(pit != -1);
95 Paragraph const & par = pars_[pit];
98 << BOOST_CURRENT_FUNCTION
103 InsetList::const_iterator iit = par.insetlist.begin();
104 InsetList::const_iterator iend = par.insetlist.end();
105 for (; iit != iend; ++iit) {
106 InsetBase * inset = iit->inset;
109 << BOOST_CURRENT_FUNCTION
110 << ": examining inset " << inset << endl;
112 if (bv.coordCache().getInsets().has(inset))
114 << BOOST_CURRENT_FUNCTION
115 << ": xo: " << inset->xo(bv) << "..."
116 << inset->xo(bv) + inset->width()
117 << " yo: " << inset->yo(bv) - inset->ascent()
119 << inset->yo(bv) + inset->descent()
123 << BOOST_CURRENT_FUNCTION
124 << ": inset has no cached position" << endl;
126 if (inset->covers(bv, x, y)) {
128 << BOOST_CURRENT_FUNCTION
129 << ": Hit inset: " << inset << endl;
134 << BOOST_CURRENT_FUNCTION
135 << ": No inset hit. " << endl;
141 // Gets the fully instantiated font at a given position in a paragraph
142 // Basically the same routine as Paragraph::getFont() in paragraph.C.
143 // The difference is that this one is used for displaying, and thus we
144 // are allowed to make cosmetic improvements. For instance make footnotes
146 LyXFont LyXText::getFont(Buffer const & buffer, Paragraph const & par,
147 pos_type const pos) const
149 BOOST_ASSERT(pos >= 0);
151 LyXLayout_ptr const & layout = par.layout();
155 BufferParams const & params = buffer.params();
156 pos_type const body_pos = par.beginOfBody();
158 // We specialize the 95% common case:
159 if (!par.getDepth()) {
160 LyXFont f = par.getFontSettings(params, pos);
161 if (!isMainText(buffer))
162 applyOuterFont(buffer, f);
165 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
166 lf = layout->labelfont;
167 rlf = layout->reslabelfont;
170 rlf = layout->resfont;
172 // In case the default family has been customized
173 if (lf.family() == LyXFont::INHERIT_FAMILY)
174 rlf.setFamily(params.getFont().family());
175 return f.realize(rlf);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 LyXFont font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
188 if (!isMainText(buffer))
189 applyOuterFont(buffer, font);
191 // Find the pit value belonging to paragraph. This will not break
192 // even if pars_ would not be a vector anymore.
193 // Performance appears acceptable.
195 pit_type pit = pars_.size();
196 for (pit_type it = 0; it < pit; ++it)
197 if (&pars_[it] == &par) {
201 // Realize against environment font information
202 // NOTE: the cast to pit_type should be removed when pit_type
203 // changes to a unsigned integer.
204 if (pit < pit_type(pars_.size()))
205 font.realize(outerFont(pit, pars_));
207 // Realize with the fonts of lesser depth.
208 font.realize(params.getFont());
213 // There are currently two font mechanisms in LyX:
214 // 1. The font attributes in a lyxtext, and
215 // 2. The inset-specific font properties, defined in an inset's
216 // metrics() and draw() methods and handed down the inset chain through
217 // the pi/mi parameters, and stored locally in a lyxtext in font_.
218 // This is where the two are integrated in the final fully realized
220 void LyXText::applyOuterFont(Buffer const & buffer, LyXFont & font) const {
222 lf.reduce(buffer.params().getFont());
224 lf.setLanguage(font.language());
229 LyXFont LyXText::getLayoutFont(Buffer const & buffer, pit_type const pit) const
231 LyXLayout_ptr const & layout = pars_[pit].layout();
233 if (!pars_[pit].getDepth()) {
234 LyXFont lf = layout->resfont;
235 // In case the default family has been customized
236 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
237 lf.setFamily(buffer.params().getFont().family());
241 LyXFont font = layout->font;
242 // Realize with the fonts of lesser depth.
243 //font.realize(outerFont(pit, paragraphs()));
244 font.realize(buffer.params().getFont());
250 LyXFont LyXText::getLabelFont(Buffer const & buffer, Paragraph const & par) const
252 LyXLayout_ptr const & layout = par.layout();
254 if (!par.getDepth()) {
255 LyXFont lf = layout->reslabelfont;
256 // In case the default family has been customized
257 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
258 lf.setFamily(buffer.params().getFont().family());
262 LyXFont font = layout->labelfont;
263 // Realize with the fonts of lesser depth.
264 font.realize(buffer.params().getFont());
270 void LyXText::setCharFont(Buffer const & buffer, pit_type pit,
271 pos_type pos, LyXFont const & fnt)
274 LyXLayout_ptr const & layout = pars_[pit].layout();
276 // Get concrete layout font to reduce against
279 if (pos < pars_[pit].beginOfBody())
280 layoutfont = layout->labelfont;
282 layoutfont = layout->font;
284 // Realize against environment font information
285 if (pars_[pit].getDepth()) {
287 while (!layoutfont.resolved() &&
288 tp != pit_type(paragraphs().size()) &&
289 pars_[tp].getDepth()) {
290 tp = outerHook(tp, paragraphs());
291 if (tp != pit_type(paragraphs().size()))
292 layoutfont.realize(pars_[tp].layout()->font);
296 // Inside inset, apply the inset's font attributes if any
298 if (!isMainText(buffer))
299 layoutfont.realize(font_);
301 layoutfont.realize(buffer.params().getFont());
303 // Now, reduce font against full layout font
304 font.reduce(layoutfont);
306 pars_[pit].setFont(pos, font);
310 // return past-the-last paragraph influenced by a layout change on pit
311 pit_type LyXText::undoSpan(pit_type pit)
313 pit_type end = paragraphs().size();
314 pit_type nextpit = pit + 1;
317 //because of parindents
318 if (!pars_[pit].getDepth())
319 return boost::next(nextpit);
320 //because of depth constrains
321 for (; nextpit != end; ++pit, ++nextpit) {
322 if (!pars_[pit].getDepth())
329 void LyXText::setLayout(Buffer const & buffer, pit_type start, pit_type end,
330 string const & layout)
332 BOOST_ASSERT(start != end);
334 BufferParams const & bufparams = buffer.params();
335 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
337 for (pit_type pit = start; pit != end; ++pit) {
338 pars_[pit].applyLayout(lyxlayout);
339 if (lyxlayout->margintype == MARGIN_MANUAL)
340 pars_[pit].setLabelWidthString(buffer.translateLabel(lyxlayout->labelstring()));
345 // set layout over selection and make a total rebreak of those paragraphs
346 void LyXText::setLayout(LCursor & cur, string const & layout)
348 BOOST_ASSERT(this == cur.text());
349 // special handling of new environment insets
350 BufferView & bv = cur.bv();
351 BufferParams const & params = bv.buffer()->params();
352 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
353 if (lyxlayout->is_environment) {
354 // move everything in a new environment inset
355 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
356 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
357 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
358 lyx::dispatch(FuncRequest(LFUN_CUT));
359 InsetBase * inset = new InsetEnvironment(params, layout);
360 insertInset(cur, inset);
361 //inset->edit(cur, true);
362 //lyx::dispatch(FuncRequest(LFUN_PASTE));
366 pit_type start = cur.selBegin().pit();
367 pit_type end = cur.selEnd().pit() + 1;
368 pit_type undopit = undoSpan(end - 1);
369 recUndo(cur, start, undopit - 1);
370 setLayout(cur.buffer(), start, end, layout);
371 updateLabels(cur.buffer());
375 static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
376 Paragraph const & par, int max_depth)
378 if (par.layout()->labeltype == LABEL_BIBLIO)
380 int const depth = par.params().depth();
381 if (type == LyXText::INC_DEPTH && depth < max_depth)
383 if (type == LyXText::DEC_DEPTH && depth > 0)
389 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
391 BOOST_ASSERT(this == cur.text());
392 // this happens when selecting several cells in tabular (bug 2630)
393 if (cur.selBegin().idx() != cur.selEnd().idx())
396 pit_type const beg = cur.selBegin().pit();
397 pit_type const end = cur.selEnd().pit() + 1;
398 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
400 for (pit_type pit = beg; pit != end; ++pit) {
401 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
403 max_depth = pars_[pit].getMaxDepthAfter();
409 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
411 BOOST_ASSERT(this == cur.text());
412 pit_type const beg = cur.selBegin().pit();
413 pit_type const end = cur.selEnd().pit() + 1;
414 recordUndoSelection(cur);
415 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
417 for (pit_type pit = beg; pit != end; ++pit) {
418 Paragraph & par = pars_[pit];
419 if (lyx::changeDepthAllowed(type, par, max_depth)) {
420 int const depth = par.params().depth();
421 if (type == INC_DEPTH)
422 par.params().depth(depth + 1);
424 par.params().depth(depth - 1);
426 max_depth = par.getMaxDepthAfter();
428 // this handles the counter labels, and also fixes up
429 // depth values for follow-on (child) paragraphs
430 updateLabels(cur.buffer());
434 // set font over selection
435 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
437 BOOST_ASSERT(this == cur.text());
438 // if there is no selection just set the current_font
439 if (!cur.selection()) {
440 // Determine basis font
442 pit_type pit = cur.pit();
443 if (cur.pos() < pars_[pit].beginOfBody())
444 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
446 layoutfont = getLayoutFont(cur.buffer(), pit);
448 // Update current font
449 real_current_font.update(font,
450 cur.buffer().params().language,
453 // Reduce to implicit settings
454 current_font = real_current_font;
455 current_font.reduce(layoutfont);
456 // And resolve it completely
457 real_current_font.realize(layoutfont);
462 // Ok, we have a selection.
463 recordUndoSelection(cur);
465 DocIterator dit = cur.selectionBegin();
466 DocIterator ditend = cur.selectionEnd();
468 BufferParams const & params = cur.buffer().params();
470 // Don't use forwardChar here as ditend might have
471 // pos() == lastpos() and forwardChar would miss it.
472 // Can't use forwardPos either as this descends into
474 for (; dit != ditend; dit.forwardPosNoDescend()) {
475 if (dit.pos() != dit.lastpos()) {
476 LyXFont f = getFont(cur.buffer(), dit.paragraph(), dit.pos());
477 f.update(font, params.language, toggleall);
478 setCharFont(cur.buffer(), dit.pit(), dit.pos(), f);
484 // the cursor set functions have a special mechanism. When they
485 // realize you left an empty paragraph, they will delete it.
487 bool LyXText::cursorHome(LCursor & cur)
489 BOOST_ASSERT(this == cur.text());
490 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
491 Row const & row = pm.getRow(cur.pos(),cur.boundary());
492 return setCursor(cur, cur.pit(), row.pos());
496 bool LyXText::cursorEnd(LCursor & cur)
498 BOOST_ASSERT(this == cur.text());
499 // if not on the last row of the par, put the cursor before
500 // the final space exept if I have a spanning inset or one string
501 // is so long that we force a break.
502 pos_type end = cur.textRow().endpos();
504 // empty text, end-1 is no valid position
506 bool boundary = false;
507 if (end != cur.lastpos()) {
508 if (!cur.paragraph().isLineSeparator(end-1)
509 && !cur.paragraph().isNewline(end-1))
514 return setCursor(cur, cur.pit(), end, true, boundary);
518 bool LyXText::cursorTop(LCursor & cur)
520 BOOST_ASSERT(this == cur.text());
521 return setCursor(cur, 0, 0);
525 bool LyXText::cursorBottom(LCursor & cur)
527 BOOST_ASSERT(this == cur.text());
528 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
532 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
534 BOOST_ASSERT(this == cur.text());
535 // If the mask is completely neutral, tell user
536 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
537 // Could only happen with user style
538 cur.message(_("No font change defined. "
539 "Use Character under the Layout menu to define font change."));
543 // Try implicit word selection
544 // If there is a change in the language the implicit word selection
546 CursorSlice resetCursor = cur.top();
547 bool implicitSelection =
548 font.language() == ignore_language
549 && font.number() == LyXFont::IGNORE
550 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
553 setFont(cur, font, toggleall);
555 // Implicit selections are cleared afterwards
556 // and cursor is set to the original position.
557 if (implicitSelection) {
558 cur.clearSelection();
559 cur.top() = resetCursor;
565 docstring LyXText::getStringToIndex(LCursor const & cur)
567 BOOST_ASSERT(this == cur.text());
571 idxstring = cur.selectionAsString(false);
573 // Try implicit word selection. If there is a change
574 // in the language the implicit word selection is
576 LCursor tmpcur = cur;
577 selectWord(tmpcur, PREVIOUS_WORD);
579 if (!tmpcur.selection())
580 cur.message(_("Nothing to index!"));
581 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
582 cur.message(_("Cannot index more than one paragraph!"));
584 idxstring = tmpcur.selectionAsString(false);
591 void LyXText::setParagraph(LCursor & cur,
592 Spacing const & spacing, LyXAlignment align,
593 docstring const & labelwidthstring, bool noindent)
595 BOOST_ASSERT(cur.text());
596 // make sure that the depth behind the selection are restored, too
597 pit_type undopit = undoSpan(cur.selEnd().pit());
598 recUndo(cur, cur.selBegin().pit(), undopit - 1);
600 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
602 Paragraph & par = pars_[pit];
603 ParagraphParameters & params = par.params();
604 params.spacing(spacing);
606 // does the layout allow the new alignment?
607 LyXLayout_ptr const & layout = par.layout();
609 if (align == LYX_ALIGN_LAYOUT)
610 align = layout->align;
611 if (align & layout->alignpossible) {
612 if (align == layout->align)
613 params.align(LYX_ALIGN_LAYOUT);
617 par.setLabelWidthString(labelwidthstring);
618 params.noindent(noindent);
623 // this really should just insert the inset and not move the cursor.
624 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
626 BOOST_ASSERT(this == cur.text());
628 cur.paragraph().insertInset(cur.pos(), inset,
629 Change(cur.buffer().params().trackChanges ?
630 Change::INSERTED : Change::UNCHANGED));
634 // needed to insert the selection
635 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
637 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
638 current_font, str, autoBreakRows_);
642 // turn double CR to single CR, others are converted into one
643 // blank. Then insertStringAsLines is called
644 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
646 docstring linestr = str;
647 bool newline_inserted = false;
649 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
650 if (linestr[i] == '\n') {
651 if (newline_inserted) {
652 // we know that \r will be ignored by
653 // insertStringAsLines. Of course, it is a dirty
654 // trick, but it works...
655 linestr[i - 1] = '\r';
659 newline_inserted = true;
661 } else if (isPrintable(linestr[i])) {
662 newline_inserted = false;
665 insertStringAsLines(cur, linestr);
669 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
670 bool setfont, bool boundary)
673 setCursorIntern(cur, par, pos, setfont, boundary);
674 return cur.bv().checkDepm(cur, old);
678 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
680 BOOST_ASSERT(par != int(paragraphs().size()));
684 // now some strict checking
685 Paragraph & para = getPar(par);
687 // None of these should happen, but we're scaredy-cats
689 lyxerr << "dont like -1" << endl;
693 if (pos > para.size()) {
694 lyxerr << "dont like 1, pos: " << pos
695 << " size: " << para.size()
696 << " par: " << par << endl;
702 void LyXText::setCursorIntern(LCursor & cur,
703 pit_type par, pos_type pos, bool setfont, bool boundary)
705 BOOST_ASSERT(this == cur.text());
706 cur.boundary(boundary);
707 setCursor(cur.top(), par, pos);
713 void LyXText::setCurrentFont(LCursor & cur)
715 BOOST_ASSERT(this == cur.text());
716 pos_type pos = cur.pos();
717 Paragraph & par = cur.paragraph();
719 if (cur.boundary() && pos > 0)
723 if (pos == cur.lastpos())
725 else // potentional bug... BUG (Lgb)
726 if (par.isSeparator(pos)) {
727 if (pos > cur.textRow().pos() &&
728 bidi.level(pos) % 2 ==
729 bidi.level(pos - 1) % 2)
731 else if (pos + 1 < cur.lastpos())
736 BufferParams const & bufparams = cur.buffer().params();
737 current_font = par.getFontSettings(bufparams, pos);
738 real_current_font = getFont(cur.buffer(), par, pos);
740 if (cur.pos() == cur.lastpos()
741 && bidi.isBoundary(cur.buffer(), par, cur.pos())
742 && !cur.boundary()) {
743 Language const * lang = par.getParLanguage(bufparams);
744 current_font.setLanguage(lang);
745 current_font.setNumber(LyXFont::OFF);
746 real_current_font.setLanguage(lang);
747 real_current_font.setNumber(LyXFont::OFF);
752 // x is an absolute screen coord
753 // returns the column near the specified x-coordinate of the row
754 // x is set to the real beginning of this column
755 pos_type LyXText::getColumnNearX(BufferView const & bv, int right_margin,
756 pit_type const pit, Row const & row, int & x, bool & boundary) const
758 Buffer const & buffer = *bv.buffer();
759 TextMetrics const & tm = bv.textMetrics(this);
761 /// For the main LyXText, it is possible that this pit is not
762 /// yet in the CoordCache when moving cursor up.
763 /// x Paragraph coordinate is always 0 for main text anyway.
764 int const xo = isMainText(*bv.buffer())?
765 0 : bv.coordCache().get(this, pit).x_;
767 RowMetrics const r = tm.computeRowMetrics(pit, row);
768 Paragraph const & par = pars_[pit];
770 pos_type vc = row.pos();
771 pos_type end = row.endpos();
773 LyXLayout_ptr const & layout = par.layout();
775 bool left_side = false;
777 pos_type body_pos = par.beginOfBody();
780 double last_tmpx = tmpx;
783 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
786 // check for empty row
792 frontend::FontMetrics const & fm
793 = theFontMetrics(getLabelFont(buffer, par));
795 while (vc < end && tmpx <= x) {
796 c = bidi.vis2log(vc);
798 if (body_pos > 0 && c == body_pos - 1) {
800 docstring const lsep = from_utf8(layout->labelsep);
801 tmpx += r.label_hfill + fm.width(lsep);
802 if (par.isLineSeparator(body_pos - 1))
803 tmpx -= singleWidth(buffer, par, body_pos - 1);
806 if (par.hfillExpansion(row, c)) {
807 tmpx += singleWidth(buffer, par, c);
811 tmpx += r.label_hfill;
812 } else if (par.isSeparator(c)) {
813 tmpx += singleWidth(buffer, par, c);
817 tmpx += singleWidth(buffer, par, c);
822 if ((tmpx + last_tmpx) / 2 > x) {
827 BOOST_ASSERT(vc <= end); // This shouldn't happen.
830 // This (rtl_support test) is not needed, but gives
831 // some speedup if rtl_support == false
832 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
834 // If lastrow is false, we don't need to compute
836 bool const rtl = lastrow ? isRTL(buffer, par) : false;
838 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
839 (!rtl && !left_side && vc == end && x > tmpx + 5)))
841 else if (vc == row.pos()) {
842 c = bidi.vis2log(vc);
843 if (bidi.level(c) % 2 == 1)
846 c = bidi.vis2log(vc - 1);
847 bool const rtl = (bidi.level(c) % 2 == 1);
848 if (left_side == rtl) {
850 boundary = bidi.isBoundary(buffer, par, c);
854 // I believe this code is not needed anymore (Jug 20050717)
856 // The following code is necessary because the cursor position past
857 // the last char in a row is logically equivalent to that before
858 // the first char in the next row. That's why insets causing row
859 // divisions -- Newline and display-style insets -- must be treated
860 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
861 // Newline inset, air gap below:
862 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
863 if (bidi.level(end -1) % 2 == 0)
864 tmpx -= singleWidth(buffer, par, end - 1);
866 tmpx += singleWidth(buffer, par, end - 1);
870 // Air gap above display inset:
871 if (row.pos() < end && c >= end && end < par.size()
872 && par.isInset(end) && par.getInset(end)->display()) {
875 // Air gap below display inset:
876 if (row.pos() < end && c >= end && par.isInset(end - 1)
877 && par.getInset(end - 1)->display()) {
883 pos_type const col = c - row.pos();
885 if (!c || end == par.size())
888 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
893 return min(col, end - 1 - row.pos());
897 // y is screen coordinate
898 pit_type LyXText::getPitNearY(BufferView & bv, int y)
900 BOOST_ASSERT(!paragraphs().empty());
901 BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
902 CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
904 << BOOST_CURRENT_FUNCTION
905 << ": y: " << y << " cache size: " << cc.size()
908 // look for highest numbered paragraph with y coordinate less than given y
911 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
912 CoordCache::InnerParPosCache::const_iterator et = cc.end();
913 CoordCache::InnerParPosCache::const_iterator last = et; last--;
915 TextMetrics & tm = bv.textMetrics(this);
916 ParagraphMetrics const & pm = tm.parMetrics(it->first);
918 // If we are off-screen (before the visible part)
920 // and even before the first paragraph in the cache.
921 && y < it->second.y_ - int(pm.ascent())) {
922 // and we are not at the first paragraph in the inset.
925 // then this is the paragraph we are looking for.
927 // rebreak it and update the CoordCache.
928 tm.redoParagraph(pit);
929 bv.coordCache().parPos()[this][pit] =
930 Point(0, it->second.y_ - pm.descent());
934 ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
936 // If we are off-screen (after the visible part)
937 if (y > bv.workHeight()
938 // and even after the first paragraph in the cache.
939 && y >= last->second.y_ + int(pm_last.descent())) {
940 pit = last->first + 1;
941 // and we are not at the last paragraph in the inset.
942 if (pit == int(pars_.size()))
944 // then this is the paragraph we are looking for.
945 // rebreak it and update the CoordCache.
946 tm.redoParagraph(pit);
947 bv.coordCache().parPos()[this][pit] =
948 Point(0, last->second.y_ + pm_last.ascent());
952 for (; it != et; ++it) {
954 << BOOST_CURRENT_FUNCTION
955 << " examining: pit: " << it->first
956 << " y: " << it->second.y_
959 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
961 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
968 << BOOST_CURRENT_FUNCTION
969 << ": found best y: " << yy << " for pit: " << pit
976 Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const
978 ParagraphMetrics const & pm = bv.parMetrics(this, pit);
980 int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
981 BOOST_ASSERT(!pm.rows().empty());
982 RowList::const_iterator rit = pm.rows().begin();
983 RowList::const_iterator const rlast = boost::prior(pm.rows().end());
984 for (; rit != rlast; yy += rit->height(), ++rit)
985 if (yy + rit->height() > y)
991 // x,y are absolute screen coordinates
992 // sets cursor recursively descending into nested editable insets
993 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
995 if (lyxerr.debugging(Debug::WORKAREA)) {
996 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
997 cur.bv().coordCache().dump();
999 pit_type pit = getPitNearY(cur.bv(), y);
1000 BOOST_ASSERT(pit != -1);
1002 Row const & row = getRowNearY(cur.bv(), y, pit);
1005 TextMetrics const & tm = cur.bv().textMetrics(this);
1006 ParagraphMetrics const & pm = tm.parMetrics(pit);
1007 int right_margin = tm.rightMargin(pm);
1008 int xx = x; // is modified by getColumnNearX
1009 pos_type const pos = row.pos()
1010 + getColumnNearX(cur.bv(), right_margin, pit, row, xx, bound);
1013 cur.boundary(bound);
1016 // try to descend into nested insets
1017 InsetBase * inset = checkInsetHit(cur.bv(), x, y);
1018 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1020 // Either we deconst editXY or better we move current_font
1021 // and real_current_font to LCursor
1022 setCurrentFont(cur);
1026 InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
1027 //InsetBase * insetBehind = pars_[pit].getInset(pos);
1029 // This should be just before or just behind the
1030 // cursor position set above.
1031 BOOST_ASSERT((pos != 0 && inset == insetBefore)
1032 || inset == pars_[pit].getInset(pos));
1034 // Make sure the cursor points to the position before
1036 if (inset == insetBefore)
1039 // Try to descend recursively inside the inset.
1040 inset = inset->editXY(cur, x, y);
1042 if (cur.top().text() == this)
1043 setCurrentFont(cur);
1048 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1050 if (cur.selection())
1052 if (cur.pos() == cur.lastpos())
1054 InsetBase * inset = cur.nextInset();
1055 if (!isHighlyEditableInset(inset))
1057 inset->edit(cur, front);
1062 bool LyXText::cursorLeft(LCursor & cur)
1064 // Tell BufferView to test for FitCursor in any case!
1065 cur.updateFlags(Update::FitCursor);
1067 if (!cur.boundary() && cur.pos() > 0 &&
1068 cur.textRow().pos() == cur.pos() &&
1069 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1070 !cur.paragraph().isNewline(cur.pos()-1)) {
1071 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1073 if (cur.pos() != 0) {
1074 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1075 if (!checkAndActivateInset(cur, false)) {
1076 /** FIXME: What's this cause purpose???
1077 bool boundary = cur.boundary();
1078 if (false && !boundary &&
1079 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1081 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1084 return updateNeeded;
1087 if (cur.pit() != 0) {
1088 // Steps into the paragraph above
1089 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1095 bool LyXText::cursorRight(LCursor & cur)
1097 // Tell BufferView to test for FitCursor in any case!
1098 cur.updateFlags(Update::FitCursor);
1100 if (cur.pos() != cur.lastpos()) {
1102 return setCursor(cur, cur.pit(), cur.pos(),
1105 bool updateNeeded = false;
1106 if (!checkAndActivateInset(cur, true)) {
1107 if (cur.textRow().endpos() == cur.pos() + 1 &&
1108 cur.textRow().endpos() != cur.lastpos() &&
1109 !cur.paragraph().isLineSeparator(cur.pos()) &&
1110 !cur.paragraph().isNewline(cur.pos())) {
1113 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1114 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1116 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1118 return updateNeeded;
1121 if (cur.pit() != cur.lastpit())
1122 return setCursor(cur, cur.pit() + 1, 0);
1127 bool LyXText::cursorUp(LCursor & cur)
1129 // Tell BufferView to test for FitCursor in any case!
1130 cur.updateFlags(Update::FitCursor);
1132 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1135 int const x = cur.targetX();
1137 if (cur.pos() && cur.boundary())
1138 row = pm.pos2row(cur.pos()-1);
1140 row = pm.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 - pm.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 cur.bv().checkDepm(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 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1171 updateNeeded |= setCursor(cur, cur.pit(),
1172 x2pos(cur.bv(), cur.pit(), pmcur.rows().size() - 1, x));
1177 return updateNeeded;
1181 bool LyXText::cursorDown(LCursor & cur)
1183 // Tell BufferView to test for FitCursor in any case!
1184 cur.updateFlags(Update::FitCursor);
1186 ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
1189 int const x = cur.targetX();
1191 if (cur.pos() && cur.boundary())
1192 row = pm.pos2row(cur.pos()-1);
1194 row = pm.pos2row(cur.pos());
1196 if (!cur.selection()) {
1197 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1199 // To middle of next row
1200 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1201 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1202 cur.clearSelection();
1204 // This happens when you move out of an inset.
1205 // And to give the DEPM the possibility of doing
1206 // something we must provide it with two different
1208 LCursor dummy = cur;
1212 bool const changed = cur.bv().checkDepm(dummy, old);
1214 // Make sure that cur gets back whatever happened to dummy(Lgb)
1221 bool updateNeeded = false;
1223 if (row + 1 < int(pm.rows().size())) {
1224 updateNeeded |= setCursor(cur, cur.pit(),
1225 x2pos(cur.bv(), cur.pit(), row + 1, x));
1226 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1228 updateNeeded |= setCursor(cur, cur.pit(),
1229 x2pos(cur.bv(), cur.pit(), 0, x));
1234 return updateNeeded;
1238 bool LyXText::cursorUpParagraph(LCursor & cur)
1240 bool updated = false;
1242 updated = setCursor(cur, cur.pit(), 0);
1243 else if (cur.pit() != 0)
1244 updated = setCursor(cur, cur.pit() - 1, 0);
1249 bool LyXText::cursorDownParagraph(LCursor & cur)
1251 bool updated = false;
1252 if (cur.pit() != cur.lastpit())
1253 updated = setCursor(cur, cur.pit() + 1, 0);
1255 updated = setCursor(cur, cur.pit(), cur.lastpos());
1260 // fix the cursor `cur' after a characters has been deleted at `where'
1261 // position. Called by deleteEmptyParagraphMechanism
1262 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1264 // Do nothing if cursor is not in the paragraph where the
1265 // deletion occured,
1266 if (cur.pit() != where.pit())
1269 // If cursor position is after the deletion place update it
1270 if (cur.pos() > where.pos())
1273 // Check also if we don't want to set the cursor on a spot behind the
1274 // pagragraph because we erased the last character.
1275 if (cur.pos() > cur.lastpos())
1276 cur.pos() = cur.lastpos();
1280 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur,
1281 LCursor & old, bool & need_anchor_change)
1283 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1284 // old should point to us
1285 BOOST_ASSERT(old.text() == this);
1287 Paragraph & oldpar = old.paragraph();
1289 // We allow all kinds of "mumbo-jumbo" when freespacing.
1290 if (oldpar.isFreeSpacing())
1293 /* Ok I'll put some comments here about what is missing.
1294 There are still some small problems that can lead to
1295 double spaces stored in the document file or space at
1296 the beginning of paragraphs(). This happens if you have
1297 the cursor between to spaces and then save. Or if you
1298 cut and paste and the selection have a space at the
1299 beginning and then save right after the paste. (Lgb)
1302 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1303 // delete the LineSeparator.
1306 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1307 // delete the LineSeparator.
1310 bool const same_inset = &old.inset() == &cur.inset();
1311 bool const same_par = same_inset && old.pit() == cur.pit();
1312 bool const same_par_pos = same_par && old.pos() == cur.pos();
1314 // If the chars around the old cursor were spaces, delete one of them.
1315 if (!same_par_pos) {
1316 // Only if the cursor has really moved.
1318 && old.pos() < oldpar.size()
1319 && oldpar.isLineSeparator(old.pos())
1320 && oldpar.isLineSeparator(old.pos() - 1)
1321 && !oldpar.isDeleted(old.pos() - 1)) {
1322 oldpar.eraseChar(old.pos() - 1, false); // do not track changes in DEPM
1323 #ifdef WITH_WARNINGS
1324 #warning This will not work anymore when we have multiple views of the same buffer
1325 // In this case, we will have to correct also the cursors held by
1326 // other bufferviews. It will probably be easier to do that in a more
1327 // automated way in CursorSlice code. (JMarc 26/09/2001)
1329 // correct all cursor parts
1331 fixCursorAfterDelete(cur.top(), old.top());
1332 need_anchor_change = true;
1338 // only do our magic if we changed paragraph
1342 // don't delete anything if this is the ONLY paragraph!
1343 if (old.lastpit() == 0)
1346 // Do not delete empty paragraphs with keepempty set.
1347 if (oldpar.allowEmpty())
1350 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1352 recordUndo(old, Undo::ATOMIC,
1353 max(old.pit() - 1, pit_type(0)),
1354 min(old.pit() + 1, old.lastpit()));
1355 ParagraphList & plist = old.text()->paragraphs();
1356 plist.erase(boost::next(plist.begin(), old.pit()));
1358 // see #warning above
1359 if (cur.depth() >= old.depth()) {
1360 CursorSlice & curslice = cur[old.depth() - 1];
1361 if (&curslice.inset() == &old.inset()
1362 && curslice.pit() > old.pit()) {
1364 // since a paragraph has been deleted, all the
1365 // insets after `old' have been copied and
1366 // their address has changed. Therefore we
1367 // need to `regenerate' cur. (JMarc)
1368 cur.updateInsets(&(cur.bottom().inset()));
1369 need_anchor_change = true;
1375 if (oldpar.stripLeadingSpaces())
1376 need_anchor_change = true;
1382 void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const
1384 recordUndo(cur, Undo::ATOMIC, first, last);
1388 void LyXText::recUndo(LCursor & cur, pit_type par) const
1390 recordUndo(cur, Undo::ATOMIC, par, par);