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/Application.h"
54 #include "frontends/FontLoader.h"
55 #include "frontends/FontMetrics.h"
57 #include "insets/insetenv.h"
59 #include "mathed/InsetMathHull.h"
61 #include "support/textutils.h"
63 #include <boost/current_function.hpp>
72 using std::ostringstream;
78 LyXText::LyXText(BufferView * bv)
79 : maxwidth_(bv ? bv->workWidth() : 100),
80 current_font(LyXFont::ALL_INHERIT),
81 background_color_(LColor::background),
87 void LyXText::init(BufferView * bv)
91 maxwidth_ = bv->workWidth();
96 pit_type const end = paragraphs().size();
97 for (pit_type pit = 0; pit != end; ++pit)
98 pars_[pit].rows().clear();
100 updateLabels(*bv->buffer());
104 bool LyXText::isMainText() const
106 return &bv()->buffer()->text() == this;
110 //takes screen x,y coordinates
111 InsetBase * LyXText::checkInsetHit(int x, int y) const
113 pit_type pit = getPitNearY(y);
114 BOOST_ASSERT(pit != -1);
116 Paragraph const & par = pars_[pit];
119 << BOOST_CURRENT_FUNCTION
124 InsetList::const_iterator iit = par.insetlist.begin();
125 InsetList::const_iterator iend = par.insetlist.end();
126 for (; iit != iend; ++iit) {
127 InsetBase * inset = iit->inset;
130 << BOOST_CURRENT_FUNCTION
131 << ": examining inset " << inset << endl;
133 if (theCoords.getInsets().has(inset))
135 << BOOST_CURRENT_FUNCTION
136 << ": xo: " << inset->xo() << "..."
137 << inset->xo() + inset->width()
138 << " yo: " << inset->yo() - inset->ascent()
140 << inset->yo() + inset->descent()
144 << BOOST_CURRENT_FUNCTION
145 << ": inset has no cached position" << endl;
147 if (inset->covers(x, y)) {
149 << BOOST_CURRENT_FUNCTION
150 << ": Hit inset: " << inset << endl;
155 << BOOST_CURRENT_FUNCTION
156 << ": No inset hit. " << endl;
162 // Gets the fully instantiated font at a given position in a paragraph
163 // Basically the same routine as Paragraph::getFont() in paragraph.C.
164 // The difference is that this one is used for displaying, and thus we
165 // are allowed to make cosmetic improvements. For instance make footnotes
167 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
169 BOOST_ASSERT(pos >= 0);
171 LyXLayout_ptr const & layout = par.layout();
175 BufferParams const & params = bv()->buffer()->params();
176 pos_type const body_pos = par.beginOfBody();
178 // We specialize the 95% common case:
179 if (!par.getDepth()) {
180 LyXFont f = par.getFontSettings(params, pos);
185 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
186 lf = layout->labelfont;
187 rlf = layout->reslabelfont;
190 rlf = layout->resfont;
192 // In case the default family has been customized
193 if (lf.family() == LyXFont::INHERIT_FAMILY)
194 rlf.setFamily(params.getFont().family());
195 return f.realize(rlf);
198 // The uncommon case need not be optimized as much
201 layoutfont = layout->labelfont;
203 layoutfont = layout->font;
205 LyXFont font = par.getFontSettings(params, pos);
206 font.realize(layoutfont);
209 applyOuterFont(font);
211 // Find the pit value belonging to paragraph. This will not break
212 // even if pars_ would not be a vector anymore.
213 // Performance appears acceptable.
215 pit_type pit = pars_.size();
216 for (pit_type it = 0; it < pit; ++it)
217 if (&pars_[it] == &par) {
221 // Realize against environment font information
222 // NOTE: the cast to pit_type should be removed when pit_type
223 // changes to a unsigned integer.
224 if (pit < pit_type(pars_.size()))
225 font.realize(outerFont(pit, pars_));
227 // Realize with the fonts of lesser depth.
228 font.realize(params.getFont());
233 // There are currently two font mechanisms in LyX:
234 // 1. The font attributes in a lyxtext, and
235 // 2. The inset-specific font properties, defined in an inset's
236 // metrics() and draw() methods and handed down the inset chain through
237 // the pi/mi parameters, and stored locally in a lyxtext in font_.
238 // This is where the two are integrated in the final fully realized
240 void LyXText::applyOuterFont(LyXFont & font) const {
242 lf.reduce(bv()->buffer()->params().getFont());
244 lf.setLanguage(font.language());
249 LyXFont LyXText::getLayoutFont(pit_type const pit) const
251 LyXLayout_ptr const & layout = pars_[pit].layout();
253 if (!pars_[pit].getDepth()) {
254 LyXFont lf = layout->resfont;
255 // In case the default family has been customized
256 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
257 lf.setFamily(bv()->buffer()->params().getFont().family());
261 LyXFont font = layout->font;
262 // Realize with the fonts of lesser depth.
263 //font.realize(outerFont(pit, paragraphs()));
264 font.realize(bv()->buffer()->params().getFont());
270 LyXFont LyXText::getLabelFont(Paragraph const & par) const
272 LyXLayout_ptr const & layout = par.layout();
274 if (!par.getDepth()) {
275 LyXFont lf = layout->reslabelfont;
276 // In case the default family has been customized
277 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
278 lf.setFamily(bv()->buffer()->params().getFont().family());
282 LyXFont font = layout->labelfont;
283 // Realize with the fonts of lesser depth.
284 font.realize(bv()->buffer()->params().getFont());
290 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
293 LyXLayout_ptr const & layout = pars_[pit].layout();
295 // Get concrete layout font to reduce against
298 if (pos < pars_[pit].beginOfBody())
299 layoutfont = layout->labelfont;
301 layoutfont = layout->font;
303 // Realize against environment font information
304 if (pars_[pit].getDepth()) {
306 while (!layoutfont.resolved() &&
307 tp != pit_type(paragraphs().size()) &&
308 pars_[tp].getDepth()) {
309 tp = outerHook(tp, paragraphs());
310 if (tp != pit_type(paragraphs().size()))
311 layoutfont.realize(pars_[tp].layout()->font);
315 // Inside inset, apply the inset's font attributes if any
318 layoutfont.realize(font_);
320 layoutfont.realize(bv()->buffer()->params().getFont());
322 // Now, reduce font against full layout font
323 font.reduce(layoutfont);
325 pars_[pit].setFont(pos, font);
329 // return past-the-last paragraph influenced by a layout change on pit
330 pit_type LyXText::undoSpan(pit_type pit)
332 pit_type end = paragraphs().size();
333 pit_type nextpit = pit + 1;
336 //because of parindents
337 if (!pars_[pit].getDepth())
338 return boost::next(nextpit);
339 //because of depth constrains
340 for (; nextpit != end; ++pit, ++nextpit) {
341 if (!pars_[pit].getDepth())
348 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
350 BOOST_ASSERT(start != end);
352 BufferParams const & bufparams = bv()->buffer()->params();
353 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
355 for (pit_type pit = start; pit != end; ++pit) {
356 pars_[pit].applyLayout(lyxlayout);
357 if (lyxlayout->margintype == MARGIN_MANUAL)
358 pars_[pit].setLabelWidthString(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 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_BEGIN));
375 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_END_SELECT));
376 theApp->lyxFunc().dispatch(FuncRequest(LFUN_CUT));
377 InsetBase * inset = new InsetEnvironment(params, layout);
378 insertInset(cur, inset);
379 //inset->edit(cur, true);
380 //theApp->lyxFunc().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);
641 par.setLabelWidthString(labelwidthstring);
642 params.noindent(noindent);
647 // this really should just insert the inset and not move the cursor.
648 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
650 BOOST_ASSERT(this == cur.text());
652 cur.paragraph().insertInset(cur.pos(), inset);
656 // needed to insert the selection
657 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
659 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
660 current_font, str, autoBreakRows_);
664 // turn double CR to single CR, others are converted into one
665 // blank. Then insertStringAsLines is called
666 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
668 docstring linestr = str;
669 bool newline_inserted = false;
671 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
672 if (linestr[i] == '\n') {
673 if (newline_inserted) {
674 // we know that \r will be ignored by
675 // insertStringAsLines. Of course, it is a dirty
676 // trick, but it works...
677 linestr[i - 1] = '\r';
681 newline_inserted = true;
683 } else if (isPrintable(linestr[i])) {
684 newline_inserted = false;
687 insertStringAsLines(cur, linestr);
691 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
692 bool setfont, bool boundary)
695 setCursorIntern(cur, par, pos, setfont, boundary);
696 return deleteEmptyParagraphMechanism(cur, old);
700 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
702 BOOST_ASSERT(par != int(paragraphs().size()));
706 // now some strict checking
707 Paragraph & para = getPar(par);
709 // None of these should happen, but we're scaredy-cats
711 lyxerr << "dont like -1" << endl;
715 if (pos > para.size()) {
716 lyxerr << "dont like 1, pos: " << pos
717 << " size: " << para.size()
718 << " par: " << par << endl;
724 void LyXText::setCursorIntern(LCursor & cur,
725 pit_type par, pos_type pos, bool setfont, bool boundary)
727 BOOST_ASSERT(this == cur.text());
728 cur.boundary(boundary);
729 setCursor(cur.top(), par, pos);
736 void LyXText::setCurrentFont(LCursor & cur)
738 BOOST_ASSERT(this == cur.text());
739 pos_type pos = cur.pos();
740 Paragraph & par = cur.paragraph();
742 if (cur.boundary() && pos > 0)
746 if (pos == cur.lastpos())
748 else // potentional bug... BUG (Lgb)
749 if (par.isSeparator(pos)) {
750 if (pos > cur.textRow().pos() &&
751 bidi.level(pos) % 2 ==
752 bidi.level(pos - 1) % 2)
754 else if (pos + 1 < cur.lastpos())
759 BufferParams const & bufparams = cur.buffer().params();
760 current_font = par.getFontSettings(bufparams, pos);
761 real_current_font = getFont(par, pos);
763 if (cur.pos() == cur.lastpos()
764 && bidi.isBoundary(cur.buffer(), par, cur.pos())
765 && !cur.boundary()) {
766 Language const * lang = par.getParLanguage(bufparams);
767 current_font.setLanguage(lang);
768 current_font.setNumber(LyXFont::OFF);
769 real_current_font.setLanguage(lang);
770 real_current_font.setNumber(LyXFont::OFF);
775 // x is an absolute screen coord
776 // returns the column near the specified x-coordinate of the row
777 // x is set to the real beginning of this column
778 pos_type LyXText::getColumnNearX(pit_type const pit,
779 Row const & row, int & x, bool & boundary) const
781 int const xo = theCoords.get(this, pit).x_;
783 RowMetrics const r = computeRowMetrics(pit, row);
784 Paragraph const & par = pars_[pit];
786 pos_type vc = row.pos();
787 pos_type end = row.endpos();
789 LyXLayout_ptr const & layout = par.layout();
791 bool left_side = false;
793 pos_type body_pos = par.beginOfBody();
796 double last_tmpx = tmpx;
799 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
802 // check for empty row
808 lyx::frontend::FontMetrics const & fm = theApp->fontLoader().metrics(
811 while (vc < end && tmpx <= x) {
812 c = bidi.vis2log(vc);
814 if (body_pos > 0 && c == body_pos - 1) {
815 string lsep = layout->labelsep;
816 docstring dlsep(lsep.begin(), lsep.end());
817 tmpx += r.label_hfill + fm.width(dlsep);
818 if (par.isLineSeparator(body_pos - 1))
819 tmpx -= singleWidth(par, body_pos - 1);
822 if (hfillExpansion(par, row, c)) {
823 tmpx += singleWidth(par, c);
827 tmpx += r.label_hfill;
828 } else if (par.isSeparator(c)) {
829 tmpx += singleWidth(par, c);
833 tmpx += singleWidth(par, c);
838 if ((tmpx + last_tmpx) / 2 > x) {
843 BOOST_ASSERT(vc <= end); // This shouldn't happen.
846 // This (rtl_support test) is not needed, but gives
847 // some speedup if rtl_support == false
848 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
850 // If lastrow is false, we don't need to compute
852 bool const rtl = lastrow ? isRTL(par) : false;
854 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
855 (!rtl && !left_side && vc == end && x > tmpx + 5)))
857 else if (vc == row.pos()) {
858 c = bidi.vis2log(vc);
859 if (bidi.level(c) % 2 == 1)
862 c = bidi.vis2log(vc - 1);
863 bool const rtl = (bidi.level(c) % 2 == 1);
864 if (left_side == rtl) {
866 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
870 // I believe this code is not needed anymore (Jug 20050717)
872 // The following code is necessary because the cursor position past
873 // the last char in a row is logically equivalent to that before
874 // the first char in the next row. That's why insets causing row
875 // divisions -- Newline and display-style insets -- must be treated
876 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
877 // Newline inset, air gap below:
878 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
879 if (bidi.level(end -1) % 2 == 0)
880 tmpx -= singleWidth(par, end - 1);
882 tmpx += singleWidth(par, end - 1);
886 // Air gap above display inset:
887 if (row.pos() < end && c >= end && end < par.size()
888 && par.isInset(end) && par.getInset(end)->display()) {
891 // Air gap below display inset:
892 if (row.pos() < end && c >= end && par.isInset(end - 1)
893 && par.getInset(end - 1)->display()) {
899 pos_type const col = c - row.pos();
901 if (!c || end == par.size())
904 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
909 return min(col, end - 1 - row.pos());
913 // y is screen coordinate
914 pit_type LyXText::getPitNearY(int y) const
916 BOOST_ASSERT(!paragraphs().empty());
917 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
918 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
920 << BOOST_CURRENT_FUNCTION
921 << ": y: " << y << " cache size: " << cc.size()
924 // look for highest numbered paragraph with y coordinate less than given y
927 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
928 CoordCache::InnerParPosCache::const_iterator et = cc.end();
929 for (; it != et; ++it) {
931 << BOOST_CURRENT_FUNCTION
932 << " examining: pit: " << it->first
933 << " y: " << it->second.y_
936 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
943 << BOOST_CURRENT_FUNCTION
944 << ": found best y: " << yy << " for pit: " << pit
951 Row const & LyXText::getRowNearY(int y, pit_type pit) const
953 Paragraph const & par = pars_[pit];
954 int yy = theCoords.get(this, pit).y_ - par.ascent();
955 BOOST_ASSERT(!par.rows().empty());
956 RowList::const_iterator rit = par.rows().begin();
957 RowList::const_iterator const rlast = boost::prior(par.rows().end());
958 for (; rit != rlast; yy += rit->height(), ++rit)
959 if (yy + rit->height() > y)
965 // x,y are absolute screen coordinates
966 // sets cursor recursively descending into nested editable insets
967 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
969 pit_type pit = getPitNearY(y);
970 BOOST_ASSERT(pit != -1);
971 Row const & row = getRowNearY(y, pit);
974 int xx = x; // is modified by getColumnNearX
975 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
981 // try to descend into nested insets
982 InsetBase * inset = checkInsetHit(x, y);
983 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
985 // Either we deconst editXY or better we move current_font
986 // and real_current_font to LCursor
991 // This should be just before or just behind the
992 // cursor position set above.
993 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
994 || inset == pars_[pit].getInset(pos));
995 // Make sure the cursor points to the position before
997 if (inset == pars_[pit].getInset(pos - 1))
999 inset = inset->editXY(cur, x, y);
1000 if (cur.top().text() == this)
1001 setCurrentFont(cur);
1006 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1008 if (cur.selection())
1010 if (cur.pos() == cur.lastpos())
1012 InsetBase * inset = cur.nextInset();
1013 if (!isHighlyEditableInset(inset))
1015 inset->edit(cur, front);
1020 bool LyXText::cursorLeft(LCursor & cur)
1022 if (!cur.boundary() && cur.pos() > 0 &&
1023 cur.textRow().pos() == cur.pos() &&
1024 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1025 !cur.paragraph().isNewline(cur.pos()-1)) {
1026 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1028 if (cur.pos() != 0) {
1029 bool boundary = cur.boundary();
1030 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1031 if (!checkAndActivateInset(cur, false)) {
1032 if (false && !boundary &&
1033 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1035 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1037 return updateNeeded;
1040 if (cur.pit() != 0) {
1041 // Steps into the paragraph above
1042 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1048 bool LyXText::cursorRight(LCursor & cur)
1050 if (cur.pos() != cur.lastpos()) {
1052 return setCursor(cur, cur.pit(), cur.pos(),
1055 bool updateNeeded = false;
1056 if (!checkAndActivateInset(cur, true)) {
1057 if (cur.textRow().endpos() == cur.pos() + 1 &&
1058 cur.textRow().endpos() != cur.lastpos() &&
1059 !cur.paragraph().isLineSeparator(cur.pos()) &&
1060 !cur.paragraph().isNewline(cur.pos())) {
1063 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1064 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1066 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1068 return updateNeeded;
1071 if (cur.pit() != cur.lastpit())
1072 return setCursor(cur, cur.pit() + 1, 0);
1077 bool LyXText::cursorUp(LCursor & cur)
1079 Paragraph const & par = cur.paragraph();
1081 int const x = cur.targetX();
1083 if (cur.pos() && cur.boundary())
1084 row = par.pos2row(cur.pos()-1);
1086 row = par.pos2row(cur.pos());
1088 if (!cur.selection()) {
1089 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1091 // Go to middle of previous row. 16 found to work OK;
1092 // 12 = top/bottom margin of display math
1093 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1094 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1095 cur.clearSelection();
1097 // This happens when you move out of an inset.
1098 // And to give the DEPM the possibility of doing
1099 // something we must provide it with two different
1101 LCursor dummy = cur;
1105 return deleteEmptyParagraphMechanism(dummy, old);
1108 bool updateNeeded = false;
1111 updateNeeded |= setCursor(cur, cur.pit(),
1112 x2pos(cur.pit(), row - 1, x));
1113 } else if (cur.pit() > 0) {
1115 //cannot use 'par' now
1116 updateNeeded |= setCursor(cur, cur.pit(),
1117 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1122 return updateNeeded;
1126 bool LyXText::cursorDown(LCursor & cur)
1128 Paragraph const & par = cur.paragraph();
1130 int const x = cur.targetX();
1132 if (cur.pos() && cur.boundary())
1133 row = par.pos2row(cur.pos()-1);
1135 row = par.pos2row(cur.pos());
1137 if (!cur.selection()) {
1138 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1140 // To middle of next row
1141 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1142 editXY(cur, x, y + par.rows()[row].descent() + margin);
1143 cur.clearSelection();
1145 // This happens when you move out of an inset.
1146 // And to give the DEPM the possibility of doing
1147 // something we must provide it with two different
1149 LCursor dummy = cur;
1153 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1155 // Make sure that cur gets back whatever happened to dummy(Lgb)
1162 bool updateNeeded = false;
1164 if (row + 1 < int(par.rows().size())) {
1165 updateNeeded |= setCursor(cur, cur.pit(),
1166 x2pos(cur.pit(), row + 1, x));
1167 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1169 updateNeeded |= setCursor(cur, cur.pit(),
1170 x2pos(cur.pit(), 0, x));
1175 return updateNeeded;
1179 bool LyXText::cursorUpParagraph(LCursor & cur)
1181 bool updated = false;
1183 updated = setCursor(cur, cur.pit(), 0);
1184 else if (cur.pit() != 0)
1185 updated = setCursor(cur, cur.pit() - 1, 0);
1190 bool LyXText::cursorDownParagraph(LCursor & cur)
1192 bool updated = false;
1193 if (cur.pit() != cur.lastpit())
1194 updated = setCursor(cur, cur.pit() + 1, 0);
1196 updated = setCursor(cur, cur.pit(), cur.lastpos());
1201 // fix the cursor `cur' after a characters has been deleted at `where'
1202 // position. Called by deleteEmptyParagraphMechanism
1203 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1205 // Do nothing if cursor is not in the paragraph where the
1206 // deletion occured,
1207 if (cur.pit() != where.pit())
1210 // If cursor position is after the deletion place update it
1211 if (cur.pos() > where.pos())
1214 // Check also if we don't want to set the cursor on a spot behind the
1215 // pagragraph because we erased the last character.
1216 if (cur.pos() > cur.lastpos())
1217 cur.pos() = cur.lastpos();
1221 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1223 // Would be wrong to delete anything if we have a selection.
1224 if (cur.selection())
1227 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1228 // old should point to us
1229 BOOST_ASSERT(old.text() == this);
1231 Paragraph & oldpar = old.paragraph();
1233 // We allow all kinds of "mumbo-jumbo" when freespacing.
1234 if (oldpar.isFreeSpacing())
1237 /* Ok I'll put some comments here about what is missing.
1238 There are still some small problems that can lead to
1239 double spaces stored in the document file or space at
1240 the beginning of paragraphs(). This happens if you have
1241 the cursor between to spaces and then save. Or if you
1242 cut and paste and the selection have a space at the
1243 beginning and then save right after the paste. (Lgb)
1246 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1247 // delete the LineSeparator.
1250 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1251 // delete the LineSeparator.
1254 bool const same_inset = &old.inset() == &cur.inset();
1255 bool const same_par = same_inset && old.pit() == cur.pit();
1256 bool const same_par_pos = same_par && old.pos() == cur.pos();
1258 // If the chars around the old cursor were spaces, delete one of them.
1259 if (!same_par_pos) {
1260 // Only if the cursor has really moved.
1262 && old.pos() < oldpar.size()
1263 && oldpar.isLineSeparator(old.pos())
1264 && oldpar.isLineSeparator(old.pos() - 1)
1265 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1266 // We need to set the text to Change::INSERTED to
1267 // get it erased properly
1268 oldpar.setChange(old.pos() -1, Change::INSERTED);
1269 oldpar.erase(old.pos() - 1);
1270 #ifdef WITH_WARNINGS
1271 #warning This will not work anymore when we have multiple views of the same buffer
1272 // In this case, we will have to correct also the cursors held by
1273 // other bufferviews. It will probably be easier to do that in a more
1274 // automated way in CursorSlice code. (JMarc 26/09/2001)
1276 // correct all cursor parts
1278 fixCursorAfterDelete(cur.top(), old.top());
1285 // only do our magic if we changed paragraph
1289 // don't delete anything if this is the ONLY paragraph!
1290 if (old.lastpit() == 0)
1293 // Do not delete empty paragraphs with keepempty set.
1294 if (oldpar.allowEmpty())
1297 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1299 recordUndo(old, Undo::ATOMIC,
1300 max(old.pit() - 1, pit_type(0)),
1301 min(old.pit() + 1, old.lastpit()));
1302 ParagraphList & plist = old.text()->paragraphs();
1303 plist.erase(boost::next(plist.begin(), old.pit()));
1305 // see #warning above
1306 if (cur.depth() >= old.depth()) {
1307 CursorSlice & curslice = cur[old.depth() - 1];
1308 if (&curslice.inset() == &old.inset()
1309 && curslice.pit() > old.pit()) {
1311 // since a paragraph has been deleted, all the
1312 // insets after `old' have been copied and
1313 // their address has changed. Therefore we
1314 // need to `regenerate' cur. (JMarc)
1315 cur.updateInsets(&(cur.bottom().inset()));
1319 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1320 //ParIterator par_it(old);
1321 //updateLabels(old.buffer(), par_it);
1322 // So for now we do the full update:
1323 updateLabels(old.buffer());
1327 if (oldpar.stripLeadingSpaces())
1334 void LyXText::recUndo(pit_type first, pit_type last) const
1336 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1340 void LyXText::recUndo(pit_type par) const
1342 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1346 int defaultRowHeight()
1348 return int(theApp->fontLoader().metrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);