3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferlist.h"
27 #include "bufferparams.h"
28 #include "BufferView.h"
30 #include "coordcache.h"
32 #include "CutAndPaste.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
36 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "pariterator.h"
48 #include "lyxserver.h"
49 #include "lyxsocket.h"
53 #include "frontends/FontMetrics.h"
55 #include "insets/insetenv.h"
57 #include "mathed/InsetMathHull.h"
59 #include "support/textutils.h"
61 #include <boost/current_function.hpp>
69 using std::ostringstream;
75 LyXText::LyXText(BufferView * bv)
76 : maxwidth_(bv ? bv->workWidth() : 100),
77 current_font(LyXFont::ALL_INHERIT),
78 background_color_(LColor::background),
84 void LyXText::init(BufferView * bv)
88 maxwidth_ = bv->workWidth();
93 pit_type const end = paragraphs().size();
94 for (pit_type pit = 0; pit != end; ++pit)
95 pars_[pit].rows().clear();
97 updateLabels(*bv->buffer());
101 bool LyXText::isMainText() const
103 return &bv()->buffer()->text() == this;
107 //takes screen x,y coordinates
108 InsetBase * LyXText::checkInsetHit(int x, int y) const
110 pit_type pit = getPitNearY(y);
111 BOOST_ASSERT(pit != -1);
113 Paragraph const & par = pars_[pit];
116 << BOOST_CURRENT_FUNCTION
121 InsetList::const_iterator iit = par.insetlist.begin();
122 InsetList::const_iterator iend = par.insetlist.end();
123 for (; iit != iend; ++iit) {
124 InsetBase * inset = iit->inset;
127 << BOOST_CURRENT_FUNCTION
128 << ": examining inset " << inset << endl;
130 if (bv()->coordCache().getInsets().has(inset))
132 << BOOST_CURRENT_FUNCTION
133 << ": xo: " << inset->xo(*bv()) << "..."
134 << inset->xo(*bv()) + inset->width()
135 << " yo: " << inset->yo(*bv()) - inset->ascent()
137 << inset->yo(*bv()) + inset->descent()
141 << BOOST_CURRENT_FUNCTION
142 << ": inset has no cached position" << endl;
144 if (inset->covers(*bv(), x, y)) {
146 << BOOST_CURRENT_FUNCTION
147 << ": Hit inset: " << inset << endl;
152 << BOOST_CURRENT_FUNCTION
153 << ": No inset hit. " << endl;
159 // Gets the fully instantiated font at a given position in a paragraph
160 // Basically the same routine as Paragraph::getFont() in paragraph.C.
161 // The difference is that this one is used for displaying, and thus we
162 // are allowed to make cosmetic improvements. For instance make footnotes
164 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
166 BOOST_ASSERT(pos >= 0);
168 LyXLayout_ptr const & layout = par.layout();
172 BufferParams const & params = bv()->buffer()->params();
173 pos_type const body_pos = par.beginOfBody();
175 // We specialize the 95% common case:
176 if (!par.getDepth()) {
177 LyXFont f = par.getFontSettings(params, pos);
182 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
183 lf = layout->labelfont;
184 rlf = layout->reslabelfont;
187 rlf = layout->resfont;
189 // In case the default family has been customized
190 if (lf.family() == LyXFont::INHERIT_FAMILY)
191 rlf.setFamily(params.getFont().family());
192 return f.realize(rlf);
195 // The uncommon case need not be optimized as much
198 layoutfont = layout->labelfont;
200 layoutfont = layout->font;
202 LyXFont font = par.getFontSettings(params, pos);
203 font.realize(layoutfont);
206 applyOuterFont(font);
208 // Find the pit value belonging to paragraph. This will not break
209 // even if pars_ would not be a vector anymore.
210 // Performance appears acceptable.
212 pit_type pit = pars_.size();
213 for (pit_type it = 0; it < pit; ++it)
214 if (&pars_[it] == &par) {
218 // Realize against environment font information
219 // NOTE: the cast to pit_type should be removed when pit_type
220 // changes to a unsigned integer.
221 if (pit < pit_type(pars_.size()))
222 font.realize(outerFont(pit, pars_));
224 // Realize with the fonts of lesser depth.
225 font.realize(params.getFont());
230 // There are currently two font mechanisms in LyX:
231 // 1. The font attributes in a lyxtext, and
232 // 2. The inset-specific font properties, defined in an inset's
233 // metrics() and draw() methods and handed down the inset chain through
234 // the pi/mi parameters, and stored locally in a lyxtext in font_.
235 // This is where the two are integrated in the final fully realized
237 void LyXText::applyOuterFont(LyXFont & font) const {
239 lf.reduce(bv()->buffer()->params().getFont());
241 lf.setLanguage(font.language());
246 LyXFont LyXText::getLayoutFont(pit_type const pit) const
248 LyXLayout_ptr const & layout = pars_[pit].layout();
250 if (!pars_[pit].getDepth()) {
251 LyXFont lf = layout->resfont;
252 // In case the default family has been customized
253 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
254 lf.setFamily(bv()->buffer()->params().getFont().family());
258 LyXFont font = layout->font;
259 // Realize with the fonts of lesser depth.
260 //font.realize(outerFont(pit, paragraphs()));
261 font.realize(bv()->buffer()->params().getFont());
267 LyXFont LyXText::getLabelFont(Paragraph const & par) const
269 LyXLayout_ptr const & layout = par.layout();
271 if (!par.getDepth()) {
272 LyXFont lf = layout->reslabelfont;
273 // In case the default family has been customized
274 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
275 lf.setFamily(bv()->buffer()->params().getFont().family());
279 LyXFont font = layout->labelfont;
280 // Realize with the fonts of lesser depth.
281 font.realize(bv()->buffer()->params().getFont());
287 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
290 LyXLayout_ptr const & layout = pars_[pit].layout();
292 // Get concrete layout font to reduce against
295 if (pos < pars_[pit].beginOfBody())
296 layoutfont = layout->labelfont;
298 layoutfont = layout->font;
300 // Realize against environment font information
301 if (pars_[pit].getDepth()) {
303 while (!layoutfont.resolved() &&
304 tp != pit_type(paragraphs().size()) &&
305 pars_[tp].getDepth()) {
306 tp = outerHook(tp, paragraphs());
307 if (tp != pit_type(paragraphs().size()))
308 layoutfont.realize(pars_[tp].layout()->font);
312 // Inside inset, apply the inset's font attributes if any
315 layoutfont.realize(font_);
317 layoutfont.realize(bv()->buffer()->params().getFont());
319 // Now, reduce font against full layout font
320 font.reduce(layoutfont);
322 pars_[pit].setFont(pos, font);
326 // return past-the-last paragraph influenced by a layout change on pit
327 pit_type LyXText::undoSpan(pit_type pit)
329 pit_type end = paragraphs().size();
330 pit_type nextpit = pit + 1;
333 //because of parindents
334 if (!pars_[pit].getDepth())
335 return boost::next(nextpit);
336 //because of depth constrains
337 for (; nextpit != end; ++pit, ++nextpit) {
338 if (!pars_[pit].getDepth())
345 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
347 BOOST_ASSERT(start != end);
349 BufferParams const & bufparams = bv()->buffer()->params();
350 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
352 for (pit_type pit = start; pit != end; ++pit) {
353 pars_[pit].applyLayout(lyxlayout);
354 if (lyxlayout->margintype == MARGIN_MANUAL)
355 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
360 // set layout over selection and make a total rebreak of those paragraphs
361 void LyXText::setLayout(LCursor & cur, string const & layout)
363 BOOST_ASSERT(this == cur.text());
364 // special handling of new environment insets
365 BufferView & bv = cur.bv();
366 BufferParams const & params = bv.buffer()->params();
367 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
368 if (lyxlayout->is_environment) {
369 // move everything in a new environment inset
370 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
371 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
372 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
373 lyx::dispatch(FuncRequest(LFUN_CUT));
374 InsetBase * inset = new InsetEnvironment(params, layout);
375 insertInset(cur, inset);
376 //inset->edit(cur, true);
377 //lyx::dispatch(FuncRequest(LFUN_PASTE));
381 pit_type start = cur.selBegin().pit();
382 pit_type end = cur.selEnd().pit() + 1;
383 pit_type undopit = undoSpan(end - 1);
384 recUndo(start, undopit - 1);
385 setLayout(start, end, layout);
386 updateLabels(cur.buffer());
390 static 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)
404 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
406 BOOST_ASSERT(this == cur.text());
407 // this happens when selecting several cells in tabular (bug 2630)
408 if (cur.selBegin().idx() != cur.selEnd().idx())
411 pit_type const beg = cur.selBegin().pit();
412 pit_type const end = cur.selEnd().pit() + 1;
413 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
415 for (pit_type pit = beg; pit != end; ++pit) {
416 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
418 max_depth = pars_[pit].getMaxDepthAfter();
424 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
426 BOOST_ASSERT(this == cur.text());
427 pit_type const beg = cur.selBegin().pit();
428 pit_type const end = cur.selEnd().pit() + 1;
429 recordUndoSelection(cur);
430 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
432 for (pit_type pit = beg; pit != end; ++pit) {
433 Paragraph & par = pars_[pit];
434 if (lyx::changeDepthAllowed(type, par, max_depth)) {
435 int const depth = par.params().depth();
436 if (type == INC_DEPTH)
437 par.params().depth(depth + 1);
439 par.params().depth(depth - 1);
441 max_depth = par.getMaxDepthAfter();
443 // this handles the counter labels, and also fixes up
444 // depth values for follow-on (child) paragraphs
445 updateLabels(cur.buffer());
449 // set font over selection
450 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
452 BOOST_ASSERT(this == cur.text());
453 // if there is no selection just set the current_font
454 if (!cur.selection()) {
455 // Determine basis font
457 pit_type pit = cur.pit();
458 if (cur.pos() < pars_[pit].beginOfBody())
459 layoutfont = getLabelFont(pars_[pit]);
461 layoutfont = getLayoutFont(pit);
463 // Update current font
464 real_current_font.update(font,
465 cur.buffer().params().language,
468 // Reduce to implicit settings
469 current_font = real_current_font;
470 current_font.reduce(layoutfont);
471 // And resolve it completely
472 real_current_font.realize(layoutfont);
477 // Ok, we have a selection.
478 recordUndoSelection(cur);
480 DocIterator dit = cur.selectionBegin();
481 DocIterator ditend = cur.selectionEnd();
483 BufferParams const & params = cur.buffer().params();
485 // Don't use forwardChar here as ditend might have
486 // pos() == lastpos() and forwardChar would miss it.
487 // Can't use forwardPos either as this descends into
489 for (; dit != ditend; dit.forwardPosNoDescend()) {
490 if (dit.pos() != dit.lastpos()) {
491 LyXFont f = getFont(dit.paragraph(), dit.pos());
492 f.update(font, params.language, toggleall);
493 setCharFont(dit.pit(), dit.pos(), f);
499 // the cursor set functions have a special mechanism. When they
500 // realize you left an empty paragraph, they will delete it.
502 bool LyXText::cursorHome(LCursor & cur)
504 BOOST_ASSERT(this == cur.text());
505 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, 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());
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, 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);
601 return to_utf8(idxstring);
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);
632 par.setLabelWidthString(from_ascii(labelwidthstring));
633 params.noindent(noindent);
638 // this really should just insert the inset and not move the cursor.
639 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
641 BOOST_ASSERT(this == cur.text());
643 cur.paragraph().insertInset(cur.pos(), inset,
644 Change(cur.buffer().params().trackChanges ?
645 Change::INSERTED : Change::UNCHANGED));
649 // needed to insert the selection
650 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
652 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
653 current_font, str, autoBreakRows_);
657 // turn double CR to single CR, others are converted into one
658 // blank. Then insertStringAsLines is called
659 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
661 docstring linestr = str;
662 bool newline_inserted = false;
664 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
665 if (linestr[i] == '\n') {
666 if (newline_inserted) {
667 // we know that \r will be ignored by
668 // insertStringAsLines. Of course, it is a dirty
669 // trick, but it works...
670 linestr[i - 1] = '\r';
674 newline_inserted = true;
676 } else if (isPrintable(linestr[i])) {
677 newline_inserted = false;
680 insertStringAsLines(cur, linestr);
684 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
685 bool setfont, bool boundary)
688 setCursorIntern(cur, par, pos, setfont, boundary);
689 return deleteEmptyParagraphMechanism(cur, old);
693 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
695 BOOST_ASSERT(par != int(paragraphs().size()));
699 // now some strict checking
700 Paragraph & para = getPar(par);
702 // None of these should happen, but we're scaredy-cats
704 lyxerr << "dont like -1" << endl;
708 if (pos > para.size()) {
709 lyxerr << "dont like 1, pos: " << pos
710 << " size: " << para.size()
711 << " par: " << par << endl;
717 void LyXText::setCursorIntern(LCursor & cur,
718 pit_type par, pos_type pos, bool setfont, bool boundary)
720 BOOST_ASSERT(this == cur.text());
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 = bv()->coordCache().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 frontend::FontMetrics const & fm
802 = theFontMetrics(getLabelFont(par));
804 while (vc < end && tmpx <= x) {
805 c = bidi.vis2log(vc);
807 if (body_pos > 0 && c == body_pos - 1) {
809 docstring const lsep = from_utf8(layout->labelsep);
810 tmpx += r.label_hfill + fm.width(lsep);
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(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
911 CoordCache::InnerParPosCache const & cc = bv()->coordCache().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 = bv()->coordCache().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 InsetBase * inset2 = pars_[pit].getInset(pos - 1);
987 InsetBase * inset3 = pars_[pit].getInset(pos);
989 BOOST_ASSERT((pos != 0 && inset == inset2)
991 // Make sure the cursor points to the position before
993 if (inset == pars_[pit].getInset(pos - 1))
995 inset = inset->editXY(cur, x, y);
996 if (cur.top().text() == this)
1002 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1004 if (cur.selection())
1006 if (cur.pos() == cur.lastpos())
1008 InsetBase * inset = cur.nextInset();
1009 if (!isHighlyEditableInset(inset))
1011 inset->edit(cur, front);
1016 bool LyXText::cursorLeft(LCursor & cur)
1018 if (!cur.boundary() && cur.pos() > 0 &&
1019 cur.textRow().pos() == cur.pos() &&
1020 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1021 !cur.paragraph().isNewline(cur.pos()-1)) {
1022 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1024 if (cur.pos() != 0) {
1025 bool boundary = cur.boundary();
1026 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1027 if (!checkAndActivateInset(cur, false)) {
1028 if (false && !boundary &&
1029 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1031 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1033 return updateNeeded;
1036 if (cur.pit() != 0) {
1037 // Steps into the paragraph above
1038 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1044 bool LyXText::cursorRight(LCursor & cur)
1046 if (cur.pos() != cur.lastpos()) {
1048 return setCursor(cur, cur.pit(), cur.pos(),
1051 bool updateNeeded = false;
1052 if (!checkAndActivateInset(cur, true)) {
1053 if (cur.textRow().endpos() == cur.pos() + 1 &&
1054 cur.textRow().endpos() != cur.lastpos() &&
1055 !cur.paragraph().isLineSeparator(cur.pos()) &&
1056 !cur.paragraph().isNewline(cur.pos())) {
1059 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1060 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1062 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1064 return updateNeeded;
1067 if (cur.pit() != cur.lastpit())
1068 return setCursor(cur, cur.pit() + 1, 0);
1073 bool LyXText::cursorUp(LCursor & cur)
1075 Paragraph const & par = cur.paragraph();
1077 int const x = cur.targetX();
1079 if (cur.pos() && cur.boundary())
1080 row = par.pos2row(cur.pos()-1);
1082 row = par.pos2row(cur.pos());
1084 if (!cur.selection()) {
1085 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1087 // Go to middle of previous row. 16 found to work OK;
1088 // 12 = top/bottom margin of display math
1089 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1090 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1091 cur.clearSelection();
1093 // This happens when you move out of an inset.
1094 // And to give the DEPM the possibility of doing
1095 // something we must provide it with two different
1097 LCursor dummy = cur;
1101 return deleteEmptyParagraphMechanism(dummy, old);
1104 bool updateNeeded = false;
1107 updateNeeded |= setCursor(cur, cur.pit(),
1108 x2pos(cur.pit(), row - 1, x));
1109 } else if (cur.pit() > 0) {
1111 //cannot use 'par' now
1112 updateNeeded |= setCursor(cur, cur.pit(),
1113 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1118 return updateNeeded;
1122 bool LyXText::cursorDown(LCursor & cur)
1124 Paragraph const & par = cur.paragraph();
1126 int const x = cur.targetX();
1128 if (cur.pos() && cur.boundary())
1129 row = par.pos2row(cur.pos()-1);
1131 row = par.pos2row(cur.pos());
1133 if (!cur.selection()) {
1134 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1136 // To middle of next row
1137 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1138 editXY(cur, x, y + par.rows()[row].descent() + margin);
1139 cur.clearSelection();
1141 // This happens when you move out of an inset.
1142 // And to give the DEPM the possibility of doing
1143 // something we must provide it with two different
1145 LCursor dummy = cur;
1149 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1151 // Make sure that cur gets back whatever happened to dummy(Lgb)
1158 bool updateNeeded = false;
1160 if (row + 1 < int(par.rows().size())) {
1161 updateNeeded |= setCursor(cur, cur.pit(),
1162 x2pos(cur.pit(), row + 1, x));
1163 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1165 updateNeeded |= setCursor(cur, cur.pit(),
1166 x2pos(cur.pit(), 0, x));
1171 return updateNeeded;
1175 bool LyXText::cursorUpParagraph(LCursor & cur)
1177 bool updated = false;
1179 updated = setCursor(cur, cur.pit(), 0);
1180 else if (cur.pit() != 0)
1181 updated = setCursor(cur, cur.pit() - 1, 0);
1186 bool LyXText::cursorDownParagraph(LCursor & cur)
1188 bool updated = false;
1189 if (cur.pit() != cur.lastpit())
1190 updated = setCursor(cur, cur.pit() + 1, 0);
1192 updated = setCursor(cur, cur.pit(), cur.lastpos());
1197 // fix the cursor `cur' after a characters has been deleted at `where'
1198 // position. Called by deleteEmptyParagraphMechanism
1199 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1201 // Do nothing if cursor is not in the paragraph where the
1202 // deletion occured,
1203 if (cur.pit() != where.pit())
1206 // If cursor position is after the deletion place update it
1207 if (cur.pos() > where.pos())
1210 // Check also if we don't want to set the cursor on a spot behind the
1211 // pagragraph because we erased the last character.
1212 if (cur.pos() > cur.lastpos())
1213 cur.pos() = cur.lastpos();
1217 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1219 // Would be wrong to delete anything if we have a selection.
1220 if (cur.selection())
1223 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1224 // old should point to us
1225 BOOST_ASSERT(old.text() == this);
1227 Paragraph & oldpar = old.paragraph();
1229 // We allow all kinds of "mumbo-jumbo" when freespacing.
1230 if (oldpar.isFreeSpacing())
1233 /* Ok I'll put some comments here about what is missing.
1234 There are still some small problems that can lead to
1235 double spaces stored in the document file or space at
1236 the beginning of paragraphs(). This happens if you have
1237 the cursor between to spaces and then save. Or if you
1238 cut and paste and the selection have a space at the
1239 beginning and then save right after the paste. (Lgb)
1242 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1243 // delete the LineSeparator.
1246 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1247 // delete the LineSeparator.
1250 bool const same_inset = &old.inset() == &cur.inset();
1251 bool const same_par = same_inset && old.pit() == cur.pit();
1252 bool const same_par_pos = same_par && old.pos() == cur.pos();
1254 // If the chars around the old cursor were spaces, delete one of them.
1255 if (!same_par_pos) {
1256 // Only if the cursor has really moved.
1258 && old.pos() < oldpar.size()
1259 && oldpar.isLineSeparator(old.pos())
1260 && oldpar.isLineSeparator(old.pos() - 1)
1261 && oldpar.lookupChange(old.pos() - 1).type != Change::DELETED) {
1262 oldpar.erase(old.pos() - 1, false); // do not track changes in DEPM
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(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);