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),
83 void LyXText::init(BufferView * bv)
87 maxwidth_ = bv->workWidth();
92 pit_type const end = paragraphs().size();
93 for (pit_type pit = 0; pit != end; ++pit)
94 pars_[pit].rows().clear();
96 updateLabels(*bv->buffer());
100 bool LyXText::isMainText() const
102 return &bv()->buffer()->text() == this;
106 //takes screen x,y coordinates
107 InsetBase * LyXText::checkInsetHit(int x, int y) const
109 pit_type pit = getPitNearY(y);
110 BOOST_ASSERT(pit != -1);
112 Paragraph const & par = pars_[pit];
115 << BOOST_CURRENT_FUNCTION
120 InsetList::const_iterator iit = par.insetlist.begin();
121 InsetList::const_iterator iend = par.insetlist.end();
122 for (; iit != iend; ++iit) {
123 InsetBase * inset = iit->inset;
126 << BOOST_CURRENT_FUNCTION
127 << ": examining inset " << inset << endl;
129 if (bv()->coordCache().getInsets().has(inset))
131 << BOOST_CURRENT_FUNCTION
132 << ": xo: " << inset->xo(*bv()) << "..."
133 << inset->xo(*bv()) + inset->width()
134 << " yo: " << inset->yo(*bv()) - inset->ascent()
136 << inset->yo(*bv()) + inset->descent()
140 << BOOST_CURRENT_FUNCTION
141 << ": inset has no cached position" << endl;
143 if (inset->covers(*bv(), x, y)) {
145 << BOOST_CURRENT_FUNCTION
146 << ": Hit inset: " << inset << endl;
151 << BOOST_CURRENT_FUNCTION
152 << ": No inset hit. " << endl;
158 // Gets the fully instantiated font at a given position in a paragraph
159 // Basically the same routine as Paragraph::getFont() in paragraph.C.
160 // The difference is that this one is used for displaying, and thus we
161 // are allowed to make cosmetic improvements. For instance make footnotes
163 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
165 BOOST_ASSERT(pos >= 0);
167 LyXLayout_ptr const & layout = par.layout();
171 BufferParams const & params = bv()->buffer()->params();
172 pos_type const body_pos = par.beginOfBody();
174 // We specialize the 95% common case:
175 if (!par.getDepth()) {
176 LyXFont f = par.getFontSettings(params, pos);
181 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
182 lf = layout->labelfont;
183 rlf = layout->reslabelfont;
186 rlf = layout->resfont;
188 // In case the default family has been customized
189 if (lf.family() == LyXFont::INHERIT_FAMILY)
190 rlf.setFamily(params.getFont().family());
191 return f.realize(rlf);
194 // The uncommon case need not be optimized as much
197 layoutfont = layout->labelfont;
199 layoutfont = layout->font;
201 LyXFont font = par.getFontSettings(params, pos);
202 font.realize(layoutfont);
205 applyOuterFont(font);
207 // Find the pit value belonging to paragraph. This will not break
208 // even if pars_ would not be a vector anymore.
209 // Performance appears acceptable.
211 pit_type pit = pars_.size();
212 for (pit_type it = 0; it < pit; ++it)
213 if (&pars_[it] == &par) {
217 // Realize against environment font information
218 // NOTE: the cast to pit_type should be removed when pit_type
219 // changes to a unsigned integer.
220 if (pit < pit_type(pars_.size()))
221 font.realize(outerFont(pit, pars_));
223 // Realize with the fonts of lesser depth.
224 font.realize(params.getFont());
229 // There are currently two font mechanisms in LyX:
230 // 1. The font attributes in a lyxtext, and
231 // 2. The inset-specific font properties, defined in an inset's
232 // metrics() and draw() methods and handed down the inset chain through
233 // the pi/mi parameters, and stored locally in a lyxtext in font_.
234 // This is where the two are integrated in the final fully realized
236 void LyXText::applyOuterFont(LyXFont & font) const {
238 lf.reduce(bv()->buffer()->params().getFont());
240 lf.setLanguage(font.language());
245 LyXFont LyXText::getLayoutFont(pit_type const pit) const
247 LyXLayout_ptr const & layout = pars_[pit].layout();
249 if (!pars_[pit].getDepth()) {
250 LyXFont lf = layout->resfont;
251 // In case the default family has been customized
252 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
253 lf.setFamily(bv()->buffer()->params().getFont().family());
257 LyXFont font = layout->font;
258 // Realize with the fonts of lesser depth.
259 //font.realize(outerFont(pit, paragraphs()));
260 font.realize(bv()->buffer()->params().getFont());
266 LyXFont LyXText::getLabelFont(Paragraph const & par) const
268 LyXLayout_ptr const & layout = par.layout();
270 if (!par.getDepth()) {
271 LyXFont lf = layout->reslabelfont;
272 // In case the default family has been customized
273 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
274 lf.setFamily(bv()->buffer()->params().getFont().family());
278 LyXFont font = layout->labelfont;
279 // Realize with the fonts of lesser depth.
280 font.realize(bv()->buffer()->params().getFont());
286 void LyXText::setCharFont(pit_type pit, 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
314 layoutfont.realize(font_);
316 layoutfont.realize(bv()->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(pit_type start, pit_type end, string const & layout)
346 BOOST_ASSERT(start != end);
348 BufferParams const & bufparams = bv()->buffer()->params();
349 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
351 for (pit_type pit = start; pit != end; ++pit) {
352 pars_[pit].applyLayout(lyxlayout);
353 if (lyxlayout->margintype == MARGIN_MANUAL)
354 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
359 // set layout over selection and make a total rebreak of those paragraphs
360 void LyXText::setLayout(LCursor & cur, string const & layout)
362 BOOST_ASSERT(this == cur.text());
363 // special handling of new environment insets
364 BufferView & bv = cur.bv();
365 BufferParams const & params = bv.buffer()->params();
366 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
367 if (lyxlayout->is_environment) {
368 // move everything in a new environment inset
369 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
370 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
371 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
372 lyx::dispatch(FuncRequest(LFUN_CUT));
373 InsetBase * inset = new InsetEnvironment(params, layout);
374 insertInset(cur, inset);
375 //inset->edit(cur, true);
376 //lyx::dispatch(FuncRequest(LFUN_PASTE));
380 pit_type start = cur.selBegin().pit();
381 pit_type end = cur.selEnd().pit() + 1;
382 pit_type undopit = undoSpan(end - 1);
383 recUndo(start, undopit - 1);
384 setLayout(start, end, layout);
385 updateLabels(cur.buffer());
389 static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
390 Paragraph const & par, int max_depth)
392 if (par.layout()->labeltype == LABEL_BIBLIO)
394 int const depth = par.params().depth();
395 if (type == LyXText::INC_DEPTH && depth < max_depth)
397 if (type == LyXText::DEC_DEPTH && depth > 0)
403 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
405 BOOST_ASSERT(this == cur.text());
406 // this happens when selecting several cells in tabular (bug 2630)
407 if (cur.selBegin().idx() != cur.selEnd().idx())
410 pit_type const beg = cur.selBegin().pit();
411 pit_type const end = cur.selEnd().pit() + 1;
412 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
414 for (pit_type pit = beg; pit != end; ++pit) {
415 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
417 max_depth = pars_[pit].getMaxDepthAfter();
423 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
425 BOOST_ASSERT(this == cur.text());
426 pit_type const beg = cur.selBegin().pit();
427 pit_type const end = cur.selEnd().pit() + 1;
428 recordUndoSelection(cur);
429 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
431 for (pit_type pit = beg; pit != end; ++pit) {
432 Paragraph & par = pars_[pit];
433 if (lyx::changeDepthAllowed(type, par, max_depth)) {
434 int const depth = par.params().depth();
435 if (type == INC_DEPTH)
436 par.params().depth(depth + 1);
438 par.params().depth(depth - 1);
440 max_depth = par.getMaxDepthAfter();
442 // this handles the counter labels, and also fixes up
443 // depth values for follow-on (child) paragraphs
444 updateLabels(cur.buffer());
448 // set font over selection
449 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
451 BOOST_ASSERT(this == cur.text());
452 // if there is no selection just set the current_font
453 if (!cur.selection()) {
454 // Determine basis font
456 pit_type pit = cur.pit();
457 if (cur.pos() < pars_[pit].beginOfBody())
458 layoutfont = getLabelFont(pars_[pit]);
460 layoutfont = getLayoutFont(pit);
462 // Update current font
463 real_current_font.update(font,
464 cur.buffer().params().language,
467 // Reduce to implicit settings
468 current_font = real_current_font;
469 current_font.reduce(layoutfont);
470 // And resolve it completely
471 real_current_font.realize(layoutfont);
476 // Ok, we have a selection.
477 recordUndoSelection(cur);
479 DocIterator dit = cur.selectionBegin();
480 DocIterator ditend = cur.selectionEnd();
482 BufferParams const & params = cur.buffer().params();
484 // Don't use forwardChar here as ditend might have
485 // pos() == lastpos() and forwardChar would miss it.
486 // Can't use forwardPos either as this descends into
488 for (; dit != ditend; dit.forwardPosNoDescend()) {
489 if (dit.pos() != dit.lastpos()) {
490 LyXFont f = getFont(dit.paragraph(), dit.pos());
491 f.update(font, params.language, toggleall);
492 setCharFont(dit.pit(), dit.pos(), f);
498 // the cursor set functions have a special mechanism. When they
499 // realize you left an empty paragraph, they will delete it.
501 bool LyXText::cursorHome(LCursor & cur)
503 BOOST_ASSERT(this == cur.text());
504 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
505 return setCursor(cur, cur.pit(), row.pos());
509 bool LyXText::cursorEnd(LCursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 // if not on the last row of the par, put the cursor before
513 // the final space exept if I have a spanning inset or one string
514 // is so long that we force a break.
515 pos_type end = cur.textRow().endpos();
517 // empty text, end-1 is no valid position
519 bool boundary = false;
520 if (end != cur.lastpos()) {
521 if (!cur.paragraph().isLineSeparator(end-1)
522 && !cur.paragraph().isNewline(end-1))
527 return setCursor(cur, cur.pit(), end, true, boundary);
531 bool LyXText::cursorTop(LCursor & cur)
533 BOOST_ASSERT(this == cur.text());
534 return setCursor(cur, 0, 0);
538 bool LyXText::cursorBottom(LCursor & cur)
540 BOOST_ASSERT(this == cur.text());
541 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
545 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
547 BOOST_ASSERT(this == cur.text());
548 // If the mask is completely neutral, tell user
549 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
550 // Could only happen with user style
551 cur.message(_("No font change defined. "
552 "Use Character under the Layout menu to define font change."));
556 // Try implicit word selection
557 // If there is a change in the language the implicit word selection
559 CursorSlice resetCursor = cur.top();
560 bool implicitSelection =
561 font.language() == ignore_language
562 && font.number() == LyXFont::IGNORE
563 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
566 setFont(cur, font, toggleall);
568 // Implicit selections are cleared afterwards
569 // and cursor is set to the original position.
570 if (implicitSelection) {
571 cur.clearSelection();
572 cur.top() = resetCursor;
578 string LyXText::getStringToIndex(LCursor const & cur)
580 BOOST_ASSERT(this == cur.text());
584 idxstring = cur.selectionAsString(false);
586 // Try implicit word selection. If there is a change
587 // in the language the implicit word selection is
589 LCursor tmpcur = cur;
590 selectWord(tmpcur, PREVIOUS_WORD);
592 if (!tmpcur.selection())
593 cur.message(_("Nothing to index!"));
594 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
595 cur.message(_("Cannot index more than one paragraph!"));
597 idxstring = tmpcur.selectionAsString(false);
600 return to_utf8(idxstring);
604 void LyXText::setParagraph(LCursor & cur,
605 Spacing const & spacing, LyXAlignment align,
606 string const & labelwidthstring, bool noindent)
608 BOOST_ASSERT(cur.text());
609 // make sure that the depth behind the selection are restored, too
610 pit_type undopit = undoSpan(cur.selEnd().pit());
611 recUndo(cur.selBegin().pit(), undopit - 1);
613 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
615 Paragraph & par = pars_[pit];
616 ParagraphParameters & params = par.params();
617 params.spacing(spacing);
619 // does the layout allow the new alignment?
620 LyXLayout_ptr const & layout = par.layout();
622 if (align == LYX_ALIGN_LAYOUT)
623 align = layout->align;
624 if (align & layout->alignpossible) {
625 if (align == layout->align)
626 params.align(LYX_ALIGN_LAYOUT);
631 par.setLabelWidthString(from_ascii(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(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(pit_type const pit,
771 Row const & row, int & x, bool & boundary) const
773 int const xo = bv()->coordCache().get(this, pit).x_;
775 RowMetrics const r = computeRowMetrics(pit, row);
776 Paragraph const & par = pars_[pit];
778 pos_type vc = row.pos();
779 pos_type end = row.endpos();
781 LyXLayout_ptr const & layout = par.layout();
783 bool left_side = false;
785 pos_type body_pos = par.beginOfBody();
788 double last_tmpx = tmpx;
791 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
794 // check for empty row
800 frontend::FontMetrics const & fm
801 = theFontMetrics(getLabelFont(par));
803 while (vc < end && tmpx <= x) {
804 c = bidi.vis2log(vc);
806 if (body_pos > 0 && c == body_pos - 1) {
808 docstring const lsep = from_utf8(layout->labelsep);
809 tmpx += r.label_hfill + fm.width(lsep);
810 if (par.isLineSeparator(body_pos - 1))
811 tmpx -= singleWidth(par, body_pos - 1);
814 if (par.hfillExpansion(row, c)) {
815 tmpx += singleWidth(par, c);
819 tmpx += r.label_hfill;
820 } else if (par.isSeparator(c)) {
821 tmpx += singleWidth(par, c);
825 tmpx += singleWidth(par, c);
830 if ((tmpx + last_tmpx) / 2 > x) {
835 BOOST_ASSERT(vc <= end); // This shouldn't happen.
838 // This (rtl_support test) is not needed, but gives
839 // some speedup if rtl_support == false
840 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
842 // If lastrow is false, we don't need to compute
844 bool const rtl = lastrow ? isRTL(par) : false;
846 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
847 (!rtl && !left_side && vc == end && x > tmpx + 5)))
849 else if (vc == row.pos()) {
850 c = bidi.vis2log(vc);
851 if (bidi.level(c) % 2 == 1)
854 c = bidi.vis2log(vc - 1);
855 bool const rtl = (bidi.level(c) % 2 == 1);
856 if (left_side == rtl) {
858 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
862 // I believe this code is not needed anymore (Jug 20050717)
864 // The following code is necessary because the cursor position past
865 // the last char in a row is logically equivalent to that before
866 // the first char in the next row. That's why insets causing row
867 // divisions -- Newline and display-style insets -- must be treated
868 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
869 // Newline inset, air gap below:
870 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
871 if (bidi.level(end -1) % 2 == 0)
872 tmpx -= singleWidth(par, end - 1);
874 tmpx += singleWidth(par, end - 1);
878 // Air gap above display inset:
879 if (row.pos() < end && c >= end && end < par.size()
880 && par.isInset(end) && par.getInset(end)->display()) {
883 // Air gap below display inset:
884 if (row.pos() < end && c >= end && par.isInset(end - 1)
885 && par.getInset(end - 1)->display()) {
891 pos_type const col = c - row.pos();
893 if (!c || end == par.size())
896 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
901 return min(col, end - 1 - row.pos());
905 // y is screen coordinate
906 pit_type LyXText::getPitNearY(int y) const
908 BOOST_ASSERT(!paragraphs().empty());
909 BOOST_ASSERT(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
910 CoordCache::InnerParPosCache const & cc = bv()->coordCache().getParPos().find(this)->second;
912 << BOOST_CURRENT_FUNCTION
913 << ": y: " << y << " cache size: " << cc.size()
916 // look for highest numbered paragraph with y coordinate less than given y
919 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
920 CoordCache::InnerParPosCache::const_iterator et = cc.end();
921 for (; it != et; ++it) {
923 << BOOST_CURRENT_FUNCTION
924 << " examining: pit: " << it->first
925 << " y: " << it->second.y_
928 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
935 << BOOST_CURRENT_FUNCTION
936 << ": found best y: " << yy << " for pit: " << pit
943 Row const & LyXText::getRowNearY(int y, pit_type pit) const
945 Paragraph const & par = pars_[pit];
946 int yy = bv()->coordCache().get(this, pit).y_ - par.ascent();
947 BOOST_ASSERT(!par.rows().empty());
948 RowList::const_iterator rit = par.rows().begin();
949 RowList::const_iterator const rlast = boost::prior(par.rows().end());
950 for (; rit != rlast; yy += rit->height(), ++rit)
951 if (yy + rit->height() > y)
957 // x,y are absolute screen coordinates
958 // sets cursor recursively descending into nested editable insets
959 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
961 if (lyxerr.debugging(Debug::WORKAREA)) {
962 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
963 bv()->coordCache().dump();
965 pit_type pit = getPitNearY(y);
966 BOOST_ASSERT(pit != -1);
967 Row const & row = getRowNearY(y, pit);
970 int xx = x; // is modified by getColumnNearX
971 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
977 // try to descend into nested insets
978 InsetBase * inset = checkInsetHit(x, y);
979 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
981 // Either we deconst editXY or better we move current_font
982 // and real_current_font to LCursor
987 // This should be just before or just behind the
988 // cursor position set above.
989 InsetBase * inset2 = pars_[pit].getInset(pos - 1);
990 InsetBase * inset3 = pars_[pit].getInset(pos);
992 BOOST_ASSERT((pos != 0 && inset == inset2)
994 // Make sure the cursor points to the position before
996 if (inset == pars_[pit].getInset(pos - 1))
998 inset = inset->editXY(cur, x, y);
999 if (cur.top().text() == this)
1000 setCurrentFont(cur);
1005 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1007 if (cur.selection())
1009 if (cur.pos() == cur.lastpos())
1011 InsetBase * inset = cur.nextInset();
1012 if (!isHighlyEditableInset(inset))
1014 inset->edit(cur, front);
1019 bool LyXText::cursorLeft(LCursor & cur)
1021 if (!cur.boundary() && cur.pos() > 0 &&
1022 cur.textRow().pos() == cur.pos() &&
1023 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1024 !cur.paragraph().isNewline(cur.pos()-1)) {
1025 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1027 if (cur.pos() != 0) {
1028 bool boundary = cur.boundary();
1029 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1030 if (!checkAndActivateInset(cur, false)) {
1031 if (false && !boundary &&
1032 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1034 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1036 return updateNeeded;
1039 if (cur.pit() != 0) {
1040 // Steps into the paragraph above
1041 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1047 bool LyXText::cursorRight(LCursor & cur)
1049 if (cur.pos() != cur.lastpos()) {
1051 return setCursor(cur, cur.pit(), cur.pos(),
1054 bool updateNeeded = false;
1055 if (!checkAndActivateInset(cur, true)) {
1056 if (cur.textRow().endpos() == cur.pos() + 1 &&
1057 cur.textRow().endpos() != cur.lastpos() &&
1058 !cur.paragraph().isLineSeparator(cur.pos()) &&
1059 !cur.paragraph().isNewline(cur.pos())) {
1062 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1063 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1065 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1067 return updateNeeded;
1070 if (cur.pit() != cur.lastpit())
1071 return setCursor(cur, cur.pit() + 1, 0);
1076 bool LyXText::cursorUp(LCursor & cur)
1078 Paragraph const & par = cur.paragraph();
1080 int const x = cur.targetX();
1082 if (cur.pos() && cur.boundary())
1083 row = par.pos2row(cur.pos()-1);
1085 row = par.pos2row(cur.pos());
1087 if (!cur.selection()) {
1088 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1090 // Go to middle of previous row. 16 found to work OK;
1091 // 12 = top/bottom margin of display math
1092 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1093 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1094 cur.clearSelection();
1096 // This happens when you move out of an inset.
1097 // And to give the DEPM the possibility of doing
1098 // something we must provide it with two different
1100 LCursor dummy = cur;
1104 return deleteEmptyParagraphMechanism(dummy, old);
1107 bool updateNeeded = false;
1110 updateNeeded |= setCursor(cur, cur.pit(),
1111 x2pos(cur.pit(), row - 1, x));
1112 } else if (cur.pit() > 0) {
1114 //cannot use 'par' now
1115 updateNeeded |= setCursor(cur, cur.pit(),
1116 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1121 return updateNeeded;
1125 bool LyXText::cursorDown(LCursor & cur)
1127 Paragraph const & par = cur.paragraph();
1129 int const x = cur.targetX();
1131 if (cur.pos() && cur.boundary())
1132 row = par.pos2row(cur.pos()-1);
1134 row = par.pos2row(cur.pos());
1136 if (!cur.selection()) {
1137 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1139 // To middle of next row
1140 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1141 editXY(cur, x, y + par.rows()[row].descent() + margin);
1142 cur.clearSelection();
1144 // This happens when you move out of an inset.
1145 // And to give the DEPM the possibility of doing
1146 // something we must provide it with two different
1148 LCursor dummy = cur;
1152 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1154 // Make sure that cur gets back whatever happened to dummy(Lgb)
1161 bool updateNeeded = false;
1163 if (row + 1 < int(par.rows().size())) {
1164 updateNeeded |= setCursor(cur, cur.pit(),
1165 x2pos(cur.pit(), row + 1, x));
1166 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1168 updateNeeded |= setCursor(cur, cur.pit(),
1169 x2pos(cur.pit(), 0, x));
1174 return updateNeeded;
1178 bool LyXText::cursorUpParagraph(LCursor & cur)
1180 bool updated = false;
1182 updated = setCursor(cur, cur.pit(), 0);
1183 else if (cur.pit() != 0)
1184 updated = setCursor(cur, cur.pit() - 1, 0);
1189 bool LyXText::cursorDownParagraph(LCursor & cur)
1191 bool updated = false;
1192 if (cur.pit() != cur.lastpit())
1193 updated = setCursor(cur, cur.pit() + 1, 0);
1195 updated = setCursor(cur, cur.pit(), cur.lastpos());
1200 // fix the cursor `cur' after a characters has been deleted at `where'
1201 // position. Called by deleteEmptyParagraphMechanism
1202 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1204 // Do nothing if cursor is not in the paragraph where the
1205 // deletion occured,
1206 if (cur.pit() != where.pit())
1209 // If cursor position is after the deletion place update it
1210 if (cur.pos() > where.pos())
1213 // Check also if we don't want to set the cursor on a spot behind the
1214 // pagragraph because we erased the last character.
1215 if (cur.pos() > cur.lastpos())
1216 cur.pos() = cur.lastpos();
1220 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1222 // Would be wrong to delete anything if we have a selection.
1223 if (cur.selection())
1226 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1227 // old should point to us
1228 BOOST_ASSERT(old.text() == this);
1230 Paragraph & oldpar = old.paragraph();
1232 // We allow all kinds of "mumbo-jumbo" when freespacing.
1233 if (oldpar.isFreeSpacing())
1236 /* Ok I'll put some comments here about what is missing.
1237 There are still some small problems that can lead to
1238 double spaces stored in the document file or space at
1239 the beginning of paragraphs(). This happens if you have
1240 the cursor between to spaces and then save. Or if you
1241 cut and paste and the selection have a space at the
1242 beginning and then save right after the paste. (Lgb)
1245 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1246 // delete the LineSeparator.
1249 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1250 // delete the LineSeparator.
1253 bool const same_inset = &old.inset() == &cur.inset();
1254 bool const same_par = same_inset && old.pit() == cur.pit();
1255 bool const same_par_pos = same_par && old.pos() == cur.pos();
1257 // If the chars around the old cursor were spaces, delete one of them.
1258 if (!same_par_pos) {
1259 // Only if the cursor has really moved.
1261 && old.pos() < oldpar.size()
1262 && oldpar.isLineSeparator(old.pos())
1263 && oldpar.isLineSeparator(old.pos() - 1)
1264 && oldpar.lookupChange(old.pos() - 1).type != Change::DELETED) {
1265 oldpar.erase(old.pos() - 1, false); // do not track changes in DEPM
1266 #ifdef WITH_WARNINGS
1267 #warning This will not work anymore when we have multiple views of the same buffer
1268 // In this case, we will have to correct also the cursors held by
1269 // other bufferviews. It will probably be easier to do that in a more
1270 // automated way in CursorSlice code. (JMarc 26/09/2001)
1272 // correct all cursor parts
1274 fixCursorAfterDelete(cur.top(), old.top());
1281 // only do our magic if we changed paragraph
1285 // don't delete anything if this is the ONLY paragraph!
1286 if (old.lastpit() == 0)
1289 // Do not delete empty paragraphs with keepempty set.
1290 if (oldpar.allowEmpty())
1293 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1295 recordUndo(old, Undo::ATOMIC,
1296 max(old.pit() - 1, pit_type(0)),
1297 min(old.pit() + 1, old.lastpit()));
1298 ParagraphList & plist = old.text()->paragraphs();
1299 plist.erase(boost::next(plist.begin(), old.pit()));
1301 // see #warning above
1302 if (cur.depth() >= old.depth()) {
1303 CursorSlice & curslice = cur[old.depth() - 1];
1304 if (&curslice.inset() == &old.inset()
1305 && curslice.pit() > old.pit()) {
1307 // since a paragraph has been deleted, all the
1308 // insets after `old' have been copied and
1309 // their address has changed. Therefore we
1310 // need to `regenerate' cur. (JMarc)
1311 cur.updateInsets(&(cur.bottom().inset()));
1315 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1316 //ParIterator par_it(old);
1317 //updateLabels(old.buffer(), par_it);
1318 // So for now we do the full update:
1319 updateLabels(old.buffer());
1323 if (oldpar.stripLeadingSpaces())
1330 void LyXText::recUndo(pit_type first, pit_type last) const
1332 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1336 void LyXText::recUndo(pit_type par) const
1338 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1342 int defaultRowHeight()
1344 return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);