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 "mathed/InsetMathHull.h"
56 #include "support/textutils.h"
58 #include <boost/current_function.hpp>
67 using std::ostringstream;
73 LyXText::LyXText(BufferView * bv)
74 : maxwidth_(bv ? bv->workWidth() : 100),
75 current_font(LyXFont::ALL_INHERIT),
76 background_color_(LColor::background),
82 void LyXText::init(BufferView * bv)
86 maxwidth_ = bv->workWidth();
91 pit_type const end = paragraphs().size();
92 for (pit_type pit = 0; pit != end; ++pit)
93 pars_[pit].rows().clear();
95 updateLabels(*bv->buffer());
99 bool LyXText::isMainText() const
101 return &bv()->buffer()->text() == this;
105 //takes screen x,y coordinates
106 InsetBase * LyXText::checkInsetHit(int x, int y) const
108 pit_type pit = getPitNearY(y);
109 BOOST_ASSERT(pit != -1);
111 Paragraph const & par = pars_[pit];
114 << BOOST_CURRENT_FUNCTION
119 InsetList::const_iterator iit = par.insetlist.begin();
120 InsetList::const_iterator iend = par.insetlist.end();
121 for (; iit != iend; ++iit) {
122 InsetBase * inset = iit->inset;
125 << BOOST_CURRENT_FUNCTION
126 << ": examining inset " << inset << endl;
128 if (theCoords.getInsets().has(inset))
130 << BOOST_CURRENT_FUNCTION
131 << ": xo: " << inset->xo() << "..."
132 << inset->xo() + inset->width()
133 << " yo: " << inset->yo() - inset->ascent()
135 << inset->yo() + inset->descent()
139 << BOOST_CURRENT_FUNCTION
140 << ": inset has no cached position" << endl;
142 if (inset->covers(x, y)) {
144 << BOOST_CURRENT_FUNCTION
145 << ": Hit inset: " << inset << endl;
150 << BOOST_CURRENT_FUNCTION
151 << ": No inset hit. " << endl;
157 // Gets the fully instantiated font at a given position in a paragraph
158 // Basically the same routine as Paragraph::getFont() in paragraph.C.
159 // The difference is that this one is used for displaying, and thus we
160 // are allowed to make cosmetic improvements. For instance make footnotes
162 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
164 BOOST_ASSERT(pos >= 0);
166 LyXLayout_ptr const & layout = par.layout();
170 BufferParams const & params = bv()->buffer()->params();
171 pos_type const body_pos = par.beginOfBody();
173 // We specialize the 95% common case:
174 if (!par.getDepth()) {
175 LyXFont f = par.getFontSettings(params, pos);
180 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
181 lf = layout->labelfont;
182 rlf = layout->reslabelfont;
185 rlf = layout->resfont;
187 // In case the default family has been customized
188 if (lf.family() == LyXFont::INHERIT_FAMILY)
189 rlf.setFamily(params.getFont().family());
190 return f.realize(rlf);
193 // The uncommon case need not be optimized as much
196 layoutfont = layout->labelfont;
198 layoutfont = layout->font;
200 LyXFont font = par.getFontSettings(params, pos);
201 font.realize(layoutfont);
204 applyOuterFont(font);
206 // Find the pit value belonging to paragraph. This will not break
207 // even if pars_ would not be a vector anymore.
208 // Performance appears acceptable.
210 pit_type pit = pars_.size();
211 for (pit_type it = 0; it < pit; ++it)
212 if (&pars_[it] == &par) {
216 // Realize against environment font information
217 // NOTE: the cast to pit_type should be removed when pit_type
218 // changes to a unsigned integer.
219 if (pit < pit_type(pars_.size()))
220 font.realize(outerFont(pit, pars_));
222 // Realize with the fonts of lesser depth.
223 font.realize(params.getFont());
228 // There are currently two font mechanisms in LyX:
229 // 1. The font attributes in a lyxtext, and
230 // 2. The inset-specific font properties, defined in an inset's
231 // metrics() and draw() methods and handed down the inset chain through
232 // the pi/mi parameters, and stored locally in a lyxtext in font_.
233 // This is where the two are integrated in the final fully realized
235 void LyXText::applyOuterFont(LyXFont & font) const {
237 lf.reduce(bv()->buffer()->params().getFont());
239 lf.setLanguage(font.language());
244 LyXFont LyXText::getLayoutFont(pit_type const pit) const
246 LyXLayout_ptr const & layout = pars_[pit].layout();
248 if (!pars_[pit].getDepth()) {
249 LyXFont lf = layout->resfont;
250 // In case the default family has been customized
251 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
252 lf.setFamily(bv()->buffer()->params().getFont().family());
256 LyXFont font = layout->font;
257 // Realize with the fonts of lesser depth.
258 //font.realize(outerFont(pit, paragraphs()));
259 font.realize(bv()->buffer()->params().getFont());
265 LyXFont LyXText::getLabelFont(Paragraph const & par) const
267 LyXLayout_ptr const & layout = par.layout();
269 if (!par.getDepth()) {
270 LyXFont lf = layout->reslabelfont;
271 // In case the default family has been customized
272 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
273 lf.setFamily(bv()->buffer()->params().getFont().family());
277 LyXFont font = layout->labelfont;
278 // Realize with the fonts of lesser depth.
279 font.realize(bv()->buffer()->params().getFont());
285 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
288 LyXLayout_ptr const & layout = pars_[pit].layout();
290 // Get concrete layout font to reduce against
293 if (pos < pars_[pit].beginOfBody())
294 layoutfont = layout->labelfont;
296 layoutfont = layout->font;
298 // Realize against environment font information
299 if (pars_[pit].getDepth()) {
301 while (!layoutfont.resolved() &&
302 tp != pit_type(paragraphs().size()) &&
303 pars_[tp].getDepth()) {
304 tp = outerHook(tp, paragraphs());
305 if (tp != pit_type(paragraphs().size()))
306 layoutfont.realize(pars_[tp].layout()->font);
310 // Inside inset, apply the inset's font attributes if any
313 layoutfont.realize(font_);
315 layoutfont.realize(bv()->buffer()->params().getFont());
317 // Now, reduce font against full layout font
318 font.reduce(layoutfont);
320 pars_[pit].setFont(pos, font);
324 // return past-the-last paragraph influenced by a layout change on pit
325 pit_type LyXText::undoSpan(pit_type pit)
327 pit_type end = paragraphs().size();
328 pit_type nextpit = pit + 1;
331 //because of parindents
332 if (!pars_[pit].getDepth())
333 return boost::next(nextpit);
334 //because of depth constrains
335 for (; nextpit != end; ++pit, ++nextpit) {
336 if (!pars_[pit].getDepth())
343 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
345 BOOST_ASSERT(start != end);
347 BufferParams const & bufparams = bv()->buffer()->params();
348 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
350 for (pit_type pit = start; pit != end; ++pit) {
351 pars_[pit].applyLayout(lyxlayout);
352 if (lyxlayout->margintype == MARGIN_MANUAL)
353 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
358 // set layout over selection and make a total rebreak of those paragraphs
359 void LyXText::setLayout(LCursor & cur, string const & layout)
361 BOOST_ASSERT(this == cur.text());
362 // special handling of new environment insets
363 BufferView & bv = cur.bv();
364 BufferParams const & params = bv.buffer()->params();
365 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
366 if (lyxlayout->is_environment) {
367 // move everything in a new environment inset
368 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
369 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_BEGIN));
370 theApp->lyxFunc().dispatch(FuncRequest(LFUN_LINE_END_SELECT));
371 theApp->lyxFunc().dispatch(FuncRequest(LFUN_CUT));
372 InsetBase * inset = new InsetEnvironment(params, layout);
373 insertInset(cur, inset);
374 //inset->edit(cur, true);
375 //theApp->lyxFunc().dispatch(FuncRequest(LFUN_PASTE));
379 pit_type start = cur.selBegin().pit();
380 pit_type end = cur.selEnd().pit() + 1;
381 pit_type undopit = undoSpan(end - 1);
382 recUndo(start, undopit - 1);
383 setLayout(start, end, layout);
384 updateLabels(cur.buffer());
391 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
392 Paragraph const & par, int max_depth)
394 if (par.layout()->labeltype == LABEL_BIBLIO)
396 int const depth = par.params().depth();
397 if (type == LyXText::INC_DEPTH && depth < max_depth)
399 if (type == LyXText::DEC_DEPTH && depth > 0)
408 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
410 BOOST_ASSERT(this == cur.text());
411 // this happens when selecting several cells in tabular (bug 2630)
412 if (cur.selBegin().idx() != cur.selEnd().idx())
415 pit_type const beg = cur.selBegin().pit();
416 pit_type const end = cur.selEnd().pit() + 1;
417 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
419 for (pit_type pit = beg; pit != end; ++pit) {
420 if (::changeDepthAllowed(type, pars_[pit], max_depth))
422 max_depth = pars_[pit].getMaxDepthAfter();
428 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
430 BOOST_ASSERT(this == cur.text());
431 pit_type const beg = cur.selBegin().pit();
432 pit_type const end = cur.selEnd().pit() + 1;
433 recordUndoSelection(cur);
434 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
436 for (pit_type pit = beg; pit != end; ++pit) {
437 Paragraph & par = pars_[pit];
438 if (::changeDepthAllowed(type, par, max_depth)) {
439 int const depth = par.params().depth();
440 if (type == INC_DEPTH)
441 par.params().depth(depth + 1);
443 par.params().depth(depth - 1);
445 max_depth = par.getMaxDepthAfter();
447 // this handles the counter labels, and also fixes up
448 // depth values for follow-on (child) paragraphs
449 updateLabels(cur.buffer());
453 // set font over selection
454 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
456 BOOST_ASSERT(this == cur.text());
457 // if there is no selection just set the current_font
458 if (!cur.selection()) {
459 // Determine basis font
461 pit_type pit = cur.pit();
462 if (cur.pos() < pars_[pit].beginOfBody())
463 layoutfont = getLabelFont(pars_[pit]);
465 layoutfont = getLayoutFont(pit);
467 // Update current font
468 real_current_font.update(font,
469 cur.buffer().params().language,
472 // Reduce to implicit settings
473 current_font = real_current_font;
474 current_font.reduce(layoutfont);
475 // And resolve it completely
476 real_current_font.realize(layoutfont);
481 // Ok, we have a selection.
482 recordUndoSelection(cur);
484 DocIterator dit = cur.selectionBegin();
485 DocIterator ditend = cur.selectionEnd();
487 BufferParams const & params = cur.buffer().params();
489 // Don't use forwardChar here as ditend might have
490 // pos() == lastpos() and forwardChar would miss it.
491 // Can't use forwardPos either as this descends into
493 for (; dit != ditend; dit.forwardPosNoDescend()) {
494 if (dit.pos() != dit.lastpos()) {
495 LyXFont f = getFont(dit.paragraph(), dit.pos());
496 f.update(font, params.language, toggleall);
497 setCharFont(dit.pit(), dit.pos(), f);
503 // the cursor set functions have a special mechanism. When they
504 // realize you left an empty paragraph, they will delete it.
506 bool LyXText::cursorHome(LCursor & cur)
508 BOOST_ASSERT(this == cur.text());
509 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
511 return setCursor(cur, cur.pit(), row.pos());
515 bool LyXText::cursorEnd(LCursor & cur)
517 BOOST_ASSERT(this == cur.text());
518 // if not on the last row of the par, put the cursor before
519 // the final space exept if I have a spanning inset or one string
520 // is so long that we force a break.
521 pos_type end = cur.textRow().endpos();
523 // empty text, end-1 is no valid position
525 bool boundary = false;
526 if (end != cur.lastpos()) {
527 if (!cur.paragraph().isLineSeparator(end-1)
528 && !cur.paragraph().isNewline(end-1))
533 return setCursor(cur, cur.pit(), end, true, boundary);
537 bool LyXText::cursorTop(LCursor & cur)
539 BOOST_ASSERT(this == cur.text());
540 return setCursor(cur, 0, 0);
544 bool LyXText::cursorBottom(LCursor & cur)
546 BOOST_ASSERT(this == cur.text());
547 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
551 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
553 BOOST_ASSERT(this == cur.text());
554 // If the mask is completely neutral, tell user
555 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
556 // Could only happen with user style
557 cur.message(_("No font change defined. "
558 "Use Character under the Layout menu to define font change."));
562 // Try implicit word selection
563 // If there is a change in the language the implicit word selection
565 CursorSlice resetCursor = cur.top();
566 bool implicitSelection =
567 font.language() == ignore_language
568 && font.number() == LyXFont::IGNORE
569 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
572 setFont(cur, font, toggleall);
574 // Implicit selections are cleared afterwards
575 // and cursor is set to the original position.
576 if (implicitSelection) {
577 cur.clearSelection();
578 cur.top() = resetCursor;
584 string LyXText::getStringToIndex(LCursor const & cur)
586 BOOST_ASSERT(this == cur.text());
590 idxstring = cur.selectionAsString(false);
592 // Try implicit word selection. If there is a change
593 // in the language the implicit word selection is
595 LCursor tmpcur = cur;
596 selectWord(tmpcur, lyx::PREVIOUS_WORD);
598 if (!tmpcur.selection())
599 cur.message(_("Nothing to index!"));
600 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
601 cur.message(_("Cannot index more than one paragraph!"));
603 idxstring = tmpcur.selectionAsString(false);
606 return lyx::to_utf8(idxstring);
610 void LyXText::setParagraph(LCursor & cur,
611 Spacing const & spacing, LyXAlignment align,
612 string const & labelwidthstring, bool noindent)
614 BOOST_ASSERT(cur.text());
615 // make sure that the depth behind the selection are restored, too
616 pit_type undopit = undoSpan(cur.selEnd().pit());
617 recUndo(cur.selBegin().pit(), undopit - 1);
619 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
621 Paragraph & par = pars_[pit];
622 ParagraphParameters & params = par.params();
623 params.spacing(spacing);
625 // does the layout allow the new alignment?
626 LyXLayout_ptr const & layout = par.layout();
628 if (align == LYX_ALIGN_LAYOUT)
629 align = layout->align;
630 if (align & layout->alignpossible) {
631 if (align == layout->align)
632 params.align(LYX_ALIGN_LAYOUT);
636 par.setLabelWidthString(labelwidthstring);
637 params.noindent(noindent);
642 // this really should just insert the inset and not move the cursor.
643 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
645 BOOST_ASSERT(this == cur.text());
647 cur.paragraph().insertInset(cur.pos(), inset);
651 // needed to insert the selection
652 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
654 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
655 current_font, str, autoBreakRows_);
659 // turn double CR to single CR, others are converted into one
660 // blank. Then insertStringAsLines is called
661 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
663 docstring linestr = str;
664 bool newline_inserted = false;
666 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
667 if (linestr[i] == '\n') {
668 if (newline_inserted) {
669 // we know that \r will be ignored by
670 // insertStringAsLines. Of course, it is a dirty
671 // trick, but it works...
672 linestr[i - 1] = '\r';
676 newline_inserted = true;
678 } else if (isPrintable(linestr[i])) {
679 newline_inserted = false;
682 insertStringAsLines(cur, linestr);
686 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
687 bool setfont, bool boundary)
690 setCursorIntern(cur, par, pos, setfont, boundary);
691 return deleteEmptyParagraphMechanism(cur, old);
695 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
697 BOOST_ASSERT(par != int(paragraphs().size()));
701 // now some strict checking
702 Paragraph & para = getPar(par);
704 // None of these should happen, but we're scaredy-cats
706 lyxerr << "dont like -1" << endl;
710 if (pos > para.size()) {
711 lyxerr << "dont like 1, pos: " << pos
712 << " size: " << para.size()
713 << " par: " << par << endl;
719 void LyXText::setCursorIntern(LCursor & cur,
720 pit_type par, pos_type pos, bool setfont, bool boundary)
722 BOOST_ASSERT(this == cur.text());
723 cur.boundary(boundary);
724 setCursor(cur.top(), par, pos);
731 void LyXText::setCurrentFont(LCursor & cur)
733 BOOST_ASSERT(this == cur.text());
734 pos_type pos = cur.pos();
735 Paragraph & par = cur.paragraph();
737 if (cur.boundary() && pos > 0)
741 if (pos == cur.lastpos())
743 else // potentional bug... BUG (Lgb)
744 if (par.isSeparator(pos)) {
745 if (pos > cur.textRow().pos() &&
746 bidi.level(pos) % 2 ==
747 bidi.level(pos - 1) % 2)
749 else if (pos + 1 < cur.lastpos())
754 BufferParams const & bufparams = cur.buffer().params();
755 current_font = par.getFontSettings(bufparams, pos);
756 real_current_font = getFont(par, pos);
758 if (cur.pos() == cur.lastpos()
759 && bidi.isBoundary(cur.buffer(), par, cur.pos())
760 && !cur.boundary()) {
761 Language const * lang = par.getParLanguage(bufparams);
762 current_font.setLanguage(lang);
763 current_font.setNumber(LyXFont::OFF);
764 real_current_font.setLanguage(lang);
765 real_current_font.setNumber(LyXFont::OFF);
770 // x is an absolute screen coord
771 // returns the column near the specified x-coordinate of the row
772 // x is set to the real beginning of this column
773 pos_type LyXText::getColumnNearX(pit_type const pit,
774 Row const & row, int & x, bool & boundary) const
776 int const xo = theCoords.get(this, pit).x_;
778 RowMetrics const r = computeRowMetrics(pit, row);
779 Paragraph const & par = pars_[pit];
781 pos_type vc = row.pos();
782 pos_type end = row.endpos();
784 LyXLayout_ptr const & layout = par.layout();
786 bool left_side = false;
788 pos_type body_pos = par.beginOfBody();
791 double last_tmpx = tmpx;
794 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
797 // check for empty row
803 while (vc < end && tmpx <= x) {
804 c = bidi.vis2log(vc);
806 if (body_pos > 0 && c == body_pos - 1) {
807 string lsep = layout->labelsep;
808 docstring dlsep(lsep.begin(), lsep.end());
809 tmpx += r.label_hfill +
810 font_metrics::width(dlsep, getLabelFont(par));
811 if (par.isLineSeparator(body_pos - 1))
812 tmpx -= singleWidth(par, body_pos - 1);
815 if (hfillExpansion(par, row, c)) {
816 tmpx += singleWidth(par, c);
820 tmpx += r.label_hfill;
821 } else if (par.isSeparator(c)) {
822 tmpx += singleWidth(par, c);
826 tmpx += singleWidth(par, c);
831 if ((tmpx + last_tmpx) / 2 > x) {
836 BOOST_ASSERT(vc <= end); // This shouldn't happen.
839 // This (rtl_support test) is not needed, but gives
840 // some speedup if rtl_support == false
841 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
843 // If lastrow is false, we don't need to compute
845 bool const rtl = lastrow ? isRTL(par) : false;
847 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
848 (!rtl && !left_side && vc == end && x > tmpx + 5)))
850 else if (vc == row.pos()) {
851 c = bidi.vis2log(vc);
852 if (bidi.level(c) % 2 == 1)
855 c = bidi.vis2log(vc - 1);
856 bool const rtl = (bidi.level(c) % 2 == 1);
857 if (left_side == rtl) {
859 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
863 // I believe this code is not needed anymore (Jug 20050717)
865 // The following code is necessary because the cursor position past
866 // the last char in a row is logically equivalent to that before
867 // the first char in the next row. That's why insets causing row
868 // divisions -- Newline and display-style insets -- must be treated
869 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
870 // Newline inset, air gap below:
871 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
872 if (bidi.level(end -1) % 2 == 0)
873 tmpx -= singleWidth(par, end - 1);
875 tmpx += singleWidth(par, end - 1);
879 // Air gap above display inset:
880 if (row.pos() < end && c >= end && end < par.size()
881 && par.isInset(end) && par.getInset(end)->display()) {
884 // Air gap below display inset:
885 if (row.pos() < end && c >= end && par.isInset(end - 1)
886 && par.getInset(end - 1)->display()) {
892 pos_type const col = c - row.pos();
894 if (!c || end == par.size())
897 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
902 return min(col, end - 1 - row.pos());
906 // y is screen coordinate
907 pit_type LyXText::getPitNearY(int y) const
909 BOOST_ASSERT(!paragraphs().empty());
910 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
911 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
913 << BOOST_CURRENT_FUNCTION
914 << ": y: " << y << " cache size: " << cc.size()
917 // look for highest numbered paragraph with y coordinate less than given y
920 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
921 CoordCache::InnerParPosCache::const_iterator et = cc.end();
922 for (; it != et; ++it) {
924 << BOOST_CURRENT_FUNCTION
925 << " examining: pit: " << it->first
926 << " y: " << it->second.y_
929 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
936 << BOOST_CURRENT_FUNCTION
937 << ": found best y: " << yy << " for pit: " << pit
944 Row const & LyXText::getRowNearY(int y, pit_type pit) const
946 Paragraph const & par = pars_[pit];
947 int yy = theCoords.get(this, pit).y_ - par.ascent();
948 BOOST_ASSERT(!par.rows().empty());
949 RowList::const_iterator rit = par.rows().begin();
950 RowList::const_iterator const rlast = boost::prior(par.rows().end());
951 for (; rit != rlast; yy += rit->height(), ++rit)
952 if (yy + rit->height() > y)
958 // x,y are absolute screen coordinates
959 // sets cursor recursively descending into nested editable insets
960 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
962 pit_type pit = getPitNearY(y);
963 BOOST_ASSERT(pit != -1);
964 Row const & row = getRowNearY(y, pit);
967 int xx = x; // is modified by getColumnNearX
968 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
974 // try to descend into nested insets
975 InsetBase * inset = checkInsetHit(x, y);
976 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
978 // Either we deconst editXY or better we move current_font
979 // and real_current_font to LCursor
984 // This should be just before or just behind the
985 // cursor position set above.
986 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
987 || inset == pars_[pit].getInset(pos));
988 // Make sure the cursor points to the position before
990 if (inset == pars_[pit].getInset(pos - 1))
992 inset = inset->editXY(cur, x, y);
993 if (cur.top().text() == this)
999 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1001 if (cur.selection())
1003 if (cur.pos() == cur.lastpos())
1005 InsetBase * inset = cur.nextInset();
1006 if (!isHighlyEditableInset(inset))
1008 inset->edit(cur, front);
1013 bool LyXText::cursorLeft(LCursor & cur)
1015 if (!cur.boundary() && cur.pos() > 0 &&
1016 cur.textRow().pos() == cur.pos() &&
1017 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1018 !cur.paragraph().isNewline(cur.pos()-1)) {
1019 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1021 if (cur.pos() != 0) {
1022 bool boundary = cur.boundary();
1023 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1024 if (!checkAndActivateInset(cur, false)) {
1025 if (false && !boundary &&
1026 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1028 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1030 return updateNeeded;
1033 if (cur.pit() != 0) {
1034 // Steps into the paragraph above
1035 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1041 bool LyXText::cursorRight(LCursor & cur)
1043 if (cur.pos() != cur.lastpos()) {
1045 return setCursor(cur, cur.pit(), cur.pos(),
1048 bool updateNeeded = false;
1049 if (!checkAndActivateInset(cur, true)) {
1050 if (cur.textRow().endpos() == cur.pos() + 1 &&
1051 cur.textRow().endpos() != cur.lastpos() &&
1052 !cur.paragraph().isLineSeparator(cur.pos()) &&
1053 !cur.paragraph().isNewline(cur.pos())) {
1056 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1057 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1059 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1061 return updateNeeded;
1064 if (cur.pit() != cur.lastpit())
1065 return setCursor(cur, cur.pit() + 1, 0);
1070 bool LyXText::cursorUp(LCursor & cur)
1072 Paragraph const & par = cur.paragraph();
1074 int const x = cur.targetX();
1076 if (cur.pos() && cur.boundary())
1077 row = par.pos2row(cur.pos()-1);
1079 row = par.pos2row(cur.pos());
1081 if (!cur.selection()) {
1082 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1084 // Go to middle of previous row. 16 found to work OK;
1085 // 12 = top/bottom margin of display math
1086 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1087 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1088 cur.clearSelection();
1090 // This happens when you move out of an inset.
1091 // And to give the DEPM the possibility of doing
1092 // something we must provide it with two different
1094 LCursor dummy = cur;
1098 return deleteEmptyParagraphMechanism(dummy, old);
1101 bool updateNeeded = false;
1104 updateNeeded |= setCursor(cur, cur.pit(),
1105 x2pos(cur.pit(), row - 1, x));
1106 } else if (cur.pit() > 0) {
1108 //cannot use 'par' now
1109 updateNeeded |= setCursor(cur, cur.pit(),
1110 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1115 return updateNeeded;
1119 bool LyXText::cursorDown(LCursor & cur)
1121 Paragraph const & par = cur.paragraph();
1123 int const x = cur.targetX();
1125 if (cur.pos() && cur.boundary())
1126 row = par.pos2row(cur.pos()-1);
1128 row = par.pos2row(cur.pos());
1130 if (!cur.selection()) {
1131 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1133 // To middle of next row
1134 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1135 editXY(cur, x, y + par.rows()[row].descent() + margin);
1136 cur.clearSelection();
1138 // This happens when you move out of an inset.
1139 // And to give the DEPM the possibility of doing
1140 // something we must provide it with two different
1142 LCursor dummy = cur;
1146 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1148 // Make sure that cur gets back whatever happened to dummy(Lgb)
1155 bool updateNeeded = false;
1157 if (row + 1 < int(par.rows().size())) {
1158 updateNeeded |= setCursor(cur, cur.pit(),
1159 x2pos(cur.pit(), row + 1, x));
1160 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1162 updateNeeded |= setCursor(cur, cur.pit(),
1163 x2pos(cur.pit(), 0, x));
1168 return updateNeeded;
1172 bool LyXText::cursorUpParagraph(LCursor & cur)
1174 bool updated = false;
1176 updated = setCursor(cur, cur.pit(), 0);
1177 else if (cur.pit() != 0)
1178 updated = setCursor(cur, cur.pit() - 1, 0);
1183 bool LyXText::cursorDownParagraph(LCursor & cur)
1185 bool updated = false;
1186 if (cur.pit() != cur.lastpit())
1187 updated = setCursor(cur, cur.pit() + 1, 0);
1189 updated = setCursor(cur, cur.pit(), cur.lastpos());
1194 // fix the cursor `cur' after a characters has been deleted at `where'
1195 // position. Called by deleteEmptyParagraphMechanism
1196 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1198 // Do nothing if cursor is not in the paragraph where the
1199 // deletion occured,
1200 if (cur.pit() != where.pit())
1203 // If cursor position is after the deletion place update it
1204 if (cur.pos() > where.pos())
1207 // Check also if we don't want to set the cursor on a spot behind the
1208 // pagragraph because we erased the last character.
1209 if (cur.pos() > cur.lastpos())
1210 cur.pos() = cur.lastpos();
1214 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1216 // Would be wrong to delete anything if we have a selection.
1217 if (cur.selection())
1220 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1221 // old should point to us
1222 BOOST_ASSERT(old.text() == this);
1224 Paragraph & oldpar = old.paragraph();
1226 // We allow all kinds of "mumbo-jumbo" when freespacing.
1227 if (oldpar.isFreeSpacing())
1230 /* Ok I'll put some comments here about what is missing.
1231 There are still some small problems that can lead to
1232 double spaces stored in the document file or space at
1233 the beginning of paragraphs(). This happens if you have
1234 the cursor between to spaces and then save. Or if you
1235 cut and paste and the selection have a space at the
1236 beginning and then save right after the paste. (Lgb)
1239 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1240 // delete the LineSeparator.
1243 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1244 // delete the LineSeparator.
1247 bool const same_inset = &old.inset() == &cur.inset();
1248 bool const same_par = same_inset && old.pit() == cur.pit();
1249 bool const same_par_pos = same_par && old.pos() == cur.pos();
1251 // If the chars around the old cursor were spaces, delete one of them.
1252 if (!same_par_pos) {
1253 // Only if the cursor has really moved.
1255 && old.pos() < oldpar.size()
1256 && oldpar.isLineSeparator(old.pos())
1257 && oldpar.isLineSeparator(old.pos() - 1)
1258 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1259 // We need to set the text to Change::INSERTED to
1260 // get it erased properly
1261 oldpar.setChange(old.pos() -1, Change::INSERTED);
1262 oldpar.erase(old.pos() - 1);
1263 #ifdef WITH_WARNINGS
1264 #warning This will not work anymore when we have multiple views of the same buffer
1265 // In this case, we will have to correct also the cursors held by
1266 // other bufferviews. It will probably be easier to do that in a more
1267 // automated way in CursorSlice code. (JMarc 26/09/2001)
1269 // correct all cursor parts
1271 fixCursorAfterDelete(cur.top(), old.top());
1278 // only do our magic if we changed paragraph
1282 // don't delete anything if this is the ONLY paragraph!
1283 if (old.lastpit() == 0)
1286 // Do not delete empty paragraphs with keepempty set.
1287 if (oldpar.allowEmpty())
1290 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1292 recordUndo(old, Undo::ATOMIC,
1293 max(old.pit() - 1, pit_type(0)),
1294 min(old.pit() + 1, old.lastpit()));
1295 ParagraphList & plist = old.text()->paragraphs();
1296 plist.erase(boost::next(plist.begin(), old.pit()));
1298 // see #warning above
1299 if (cur.depth() >= old.depth()) {
1300 CursorSlice & curslice = cur[old.depth() - 1];
1301 if (&curslice.inset() == &old.inset()
1302 && curslice.pit() > old.pit()) {
1304 // since a paragraph has been deleted, all the
1305 // insets after `old' have been copied and
1306 // their address has changed. Therefore we
1307 // need to `regenerate' cur. (JMarc)
1308 cur.updateInsets(&(cur.bottom().inset()));
1312 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1313 //ParIterator par_it(old);
1314 //updateLabels(old.buffer(), par_it);
1315 // So for now we do the full update:
1316 updateLabels(old.buffer());
1320 if (oldpar.stripLeadingSpaces())
1327 void LyXText::recUndo(pit_type first, pit_type last) const
1329 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1333 void LyXText::recUndo(pit_type par) const
1335 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1339 int defaultRowHeight()
1341 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);