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 "bufferparams.h"
27 #include "BufferView.h"
29 #include "coordcache.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
52 #include "insets/insetenv.h"
54 #include "support/textutils.h"
56 #include <boost/current_function.hpp>
65 using std::ostringstream;
71 LyXText::LyXText(BufferView * bv)
72 : maxwidth_(bv ? bv->workWidth() : 100),
73 current_font(LyXFont::ALL_INHERIT),
74 background_color_(LColor::background),
80 void LyXText::init(BufferView * bv)
84 maxwidth_ = bv->workWidth();
89 pit_type const end = paragraphs().size();
90 for (pit_type pit = 0; pit != end; ++pit)
91 pars_[pit].rows().clear();
93 current_font = getFont(pars_[0], 0);
94 updateLabels(*bv->buffer());
98 bool LyXText::isMainText() const
100 return &bv()->buffer()->text() == this;
104 //takes screen x,y coordinates
105 InsetBase * LyXText::checkInsetHit(int x, int y) const
107 pit_type pit = getPitNearY(y);
108 BOOST_ASSERT(pit != -1);
110 Paragraph const & par = pars_[pit];
113 << BOOST_CURRENT_FUNCTION
118 InsetList::const_iterator iit = par.insetlist.begin();
119 InsetList::const_iterator iend = par.insetlist.end();
120 for (; iit != iend; ++iit) {
121 InsetBase * inset = iit->inset;
124 << BOOST_CURRENT_FUNCTION
125 << ": examining inset " << inset << endl;
127 if (theCoords.getInsets().has(inset))
129 << BOOST_CURRENT_FUNCTION
130 << ": xo: " << inset->xo() << "..."
131 << inset->xo() + inset->width()
132 << " yo: " << inset->yo() - inset->ascent()
134 << inset->yo() + inset->descent()
138 << BOOST_CURRENT_FUNCTION
139 << ": inset has no cached position" << endl;
141 if (inset->covers(x, y)) {
143 << BOOST_CURRENT_FUNCTION
144 << ": Hit inset: " << inset << endl;
149 << BOOST_CURRENT_FUNCTION
150 << ": No inset hit. " << endl;
156 // Gets the fully instantiated font at a given position in a paragraph
157 // Basically the same routine as Paragraph::getFont() in paragraph.C.
158 // The difference is that this one is used for displaying, and thus we
159 // are allowed to make cosmetic improvements. For instance make footnotes
161 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
163 BOOST_ASSERT(pos >= 0);
165 LyXLayout_ptr const & layout = par.layout();
169 BufferParams const & params = bv()->buffer()->params();
170 pos_type const body_pos = par.beginOfBody();
172 // We specialize the 95% common case:
173 if (!par.getDepth()) {
174 LyXFont f = par.getFontSettings(params, pos);
179 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
180 lf = layout->labelfont;
181 rlf = layout->reslabelfont;
184 rlf = layout->resfont;
186 // In case the default family has been customized
187 if (lf.family() == LyXFont::INHERIT_FAMILY)
188 rlf.setFamily(params.getFont().family());
189 return f.realize(rlf);
192 // The uncommon case need not be optimized as much
195 layoutfont = layout->labelfont;
197 layoutfont = layout->font;
199 LyXFont font = par.getFontSettings(params, pos);
200 font.realize(layoutfont);
203 applyOuterFont(font);
205 // Find the pit value belonging to paragraph. This will not break
206 // even if pars_ would not be a vector anymore.
207 // Performance appears acceptable.
209 pit_type pit = pars_.size();
210 for (pit_type it = 0; it < pit; ++it)
211 if (&pars_[it] == &par) {
215 // Realize against environment font information
216 // NOTE: the cast to pit_type should be removed when pit_type
217 // changes to a unsigned integer.
218 if (pit < pit_type(pars_.size()))
219 font.realize(outerFont(pit, pars_));
221 // Realize with the fonts of lesser depth.
222 font.realize(params.getFont());
227 // There are currently two font mechanisms in LyX:
228 // 1. The font attributes in a lyxtext, and
229 // 2. The inset-specific font properties, defined in an inset's
230 // metrics() and draw() methods and handed down the inset chain through
231 // the pi/mi parameters, and stored locally in a lyxtext in font_.
232 // This is where the two are integrated in the final fully realized
234 void LyXText::applyOuterFont(LyXFont & font) const {
236 lf.reduce(bv()->buffer()->params().getFont());
238 lf.setLanguage(font.language());
243 LyXFont LyXText::getLayoutFont(pit_type const pit) const
245 LyXLayout_ptr const & layout = pars_[pit].layout();
247 if (!pars_[pit].getDepth()) {
248 LyXFont lf = layout->resfont;
249 // In case the default family has been customized
250 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
251 lf.setFamily(bv()->buffer()->params().getFont().family());
255 LyXFont font = layout->font;
256 // Realize with the fonts of lesser depth.
257 //font.realize(outerFont(pit, paragraphs()));
258 font.realize(bv()->buffer()->params().getFont());
264 LyXFont LyXText::getLabelFont(Paragraph const & par) const
266 LyXLayout_ptr const & layout = par.layout();
268 if (!par.getDepth()) {
269 LyXFont lf = layout->reslabelfont;
270 // In case the default family has been customized
271 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
272 lf.setFamily(bv()->buffer()->params().getFont().family());
276 LyXFont font = layout->labelfont;
277 // Realize with the fonts of lesser depth.
278 font.realize(bv()->buffer()->params().getFont());
284 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
287 LyXLayout_ptr const & layout = pars_[pit].layout();
289 // Get concrete layout font to reduce against
292 if (pos < pars_[pit].beginOfBody())
293 layoutfont = layout->labelfont;
295 layoutfont = layout->font;
297 // Realize against environment font information
298 if (pars_[pit].getDepth()) {
300 while (!layoutfont.resolved() &&
301 tp != pit_type(paragraphs().size()) &&
302 pars_[tp].getDepth()) {
303 tp = outerHook(tp, paragraphs());
304 if (tp != pit_type(paragraphs().size()))
305 layoutfont.realize(pars_[tp].layout()->font);
309 // Inside inset, apply the inset's font attributes if any
312 layoutfont.realize(font_);
314 layoutfont.realize(bv()->buffer()->params().getFont());
316 // Now, reduce font against full layout font
317 font.reduce(layoutfont);
319 pars_[pit].setFont(pos, font);
323 // return past-the-last paragraph influenced by a layout change on pit
324 pit_type LyXText::undoSpan(pit_type pit)
326 pit_type end = paragraphs().size();
327 pit_type nextpit = pit + 1;
330 //because of parindents
331 if (!pars_[pit].getDepth())
332 return boost::next(nextpit);
333 //because of depth constrains
334 for (; nextpit != end; ++pit, ++nextpit) {
335 if (!pars_[pit].getDepth())
342 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
344 BOOST_ASSERT(start != end);
346 BufferParams const & bufparams = bv()->buffer()->params();
347 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
349 for (pit_type pit = start; pit != end; ++pit) {
350 pars_[pit].applyLayout(lyxlayout);
351 if (lyxlayout->margintype == MARGIN_MANUAL)
352 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
357 // set layout over selection and make a total rebreak of those paragraphs
358 void LyXText::setLayout(LCursor & cur, string const & layout)
360 BOOST_ASSERT(this == cur.text());
361 // special handling of new environment insets
362 BufferView & bv = cur.bv();
363 BufferParams const & params = bv.buffer()->params();
364 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
365 if (lyxlayout->is_environment) {
366 // move everything in a new environment inset
367 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
368 bv.owner()->dispatch(FuncRequest(LFUN_LINE_BEGIN));
369 bv.owner()->dispatch(FuncRequest(LFUN_LINE_END_SELECT));
370 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
371 InsetBase * inset = new InsetEnvironment(params, layout);
372 insertInset(cur, inset);
373 //inset->edit(cur, true);
374 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
378 pit_type start = cur.selBegin().pit();
379 pit_type end = cur.selEnd().pit() + 1;
380 pit_type undopit = undoSpan(end - 1);
381 recUndo(start, undopit - 1);
382 setLayout(start, end, layout);
383 updateLabels(cur.buffer());
390 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
391 Paragraph const & par, int max_depth)
393 if (par.layout()->labeltype == LABEL_BIBLIO)
395 int const depth = par.params().depth();
396 if (type == LyXText::INC_DEPTH && depth < max_depth)
398 if (type == LyXText::DEC_DEPTH && depth > 0)
407 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
409 BOOST_ASSERT(this == cur.text());
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 (::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 (::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());
506 return setCursor(cur, cur.pit(), row.pos());
510 bool LyXText::cursorEnd(LCursor & cur)
512 BOOST_ASSERT(this == cur.text());
513 // if not on the last row of the par, put the cursor before
514 // the final space exept if I have a spanning inset or one string
515 // is so long that we force a break.
516 pos_type end = cur.textRow().endpos();
518 // empty text, end-1 is no valid position
520 bool boundary = false;
521 if (end != cur.lastpos()) {
522 if (!cur.paragraph().isLineSeparator(end-1)
523 && !cur.paragraph().isNewline(end-1))
528 return setCursor(cur, cur.pit(), end, true, boundary);
532 bool LyXText::cursorTop(LCursor & cur)
534 BOOST_ASSERT(this == cur.text());
535 return setCursor(cur, 0, 0);
539 bool LyXText::cursorBottom(LCursor & cur)
541 BOOST_ASSERT(this == cur.text());
542 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
546 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
548 BOOST_ASSERT(this == cur.text());
549 // If the mask is completely neutral, tell user
550 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
551 // Could only happen with user style
552 cur.message(_("No font change defined. "
553 "Use Character under the Layout menu to define font change."));
557 // Try implicit word selection
558 // If there is a change in the language the implicit word selection
560 CursorSlice resetCursor = cur.top();
561 bool implicitSelection =
562 font.language() == ignore_language
563 && font.number() == LyXFont::IGNORE
564 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
567 setFont(cur, font, toggleall);
569 // Implicit selections are cleared afterwards
570 // and cursor is set to the original position.
571 if (implicitSelection) {
572 cur.clearSelection();
573 cur.top() = resetCursor;
579 string LyXText::getStringToIndex(LCursor const & cur)
581 BOOST_ASSERT(this == cur.text());
584 if (cur.selection()) {
585 idxstring = cur.selectionAsString(false);
587 // Try implicit word selection. If there is a change
588 // in the language the implicit word selection is
590 LCursor tmpcur = cur;
591 selectWord(tmpcur, lyx::PREVIOUS_WORD);
593 if (!tmpcur.selection())
594 cur.message(_("Nothing to index!"));
595 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
596 cur.message(_("Cannot index more than one paragraph!"));
598 idxstring = tmpcur.selectionAsString(false);
605 void LyXText::setParagraph(LCursor & cur,
606 Spacing const & spacing, LyXAlignment align,
607 string const & labelwidthstring, bool noindent)
609 BOOST_ASSERT(cur.text());
610 // make sure that the depth behind the selection are restored, too
611 pit_type undopit = undoSpan(cur.selEnd().pit());
612 recUndo(cur.selBegin().pit(), undopit - 1);
614 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
616 Paragraph & par = pars_[pit];
617 ParagraphParameters & params = par.params();
618 params.spacing(spacing);
620 // does the layout allow the new alignment?
621 LyXLayout_ptr const & layout = par.layout();
623 if (align == LYX_ALIGN_LAYOUT)
624 align = layout->align;
625 if (align & layout->alignpossible) {
626 if (align == layout->align)
627 params.align(LYX_ALIGN_LAYOUT);
631 par.setLabelWidthString(labelwidthstring);
632 params.noindent(noindent);
637 // this really should just insert the inset and not move the cursor.
638 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
640 BOOST_ASSERT(this == cur.text());
642 cur.paragraph().insertInset(cur.pos(), inset);
646 // needed to insert the selection
647 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
649 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
650 current_font, str, autoBreakRows_);
654 // turn double CR to single CR, others are converted into one
655 // blank. Then insertStringAsLines is called
656 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
658 string linestr = str;
659 bool newline_inserted = false;
661 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
662 if (linestr[i] == '\n') {
663 if (newline_inserted) {
664 // we know that \r will be ignored by
665 // insertStringAsLines. Of course, it is a dirty
666 // trick, but it works...
667 linestr[i - 1] = '\r';
671 newline_inserted = true;
673 } else if (isPrintable(linestr[i])) {
674 newline_inserted = false;
677 insertStringAsLines(cur, linestr);
681 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
682 bool setfont, bool boundary)
685 setCursorIntern(cur, par, pos, setfont, boundary);
686 return deleteEmptyParagraphMechanism(cur, old);
690 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
692 BOOST_ASSERT(par != int(paragraphs().size()));
696 // now some strict checking
697 Paragraph & para = getPar(par);
699 // None of these should happen, but we're scaredy-cats
701 lyxerr << "dont like -1" << endl;
705 if (pos > para.size()) {
706 lyxerr << "dont like 1, pos: " << pos
707 << " size: " << para.size()
708 << " par: " << par << endl;
714 void LyXText::setCursorIntern(LCursor & cur,
715 pit_type par, pos_type pos, bool setfont, bool boundary)
717 cur.boundary(boundary);
718 setCursor(cur.top(), par, pos);
725 void LyXText::setCurrentFont(LCursor & cur)
727 BOOST_ASSERT(this == cur.text());
728 pos_type pos = cur.pos();
729 Paragraph & par = cur.paragraph();
731 if (cur.boundary() && pos > 0)
735 if (pos == cur.lastpos())
737 else // potentional bug... BUG (Lgb)
738 if (par.isSeparator(pos)) {
739 if (pos > cur.textRow().pos() &&
740 bidi.level(pos) % 2 ==
741 bidi.level(pos - 1) % 2)
743 else if (pos + 1 < cur.lastpos())
748 BufferParams const & bufparams = cur.buffer().params();
749 current_font = par.getFontSettings(bufparams, pos);
750 real_current_font = getFont(par, pos);
752 if (cur.pos() == cur.lastpos()
753 && bidi.isBoundary(cur.buffer(), par, cur.pos())
754 && !cur.boundary()) {
755 Language const * lang = par.getParLanguage(bufparams);
756 current_font.setLanguage(lang);
757 current_font.setNumber(LyXFont::OFF);
758 real_current_font.setLanguage(lang);
759 real_current_font.setNumber(LyXFont::OFF);
764 // x is an absolute screen coord
765 // returns the column near the specified x-coordinate of the row
766 // x is set to the real beginning of this column
767 pos_type LyXText::getColumnNearX(pit_type const pit,
768 Row const & row, int & x, bool & boundary) const
770 int const xo = theCoords.get(this, pit).x_;
772 RowMetrics const r = computeRowMetrics(pit, row);
773 Paragraph const & par = pars_[pit];
775 pos_type vc = row.pos();
776 pos_type end = row.endpos();
778 LyXLayout_ptr const & layout = par.layout();
780 bool left_side = false;
782 pos_type body_pos = par.beginOfBody();
785 double last_tmpx = tmpx;
788 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
791 // check for empty row
797 while (vc < end && tmpx <= x) {
798 c = bidi.vis2log(vc);
800 if (body_pos > 0 && c == body_pos - 1) {
801 string lsep = layout->labelsep;
802 docstring dlsep(lsep.begin(), lsep.end());
803 tmpx += r.label_hfill +
804 font_metrics::width(dlsep, getLabelFont(par));
805 if (par.isLineSeparator(body_pos - 1))
806 tmpx -= singleWidth(par, body_pos - 1);
809 if (hfillExpansion(par, row, c)) {
810 tmpx += singleWidth(par, c);
814 tmpx += r.label_hfill;
815 } else if (par.isSeparator(c)) {
816 tmpx += singleWidth(par, c);
820 tmpx += singleWidth(par, c);
825 if ((tmpx + last_tmpx) / 2 > x) {
830 BOOST_ASSERT(vc <= end); // This shouldn't happen.
833 // This (rtl_support test) is not needed, but gives
834 // some speedup if rtl_support == false
835 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
837 // If lastrow is false, we don't need to compute
839 bool const rtl = lastrow ? isRTL(par) : false;
841 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
842 (!rtl && !left_side && vc == end && x > tmpx + 5)))
844 else if (vc == row.pos()) {
845 c = bidi.vis2log(vc);
846 if (bidi.level(c) % 2 == 1)
849 c = bidi.vis2log(vc - 1);
850 bool const rtl = (bidi.level(c) % 2 == 1);
851 if (left_side == rtl) {
853 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
857 // I believe this code is not needed anymore (Jug 20050717)
859 // The following code is necessary because the cursor position past
860 // the last char in a row is logically equivalent to that before
861 // the first char in the next row. That's why insets causing row
862 // divisions -- Newline and display-style insets -- must be treated
863 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
864 // Newline inset, air gap below:
865 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
866 if (bidi.level(end -1) % 2 == 0)
867 tmpx -= singleWidth(par, end - 1);
869 tmpx += singleWidth(par, end - 1);
873 // Air gap above display inset:
874 if (row.pos() < end && c >= end && end < par.size()
875 && par.isInset(end) && par.getInset(end)->display()) {
878 // Air gap below display inset:
879 if (row.pos() < end && c >= end && par.isInset(end - 1)
880 && par.getInset(end - 1)->display()) {
886 pos_type const col = c - row.pos();
888 if (!c || end == par.size())
891 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
896 return min(col, end - 1 - row.pos());
900 // y is screen coordinate
901 pit_type LyXText::getPitNearY(int y) const
903 BOOST_ASSERT(!paragraphs().empty());
904 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
905 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
907 << BOOST_CURRENT_FUNCTION
908 << ": y: " << y << " cache size: " << cc.size()
911 // look for highest numbered paragraph with y coordinate less than given y
914 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
915 CoordCache::InnerParPosCache::const_iterator et = cc.end();
916 for (; it != et; ++it) {
918 << BOOST_CURRENT_FUNCTION
919 << " examining: pit: " << it->first
920 << " y: " << it->second.y_
923 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
930 << BOOST_CURRENT_FUNCTION
931 << ": found best y: " << yy << " for pit: " << pit
938 Row const & LyXText::getRowNearY(int y, pit_type pit) const
940 Paragraph const & par = pars_[pit];
941 int yy = theCoords.get(this, pit).y_ - par.ascent();
942 BOOST_ASSERT(!par.rows().empty());
943 RowList::const_iterator rit = par.rows().begin();
944 RowList::const_iterator const rlast = boost::prior(par.rows().end());
945 for (; rit != rlast; yy += rit->height(), ++rit)
946 if (yy + rit->height() > y)
952 // x,y are absolute screen coordinates
953 // sets cursor recursively descending into nested editable insets
954 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
956 pit_type pit = getPitNearY(y);
957 BOOST_ASSERT(pit != -1);
958 Row const & row = getRowNearY(y, pit);
961 int xx = x; // is modified by getColumnNearX
962 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
968 // try to descend into nested insets
969 InsetBase * inset = checkInsetHit(x, y);
970 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
972 // Either we deconst editXY or better we move current_font
973 // and real_current_font to LCursor
978 // This should be just before or just behind the
979 // cursor position set above.
980 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
981 || inset == pars_[pit].getInset(pos));
982 // Make sure the cursor points to the position before
984 if (inset == pars_[pit].getInset(pos - 1))
986 inset = inset->editXY(cur, x, y);
987 if (cur.top().text() == this)
993 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
997 if (cur.pos() == cur.lastpos())
999 InsetBase * inset = cur.nextInset();
1000 if (!isHighlyEditableInset(inset))
1002 inset->edit(cur, front);
1007 bool LyXText::cursorLeft(LCursor & cur)
1009 if (!cur.boundary() && cur.pos() > 0 &&
1010 cur.textRow().pos() == cur.pos() &&
1011 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1012 !cur.paragraph().isNewline(cur.pos()-1)) {
1013 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1015 if (cur.pos() != 0) {
1016 bool boundary = cur.boundary();
1017 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1018 if (!checkAndActivateInset(cur, false)) {
1019 if (false && !boundary &&
1020 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1022 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1024 return updateNeeded;
1027 if (cur.pit() != 0) {
1028 // Steps into the paragraph above
1029 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1035 bool LyXText::cursorRight(LCursor & cur)
1037 if (cur.pos() != cur.lastpos()) {
1039 return setCursor(cur, cur.pit(), cur.pos(),
1042 bool updateNeeded = false;
1043 if (!checkAndActivateInset(cur, true)) {
1044 if (cur.textRow().endpos() == cur.pos() + 1 &&
1045 cur.textRow().endpos() != cur.lastpos() &&
1046 !cur.paragraph().isLineSeparator(cur.pos()) &&
1047 !cur.paragraph().isNewline(cur.pos())) {
1050 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1051 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1053 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1055 return updateNeeded;
1058 if (cur.pit() != cur.lastpit())
1059 return setCursor(cur, cur.pit() + 1, 0);
1064 bool LyXText::cursorUp(LCursor & cur)
1066 Paragraph const & par = cur.paragraph();
1068 int const x = cur.targetX();
1070 if (cur.pos() && cur.boundary())
1071 row = par.pos2row(cur.pos()-1);
1073 row = par.pos2row(cur.pos());
1075 if (!cur.selection()) {
1076 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1078 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1079 cur.clearSelection();
1081 // This happens when you move out of an inset.
1082 // And to give the DEPM the possibility of doing
1083 // something we must provide it with two different
1085 LCursor dummy = cur;
1089 return deleteEmptyParagraphMechanism(dummy, old);
1092 bool updateNeeded = false;
1095 updateNeeded |= setCursor(cur, cur.pit(),
1096 x2pos(cur.pit(), row - 1, x));
1097 } else if (cur.pit() > 0) {
1099 //cannot use 'par' now
1100 updateNeeded |= setCursor(cur, cur.pit(),
1101 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1106 return updateNeeded;
1110 bool LyXText::cursorDown(LCursor & cur)
1112 Paragraph const & par = cur.paragraph();
1114 int const x = cur.targetX();
1116 if (cur.pos() && cur.boundary())
1117 row = par.pos2row(cur.pos()-1);
1119 row = par.pos2row(cur.pos());
1121 if (!cur.selection()) {
1122 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1124 editXY(cur, x, y + par.rows()[row].descent() + 1);
1125 cur.clearSelection();
1127 // This happens when you move out of an inset.
1128 // And to give the DEPM the possibility of doing
1129 // something we must provide it with two different
1131 LCursor dummy = cur;
1135 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1137 // Make sure that cur gets back whatever happened to dummy(Lgb)
1144 bool updateNeeded = false;
1146 if (row + 1 < int(par.rows().size())) {
1147 updateNeeded |= setCursor(cur, cur.pit(),
1148 x2pos(cur.pit(), row + 1, x));
1149 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1151 updateNeeded |= setCursor(cur, cur.pit(),
1152 x2pos(cur.pit(), 0, x));
1157 return updateNeeded;
1161 bool LyXText::cursorUpParagraph(LCursor & cur)
1163 bool updated = false;
1165 updated = setCursor(cur, cur.pit(), 0);
1166 else if (cur.pit() != 0)
1167 updated = setCursor(cur, cur.pit() - 1, 0);
1172 bool LyXText::cursorDownParagraph(LCursor & cur)
1174 bool updated = false;
1175 if (cur.pit() != cur.lastpit())
1176 updated = setCursor(cur, cur.pit() + 1, 0);
1178 updated = setCursor(cur, cur.pit(), cur.lastpos());
1183 // fix the cursor `cur' after a characters has been deleted at `where'
1184 // position. Called by deleteEmptyParagraphMechanism
1185 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1187 // Do nothing if cursor is not in the paragraph where the
1188 // deletion occured,
1189 if (cur.pit() != where.pit())
1192 // If cursor position is after the deletion place update it
1193 if (cur.pos() > where.pos())
1196 // Check also if we don't want to set the cursor on a spot behind the
1197 // pagragraph because we erased the last character.
1198 if (cur.pos() > cur.lastpos())
1199 cur.pos() = cur.lastpos();
1203 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1205 // Would be wrong to delete anything if we have a selection.
1206 if (cur.selection())
1209 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1210 // old should point to us
1211 BOOST_ASSERT(old.text() == this);
1213 Paragraph & oldpar = old.paragraph();
1215 // We allow all kinds of "mumbo-jumbo" when freespacing.
1216 if (oldpar.isFreeSpacing())
1219 /* Ok I'll put some comments here about what is missing.
1220 There are still some small problems that can lead to
1221 double spaces stored in the document file or space at
1222 the beginning of paragraphs(). This happens if you have
1223 the cursor between to spaces and then save. Or if you
1224 cut and paste and the selection have a space at the
1225 beginning and then save right after the paste. (Lgb)
1228 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1229 // delete the LineSeparator.
1232 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1233 // delete the LineSeparator.
1236 bool const same_inset = &old.inset() == &cur.inset();
1237 bool const same_par = same_inset && old.pit() == cur.pit();
1238 bool const same_par_pos = same_par && old.pos() == cur.pos();
1240 // If the chars around the old cursor were spaces, delete one of them.
1241 if (!same_par_pos) {
1242 // Only if the cursor has really moved.
1244 && old.pos() < oldpar.size()
1245 && oldpar.isLineSeparator(old.pos())
1246 && oldpar.isLineSeparator(old.pos() - 1)
1247 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1248 // We need to set the text to Change::INSERTED to
1249 // get it erased properly
1250 oldpar.setChange(old.pos() -1, Change::INSERTED);
1251 oldpar.erase(old.pos() - 1);
1252 #ifdef WITH_WARNINGS
1253 #warning This will not work anymore when we have multiple views of the same buffer
1254 // In this case, we will have to correct also the cursors held by
1255 // other bufferviews. It will probably be easier to do that in a more
1256 // automated way in CursorSlice code. (JMarc 26/09/2001)
1258 // correct all cursor parts
1260 fixCursorAfterDelete(cur.top(), old.top());
1267 // only do our magic if we changed paragraph
1271 // don't delete anything if this is the ONLY paragraph!
1272 if (old.lastpit() == 0)
1275 // Do not delete empty paragraphs with keepempty set.
1276 if (oldpar.allowEmpty())
1279 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1281 recordUndo(old, Undo::ATOMIC,
1282 max(old.pit() - 1, pit_type(0)),
1283 min(old.pit() + 1, old.lastpit()));
1284 ParagraphList & plist = old.text()->paragraphs();
1285 plist.erase(boost::next(plist.begin(), old.pit()));
1287 // see #warning above
1288 if (cur.depth() >= old.depth()) {
1289 CursorSlice & curslice = cur[old.depth() - 1];
1290 if (&curslice.inset() == &old.inset()
1291 && curslice.pit() > old.pit()) {
1293 // since a paragraph has been deleted, all the
1294 // insets after `old' have been copied and
1295 // their address has changed. Therefore we
1296 // need to `regenerate' cur. (JMarc)
1297 cur.updateInsets(&(cur.bottom().inset()));
1301 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1302 //ParIterator par_it(old);
1303 //updateLabels(old.buffer(), par_it);
1304 // So for now we do the full update:
1305 updateLabels(old.buffer());
1309 if (oldpar.stripLeadingSpaces())
1316 void LyXText::recUndo(pit_type first, pit_type last) const
1318 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1322 void LyXText::recUndo(pit_type par) const
1324 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1328 int defaultRowHeight()
1330 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);