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 // this happens when selecting several cells in tabular (bug 2630)
411 if (cur.selBegin().idx() != cur.selEnd().idx())
414 pit_type const beg = cur.selBegin().pit();
415 pit_type const end = cur.selEnd().pit() + 1;
416 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
418 for (pit_type pit = beg; pit != end; ++pit) {
419 if (::changeDepthAllowed(type, pars_[pit], max_depth))
421 max_depth = pars_[pit].getMaxDepthAfter();
427 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
429 BOOST_ASSERT(this == cur.text());
430 pit_type const beg = cur.selBegin().pit();
431 pit_type const end = cur.selEnd().pit() + 1;
432 recordUndoSelection(cur);
433 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
435 for (pit_type pit = beg; pit != end; ++pit) {
436 Paragraph & par = pars_[pit];
437 if (::changeDepthAllowed(type, par, max_depth)) {
438 int const depth = par.params().depth();
439 if (type == INC_DEPTH)
440 par.params().depth(depth + 1);
442 par.params().depth(depth - 1);
444 max_depth = par.getMaxDepthAfter();
446 // this handles the counter labels, and also fixes up
447 // depth values for follow-on (child) paragraphs
448 updateLabels(cur.buffer());
452 // set font over selection
453 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
455 BOOST_ASSERT(this == cur.text());
456 // if there is no selection just set the current_font
457 if (!cur.selection()) {
458 // Determine basis font
460 pit_type pit = cur.pit();
461 if (cur.pos() < pars_[pit].beginOfBody())
462 layoutfont = getLabelFont(pars_[pit]);
464 layoutfont = getLayoutFont(pit);
466 // Update current font
467 real_current_font.update(font,
468 cur.buffer().params().language,
471 // Reduce to implicit settings
472 current_font = real_current_font;
473 current_font.reduce(layoutfont);
474 // And resolve it completely
475 real_current_font.realize(layoutfont);
480 // Ok, we have a selection.
481 recordUndoSelection(cur);
483 DocIterator dit = cur.selectionBegin();
484 DocIterator ditend = cur.selectionEnd();
486 BufferParams const & params = cur.buffer().params();
488 // Don't use forwardChar here as ditend might have
489 // pos() == lastpos() and forwardChar would miss it.
490 // Can't use forwardPos either as this descends into
492 for (; dit != ditend; dit.forwardPosNoDescend()) {
493 if (dit.pos() != dit.lastpos()) {
494 LyXFont f = getFont(dit.paragraph(), dit.pos());
495 f.update(font, params.language, toggleall);
496 setCharFont(dit.pit(), dit.pos(), f);
502 // the cursor set functions have a special mechanism. When they
503 // realize you left an empty paragraph, they will delete it.
505 bool LyXText::cursorHome(LCursor & cur)
507 BOOST_ASSERT(this == cur.text());
508 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
510 return setCursor(cur, cur.pit(), row.pos());
514 bool LyXText::cursorEnd(LCursor & cur)
516 BOOST_ASSERT(this == cur.text());
517 // if not on the last row of the par, put the cursor before
518 // the final space exept if I have a spanning inset or one string
519 // is so long that we force a break.
520 pos_type end = cur.textRow().endpos();
522 // empty text, end-1 is no valid position
524 bool boundary = false;
525 if (end != cur.lastpos()) {
526 if (!cur.paragraph().isLineSeparator(end-1)
527 && !cur.paragraph().isNewline(end-1))
532 return setCursor(cur, cur.pit(), end, true, boundary);
536 bool LyXText::cursorTop(LCursor & cur)
538 BOOST_ASSERT(this == cur.text());
539 return setCursor(cur, 0, 0);
543 bool LyXText::cursorBottom(LCursor & cur)
545 BOOST_ASSERT(this == cur.text());
546 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
550 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
552 BOOST_ASSERT(this == cur.text());
553 // If the mask is completely neutral, tell user
554 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
555 // Could only happen with user style
556 cur.message(_("No font change defined. "
557 "Use Character under the Layout menu to define font change."));
561 // Try implicit word selection
562 // If there is a change in the language the implicit word selection
564 CursorSlice resetCursor = cur.top();
565 bool implicitSelection =
566 font.language() == ignore_language
567 && font.number() == LyXFont::IGNORE
568 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
571 setFont(cur, font, toggleall);
573 // Implicit selections are cleared afterwards
574 // and cursor is set to the original position.
575 if (implicitSelection) {
576 cur.clearSelection();
577 cur.top() = resetCursor;
583 string LyXText::getStringToIndex(LCursor const & cur)
585 BOOST_ASSERT(this == cur.text());
588 if (cur.selection()) {
589 idxstring = cur.selectionAsString(false);
591 // Try implicit word selection. If there is a change
592 // in the language the implicit word selection is
594 LCursor tmpcur = cur;
595 selectWord(tmpcur, lyx::PREVIOUS_WORD);
597 if (!tmpcur.selection())
598 cur.message(_("Nothing to index!"));
599 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
600 cur.message(_("Cannot index more than one paragraph!"));
602 idxstring = tmpcur.selectionAsString(false);
609 void LyXText::setParagraph(LCursor & cur,
610 Spacing const & spacing, LyXAlignment align,
611 string const & labelwidthstring, bool noindent)
613 BOOST_ASSERT(cur.text());
614 // make sure that the depth behind the selection are restored, too
615 pit_type undopit = undoSpan(cur.selEnd().pit());
616 recUndo(cur.selBegin().pit(), undopit - 1);
618 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
620 Paragraph & par = pars_[pit];
621 ParagraphParameters & params = par.params();
622 params.spacing(spacing);
624 // does the layout allow the new alignment?
625 LyXLayout_ptr const & layout = par.layout();
627 if (align == LYX_ALIGN_LAYOUT)
628 align = layout->align;
629 if (align & layout->alignpossible) {
630 if (align == layout->align)
631 params.align(LYX_ALIGN_LAYOUT);
635 par.setLabelWidthString(labelwidthstring);
636 params.noindent(noindent);
641 // this really should just insert the inset and not move the cursor.
642 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
644 BOOST_ASSERT(this == cur.text());
646 cur.paragraph().insertInset(cur.pos(), inset);
650 // needed to insert the selection
651 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
653 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
654 current_font, str, autoBreakRows_);
658 // turn double CR to single CR, others are converted into one
659 // blank. Then insertStringAsLines is called
660 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
662 string linestr = str;
663 bool newline_inserted = false;
665 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
666 if (linestr[i] == '\n') {
667 if (newline_inserted) {
668 // we know that \r will be ignored by
669 // insertStringAsLines. Of course, it is a dirty
670 // trick, but it works...
671 linestr[i - 1] = '\r';
675 newline_inserted = true;
677 } else if (isPrintable(linestr[i])) {
678 newline_inserted = false;
681 insertStringAsLines(cur, linestr);
685 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
686 bool setfont, bool boundary)
689 setCursorIntern(cur, par, pos, setfont, boundary);
690 return deleteEmptyParagraphMechanism(cur, old);
694 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
696 BOOST_ASSERT(par != int(paragraphs().size()));
700 // now some strict checking
701 Paragraph & para = getPar(par);
703 // None of these should happen, but we're scaredy-cats
705 lyxerr << "dont like -1" << endl;
709 if (pos > para.size()) {
710 lyxerr << "dont like 1, pos: " << pos
711 << " size: " << para.size()
712 << " par: " << par << endl;
718 void LyXText::setCursorIntern(LCursor & cur,
719 pit_type par, pos_type pos, bool setfont, bool boundary)
721 cur.boundary(boundary);
722 setCursor(cur.top(), par, pos);
729 void LyXText::setCurrentFont(LCursor & cur)
731 BOOST_ASSERT(this == cur.text());
732 pos_type pos = cur.pos();
733 Paragraph & par = cur.paragraph();
735 if (cur.boundary() && pos > 0)
739 if (pos == cur.lastpos())
741 else // potentional bug... BUG (Lgb)
742 if (par.isSeparator(pos)) {
743 if (pos > cur.textRow().pos() &&
744 bidi.level(pos) % 2 ==
745 bidi.level(pos - 1) % 2)
747 else if (pos + 1 < cur.lastpos())
752 BufferParams const & bufparams = cur.buffer().params();
753 current_font = par.getFontSettings(bufparams, pos);
754 real_current_font = getFont(par, pos);
756 if (cur.pos() == cur.lastpos()
757 && bidi.isBoundary(cur.buffer(), par, cur.pos())
758 && !cur.boundary()) {
759 Language const * lang = par.getParLanguage(bufparams);
760 current_font.setLanguage(lang);
761 current_font.setNumber(LyXFont::OFF);
762 real_current_font.setLanguage(lang);
763 real_current_font.setNumber(LyXFont::OFF);
768 // x is an absolute screen coord
769 // returns the column near the specified x-coordinate of the row
770 // x is set to the real beginning of this column
771 pos_type LyXText::getColumnNearX(pit_type const pit,
772 Row const & row, int & x, bool & boundary) const
774 int const xo = theCoords.get(this, pit).x_;
776 RowMetrics const r = computeRowMetrics(pit, row);
777 Paragraph const & par = pars_[pit];
779 pos_type vc = row.pos();
780 pos_type end = row.endpos();
782 LyXLayout_ptr const & layout = par.layout();
784 bool left_side = false;
786 pos_type body_pos = par.beginOfBody();
789 double last_tmpx = tmpx;
792 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
795 // check for empty row
801 while (vc < end && tmpx <= x) {
802 c = bidi.vis2log(vc);
804 if (body_pos > 0 && c == body_pos - 1) {
805 string lsep = layout->labelsep;
806 docstring dlsep(lsep.begin(), lsep.end());
807 tmpx += r.label_hfill +
808 font_metrics::width(dlsep, getLabelFont(par));
809 if (par.isLineSeparator(body_pos - 1))
810 tmpx -= singleWidth(par, body_pos - 1);
813 if (hfillExpansion(par, row, c)) {
814 tmpx += singleWidth(par, c);
818 tmpx += r.label_hfill;
819 } else if (par.isSeparator(c)) {
820 tmpx += singleWidth(par, c);
824 tmpx += singleWidth(par, c);
829 if ((tmpx + last_tmpx) / 2 > x) {
834 BOOST_ASSERT(vc <= end); // This shouldn't happen.
837 // This (rtl_support test) is not needed, but gives
838 // some speedup if rtl_support == false
839 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
841 // If lastrow is false, we don't need to compute
843 bool const rtl = lastrow ? isRTL(par) : false;
845 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
846 (!rtl && !left_side && vc == end && x > tmpx + 5)))
848 else if (vc == row.pos()) {
849 c = bidi.vis2log(vc);
850 if (bidi.level(c) % 2 == 1)
853 c = bidi.vis2log(vc - 1);
854 bool const rtl = (bidi.level(c) % 2 == 1);
855 if (left_side == rtl) {
857 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
861 // I believe this code is not needed anymore (Jug 20050717)
863 // The following code is necessary because the cursor position past
864 // the last char in a row is logically equivalent to that before
865 // the first char in the next row. That's why insets causing row
866 // divisions -- Newline and display-style insets -- must be treated
867 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
868 // Newline inset, air gap below:
869 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
870 if (bidi.level(end -1) % 2 == 0)
871 tmpx -= singleWidth(par, end - 1);
873 tmpx += singleWidth(par, end - 1);
877 // Air gap above display inset:
878 if (row.pos() < end && c >= end && end < par.size()
879 && par.isInset(end) && par.getInset(end)->display()) {
882 // Air gap below display inset:
883 if (row.pos() < end && c >= end && par.isInset(end - 1)
884 && par.getInset(end - 1)->display()) {
890 pos_type const col = c - row.pos();
892 if (!c || end == par.size())
895 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
900 return min(col, end - 1 - row.pos());
904 // y is screen coordinate
905 pit_type LyXText::getPitNearY(int y) const
907 BOOST_ASSERT(!paragraphs().empty());
908 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
909 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
911 << BOOST_CURRENT_FUNCTION
912 << ": y: " << y << " cache size: " << cc.size()
915 // look for highest numbered paragraph with y coordinate less than given y
918 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
919 CoordCache::InnerParPosCache::const_iterator et = cc.end();
920 for (; it != et; ++it) {
922 << BOOST_CURRENT_FUNCTION
923 << " examining: pit: " << it->first
924 << " y: " << it->second.y_
927 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
934 << BOOST_CURRENT_FUNCTION
935 << ": found best y: " << yy << " for pit: " << pit
942 Row const & LyXText::getRowNearY(int y, pit_type pit) const
944 Paragraph const & par = pars_[pit];
945 int yy = theCoords.get(this, pit).y_ - par.ascent();
946 BOOST_ASSERT(!par.rows().empty());
947 RowList::const_iterator rit = par.rows().begin();
948 RowList::const_iterator const rlast = boost::prior(par.rows().end());
949 for (; rit != rlast; yy += rit->height(), ++rit)
950 if (yy + rit->height() > y)
956 // x,y are absolute screen coordinates
957 // sets cursor recursively descending into nested editable insets
958 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
960 pit_type pit = getPitNearY(y);
961 BOOST_ASSERT(pit != -1);
962 Row const & row = getRowNearY(y, pit);
965 int xx = x; // is modified by getColumnNearX
966 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
972 // try to descend into nested insets
973 InsetBase * inset = checkInsetHit(x, y);
974 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
976 // Either we deconst editXY or better we move current_font
977 // and real_current_font to LCursor
982 // This should be just before or just behind the
983 // cursor position set above.
984 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
985 || inset == pars_[pit].getInset(pos));
986 // Make sure the cursor points to the position before
988 if (inset == pars_[pit].getInset(pos - 1))
990 inset = inset->editXY(cur, x, y);
991 if (cur.top().text() == this)
997 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1001 if (cur.pos() == cur.lastpos())
1003 InsetBase * inset = cur.nextInset();
1004 if (!isHighlyEditableInset(inset))
1006 inset->edit(cur, front);
1011 bool LyXText::cursorLeft(LCursor & cur)
1013 if (!cur.boundary() && cur.pos() > 0 &&
1014 cur.textRow().pos() == cur.pos() &&
1015 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1016 !cur.paragraph().isNewline(cur.pos()-1)) {
1017 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1019 if (cur.pos() != 0) {
1020 bool boundary = cur.boundary();
1021 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1022 if (!checkAndActivateInset(cur, false)) {
1023 if (false && !boundary &&
1024 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1026 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1028 return updateNeeded;
1031 if (cur.pit() != 0) {
1032 // Steps into the paragraph above
1033 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1039 bool LyXText::cursorRight(LCursor & cur)
1041 if (cur.pos() != cur.lastpos()) {
1043 return setCursor(cur, cur.pit(), cur.pos(),
1046 bool updateNeeded = false;
1047 if (!checkAndActivateInset(cur, true)) {
1048 if (cur.textRow().endpos() == cur.pos() + 1 &&
1049 cur.textRow().endpos() != cur.lastpos() &&
1050 !cur.paragraph().isLineSeparator(cur.pos()) &&
1051 !cur.paragraph().isNewline(cur.pos())) {
1054 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1055 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1057 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1059 return updateNeeded;
1062 if (cur.pit() != cur.lastpit())
1063 return setCursor(cur, cur.pit() + 1, 0);
1068 bool LyXText::cursorUp(LCursor & cur)
1070 Paragraph const & par = cur.paragraph();
1072 int const x = cur.targetX();
1074 if (cur.pos() && cur.boundary())
1075 row = par.pos2row(cur.pos()-1);
1077 row = par.pos2row(cur.pos());
1079 if (!cur.selection()) {
1080 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1082 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1083 cur.clearSelection();
1085 // This happens when you move out of an inset.
1086 // And to give the DEPM the possibility of doing
1087 // something we must provide it with two different
1089 LCursor dummy = cur;
1093 return deleteEmptyParagraphMechanism(dummy, old);
1096 bool updateNeeded = false;
1099 updateNeeded |= setCursor(cur, cur.pit(),
1100 x2pos(cur.pit(), row - 1, x));
1101 } else if (cur.pit() > 0) {
1103 //cannot use 'par' now
1104 updateNeeded |= setCursor(cur, cur.pit(),
1105 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1110 return updateNeeded;
1114 bool LyXText::cursorDown(LCursor & cur)
1116 Paragraph const & par = cur.paragraph();
1118 int const x = cur.targetX();
1120 if (cur.pos() && cur.boundary())
1121 row = par.pos2row(cur.pos()-1);
1123 row = par.pos2row(cur.pos());
1125 if (!cur.selection()) {
1126 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1128 editXY(cur, x, y + par.rows()[row].descent() + 1);
1129 cur.clearSelection();
1131 // This happens when you move out of an inset.
1132 // And to give the DEPM the possibility of doing
1133 // something we must provide it with two different
1135 LCursor dummy = cur;
1139 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1141 // Make sure that cur gets back whatever happened to dummy(Lgb)
1148 bool updateNeeded = false;
1150 if (row + 1 < int(par.rows().size())) {
1151 updateNeeded |= setCursor(cur, cur.pit(),
1152 x2pos(cur.pit(), row + 1, x));
1153 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1155 updateNeeded |= setCursor(cur, cur.pit(),
1156 x2pos(cur.pit(), 0, x));
1161 return updateNeeded;
1165 bool LyXText::cursorUpParagraph(LCursor & cur)
1167 bool updated = false;
1169 updated = setCursor(cur, cur.pit(), 0);
1170 else if (cur.pit() != 0)
1171 updated = setCursor(cur, cur.pit() - 1, 0);
1176 bool LyXText::cursorDownParagraph(LCursor & cur)
1178 bool updated = false;
1179 if (cur.pit() != cur.lastpit())
1180 updated = setCursor(cur, cur.pit() + 1, 0);
1182 updated = setCursor(cur, cur.pit(), cur.lastpos());
1187 // fix the cursor `cur' after a characters has been deleted at `where'
1188 // position. Called by deleteEmptyParagraphMechanism
1189 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1191 // Do nothing if cursor is not in the paragraph where the
1192 // deletion occured,
1193 if (cur.pit() != where.pit())
1196 // If cursor position is after the deletion place update it
1197 if (cur.pos() > where.pos())
1200 // Check also if we don't want to set the cursor on a spot behind the
1201 // pagragraph because we erased the last character.
1202 if (cur.pos() > cur.lastpos())
1203 cur.pos() = cur.lastpos();
1207 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1209 // Would be wrong to delete anything if we have a selection.
1210 if (cur.selection())
1213 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1214 // old should point to us
1215 BOOST_ASSERT(old.text() == this);
1217 Paragraph & oldpar = old.paragraph();
1219 // We allow all kinds of "mumbo-jumbo" when freespacing.
1220 if (oldpar.isFreeSpacing())
1223 /* Ok I'll put some comments here about what is missing.
1224 There are still some small problems that can lead to
1225 double spaces stored in the document file or space at
1226 the beginning of paragraphs(). This happens if you have
1227 the cursor between to spaces and then save. Or if you
1228 cut and paste and the selection have a space at the
1229 beginning and then save right after the paste. (Lgb)
1232 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1233 // delete the LineSeparator.
1236 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1237 // delete the LineSeparator.
1240 bool const same_inset = &old.inset() == &cur.inset();
1241 bool const same_par = same_inset && old.pit() == cur.pit();
1242 bool const same_par_pos = same_par && old.pos() == cur.pos();
1244 // If the chars around the old cursor were spaces, delete one of them.
1245 if (!same_par_pos) {
1246 // Only if the cursor has really moved.
1248 && old.pos() < oldpar.size()
1249 && oldpar.isLineSeparator(old.pos())
1250 && oldpar.isLineSeparator(old.pos() - 1)
1251 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1252 // We need to set the text to Change::INSERTED to
1253 // get it erased properly
1254 oldpar.setChange(old.pos() -1, Change::INSERTED);
1255 oldpar.erase(old.pos() - 1);
1256 #ifdef WITH_WARNINGS
1257 #warning This will not work anymore when we have multiple views of the same buffer
1258 // In this case, we will have to correct also the cursors held by
1259 // other bufferviews. It will probably be easier to do that in a more
1260 // automated way in CursorSlice code. (JMarc 26/09/2001)
1262 // correct all cursor parts
1264 fixCursorAfterDelete(cur.top(), old.top());
1271 // only do our magic if we changed paragraph
1275 // don't delete anything if this is the ONLY paragraph!
1276 if (old.lastpit() == 0)
1279 // Do not delete empty paragraphs with keepempty set.
1280 if (oldpar.allowEmpty())
1283 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1285 recordUndo(old, Undo::ATOMIC,
1286 max(old.pit() - 1, pit_type(0)),
1287 min(old.pit() + 1, old.lastpit()));
1288 ParagraphList & plist = old.text()->paragraphs();
1289 plist.erase(boost::next(plist.begin(), old.pit()));
1291 // see #warning above
1292 if (cur.depth() >= old.depth()) {
1293 CursorSlice & curslice = cur[old.depth() - 1];
1294 if (&curslice.inset() == &old.inset()
1295 && curslice.pit() > old.pit()) {
1297 // since a paragraph has been deleted, all the
1298 // insets after `old' have been copied and
1299 // their address has changed. Therefore we
1300 // need to `regenerate' cur. (JMarc)
1301 cur.updateInsets(&(cur.bottom().inset()));
1305 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1306 //ParIterator par_it(old);
1307 //updateLabels(old.buffer(), par_it);
1308 // So for now we do the full update:
1309 updateLabels(old.buffer());
1313 if (oldpar.stripLeadingSpaces())
1320 void LyXText::recUndo(pit_type first, pit_type last) const
1322 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1326 void LyXText::recUndo(pit_type par) const
1328 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1332 int defaultRowHeight()
1334 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);