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/math_hullinset.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 bv.owner()->dispatch(FuncRequest(LFUN_LINE_BEGIN));
370 bv.owner()->dispatch(FuncRequest(LFUN_LINE_END_SELECT));
371 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
372 InsetBase * inset = new InsetEnvironment(params, layout);
373 insertInset(cur, inset);
374 //inset->edit(cur, true);
375 //bv.owner()->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(lyx::to_utf8(_("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(lyx::to_utf8(_("Nothing to index!")));
600 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
601 cur.message(lyx::to_utf8(_("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 cur.boundary(boundary);
723 setCursor(cur.top(), par, pos);
730 void LyXText::setCurrentFont(LCursor & cur)
732 BOOST_ASSERT(this == cur.text());
733 pos_type pos = cur.pos();
734 Paragraph & par = cur.paragraph();
736 if (cur.boundary() && pos > 0)
740 if (pos == cur.lastpos())
742 else // potentional bug... BUG (Lgb)
743 if (par.isSeparator(pos)) {
744 if (pos > cur.textRow().pos() &&
745 bidi.level(pos) % 2 ==
746 bidi.level(pos - 1) % 2)
748 else if (pos + 1 < cur.lastpos())
753 BufferParams const & bufparams = cur.buffer().params();
754 current_font = par.getFontSettings(bufparams, pos);
755 real_current_font = getFont(par, pos);
757 if (cur.pos() == cur.lastpos()
758 && bidi.isBoundary(cur.buffer(), par, cur.pos())
759 && !cur.boundary()) {
760 Language const * lang = par.getParLanguage(bufparams);
761 current_font.setLanguage(lang);
762 current_font.setNumber(LyXFont::OFF);
763 real_current_font.setLanguage(lang);
764 real_current_font.setNumber(LyXFont::OFF);
769 // x is an absolute screen coord
770 // returns the column near the specified x-coordinate of the row
771 // x is set to the real beginning of this column
772 pos_type LyXText::getColumnNearX(pit_type const pit,
773 Row const & row, int & x, bool & boundary) const
775 int const xo = theCoords.get(this, pit).x_;
777 RowMetrics const r = computeRowMetrics(pit, row);
778 Paragraph const & par = pars_[pit];
780 pos_type vc = row.pos();
781 pos_type end = row.endpos();
783 LyXLayout_ptr const & layout = par.layout();
785 bool left_side = false;
787 pos_type body_pos = par.beginOfBody();
790 double last_tmpx = tmpx;
793 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
796 // check for empty row
802 while (vc < end && tmpx <= x) {
803 c = bidi.vis2log(vc);
805 if (body_pos > 0 && c == body_pos - 1) {
806 string lsep = layout->labelsep;
807 docstring dlsep(lsep.begin(), lsep.end());
808 tmpx += r.label_hfill +
809 font_metrics::width(dlsep, getLabelFont(par));
810 if (par.isLineSeparator(body_pos - 1))
811 tmpx -= singleWidth(par, body_pos - 1);
814 if (hfillExpansion(par, row, c)) {
815 tmpx += singleWidth(par, c);
819 tmpx += r.label_hfill;
820 } else if (par.isSeparator(c)) {
821 tmpx += singleWidth(par, c);
825 tmpx += singleWidth(par, c);
830 if ((tmpx + last_tmpx) / 2 > x) {
835 BOOST_ASSERT(vc <= end); // This shouldn't happen.
838 // This (rtl_support test) is not needed, but gives
839 // some speedup if rtl_support == false
840 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
842 // If lastrow is false, we don't need to compute
844 bool const rtl = lastrow ? isRTL(par) : false;
846 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
847 (!rtl && !left_side && vc == end && x > tmpx + 5)))
849 else if (vc == row.pos()) {
850 c = bidi.vis2log(vc);
851 if (bidi.level(c) % 2 == 1)
854 c = bidi.vis2log(vc - 1);
855 bool const rtl = (bidi.level(c) % 2 == 1);
856 if (left_side == rtl) {
858 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
862 // I believe this code is not needed anymore (Jug 20050717)
864 // The following code is necessary because the cursor position past
865 // the last char in a row is logically equivalent to that before
866 // the first char in the next row. That's why insets causing row
867 // divisions -- Newline and display-style insets -- must be treated
868 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
869 // Newline inset, air gap below:
870 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
871 if (bidi.level(end -1) % 2 == 0)
872 tmpx -= singleWidth(par, end - 1);
874 tmpx += singleWidth(par, end - 1);
878 // Air gap above display inset:
879 if (row.pos() < end && c >= end && end < par.size()
880 && par.isInset(end) && par.getInset(end)->display()) {
883 // Air gap below display inset:
884 if (row.pos() < end && c >= end && par.isInset(end - 1)
885 && par.getInset(end - 1)->display()) {
891 pos_type const col = c - row.pos();
893 if (!c || end == par.size())
896 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
901 return min(col, end - 1 - row.pos());
905 // y is screen coordinate
906 pit_type LyXText::getPitNearY(int y) const
908 BOOST_ASSERT(!paragraphs().empty());
909 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
910 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
912 << BOOST_CURRENT_FUNCTION
913 << ": y: " << y << " cache size: " << cc.size()
916 // look for highest numbered paragraph with y coordinate less than given y
919 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
920 CoordCache::InnerParPosCache::const_iterator et = cc.end();
921 for (; it != et; ++it) {
923 << BOOST_CURRENT_FUNCTION
924 << " examining: pit: " << it->first
925 << " y: " << it->second.y_
928 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
935 << BOOST_CURRENT_FUNCTION
936 << ": found best y: " << yy << " for pit: " << pit
943 Row const & LyXText::getRowNearY(int y, pit_type pit) const
945 Paragraph const & par = pars_[pit];
946 int yy = theCoords.get(this, pit).y_ - par.ascent();
947 BOOST_ASSERT(!par.rows().empty());
948 RowList::const_iterator rit = par.rows().begin();
949 RowList::const_iterator const rlast = boost::prior(par.rows().end());
950 for (; rit != rlast; yy += rit->height(), ++rit)
951 if (yy + rit->height() > y)
957 // x,y are absolute screen coordinates
958 // sets cursor recursively descending into nested editable insets
959 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
961 pit_type pit = getPitNearY(y);
962 BOOST_ASSERT(pit != -1);
963 Row const & row = getRowNearY(y, pit);
966 int xx = x; // is modified by getColumnNearX
967 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
973 // try to descend into nested insets
974 InsetBase * inset = checkInsetHit(x, y);
975 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
977 // Either we deconst editXY or better we move current_font
978 // and real_current_font to LCursor
983 // This should be just before or just behind the
984 // cursor position set above.
985 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
986 || inset == pars_[pit].getInset(pos));
987 // Make sure the cursor points to the position before
989 if (inset == pars_[pit].getInset(pos - 1))
991 inset = inset->editXY(cur, x, y);
992 if (cur.top().text() == this)
998 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1000 if (cur.selection())
1002 if (cur.pos() == cur.lastpos())
1004 InsetBase * inset = cur.nextInset();
1005 if (!isHighlyEditableInset(inset))
1007 inset->edit(cur, front);
1012 bool LyXText::cursorLeft(LCursor & cur)
1014 if (!cur.boundary() && cur.pos() > 0 &&
1015 cur.textRow().pos() == cur.pos() &&
1016 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1017 !cur.paragraph().isNewline(cur.pos()-1)) {
1018 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1020 if (cur.pos() != 0) {
1021 bool boundary = cur.boundary();
1022 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1023 if (!checkAndActivateInset(cur, false)) {
1024 if (false && !boundary &&
1025 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1027 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1029 return updateNeeded;
1032 if (cur.pit() != 0) {
1033 // Steps into the paragraph above
1034 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1040 bool LyXText::cursorRight(LCursor & cur)
1042 if (cur.pos() != cur.lastpos()) {
1044 return setCursor(cur, cur.pit(), cur.pos(),
1047 bool updateNeeded = false;
1048 if (!checkAndActivateInset(cur, true)) {
1049 if (cur.textRow().endpos() == cur.pos() + 1 &&
1050 cur.textRow().endpos() != cur.lastpos() &&
1051 !cur.paragraph().isLineSeparator(cur.pos()) &&
1052 !cur.paragraph().isNewline(cur.pos())) {
1055 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1056 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1058 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1060 return updateNeeded;
1063 if (cur.pit() != cur.lastpit())
1064 return setCursor(cur, cur.pit() + 1, 0);
1069 bool LyXText::cursorUp(LCursor & cur)
1071 Paragraph const & par = cur.paragraph();
1073 int const x = cur.targetX();
1075 if (cur.pos() && cur.boundary())
1076 row = par.pos2row(cur.pos()-1);
1078 row = par.pos2row(cur.pos());
1080 if (!cur.selection()) {
1081 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1083 // Go to middle of previous row. 16 found to work OK;
1084 // 12 = top/bottom margin of display math
1085 int const margin = 3 * MathHullInset::displayMargin() / 2;
1086 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1087 cur.clearSelection();
1089 // This happens when you move out of an inset.
1090 // And to give the DEPM the possibility of doing
1091 // something we must provide it with two different
1093 LCursor dummy = cur;
1097 return deleteEmptyParagraphMechanism(dummy, old);
1100 bool updateNeeded = false;
1103 updateNeeded |= setCursor(cur, cur.pit(),
1104 x2pos(cur.pit(), row - 1, x));
1105 } else if (cur.pit() > 0) {
1107 //cannot use 'par' now
1108 updateNeeded |= setCursor(cur, cur.pit(),
1109 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1114 return updateNeeded;
1118 bool LyXText::cursorDown(LCursor & cur)
1120 Paragraph const & par = cur.paragraph();
1122 int const x = cur.targetX();
1124 if (cur.pos() && cur.boundary())
1125 row = par.pos2row(cur.pos()-1);
1127 row = par.pos2row(cur.pos());
1129 if (!cur.selection()) {
1130 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1132 // To middle of next row
1133 int const margin = 3 * MathHullInset::displayMargin() / 2;
1134 editXY(cur, x, y + par.rows()[row].descent() + margin);
1135 cur.clearSelection();
1137 // This happens when you move out of an inset.
1138 // And to give the DEPM the possibility of doing
1139 // something we must provide it with two different
1141 LCursor dummy = cur;
1145 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1147 // Make sure that cur gets back whatever happened to dummy(Lgb)
1154 bool updateNeeded = false;
1156 if (row + 1 < int(par.rows().size())) {
1157 updateNeeded |= setCursor(cur, cur.pit(),
1158 x2pos(cur.pit(), row + 1, x));
1159 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1161 updateNeeded |= setCursor(cur, cur.pit(),
1162 x2pos(cur.pit(), 0, x));
1167 return updateNeeded;
1171 bool LyXText::cursorUpParagraph(LCursor & cur)
1173 bool updated = false;
1175 updated = setCursor(cur, cur.pit(), 0);
1176 else if (cur.pit() != 0)
1177 updated = setCursor(cur, cur.pit() - 1, 0);
1182 bool LyXText::cursorDownParagraph(LCursor & cur)
1184 bool updated = false;
1185 if (cur.pit() != cur.lastpit())
1186 updated = setCursor(cur, cur.pit() + 1, 0);
1188 updated = setCursor(cur, cur.pit(), cur.lastpos());
1193 // fix the cursor `cur' after a characters has been deleted at `where'
1194 // position. Called by deleteEmptyParagraphMechanism
1195 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1197 // Do nothing if cursor is not in the paragraph where the
1198 // deletion occured,
1199 if (cur.pit() != where.pit())
1202 // If cursor position is after the deletion place update it
1203 if (cur.pos() > where.pos())
1206 // Check also if we don't want to set the cursor on a spot behind the
1207 // pagragraph because we erased the last character.
1208 if (cur.pos() > cur.lastpos())
1209 cur.pos() = cur.lastpos();
1213 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1215 // Would be wrong to delete anything if we have a selection.
1216 if (cur.selection())
1219 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1220 // old should point to us
1221 BOOST_ASSERT(old.text() == this);
1223 Paragraph & oldpar = old.paragraph();
1225 // We allow all kinds of "mumbo-jumbo" when freespacing.
1226 if (oldpar.isFreeSpacing())
1229 /* Ok I'll put some comments here about what is missing.
1230 There are still some small problems that can lead to
1231 double spaces stored in the document file or space at
1232 the beginning of paragraphs(). This happens if you have
1233 the cursor between to spaces and then save. Or if you
1234 cut and paste and the selection have a space at the
1235 beginning and then save right after the paste. (Lgb)
1238 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1239 // delete the LineSeparator.
1242 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1243 // delete the LineSeparator.
1246 bool const same_inset = &old.inset() == &cur.inset();
1247 bool const same_par = same_inset && old.pit() == cur.pit();
1248 bool const same_par_pos = same_par && old.pos() == cur.pos();
1250 // If the chars around the old cursor were spaces, delete one of them.
1251 if (!same_par_pos) {
1252 // Only if the cursor has really moved.
1254 && old.pos() < oldpar.size()
1255 && oldpar.isLineSeparator(old.pos())
1256 && oldpar.isLineSeparator(old.pos() - 1)
1257 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1258 // We need to set the text to Change::INSERTED to
1259 // get it erased properly
1260 oldpar.setChange(old.pos() -1, Change::INSERTED);
1261 oldpar.erase(old.pos() - 1);
1262 #ifdef WITH_WARNINGS
1263 #warning This will not work anymore when we have multiple views of the same buffer
1264 // In this case, we will have to correct also the cursors held by
1265 // other bufferviews. It will probably be easier to do that in a more
1266 // automated way in CursorSlice code. (JMarc 26/09/2001)
1268 // correct all cursor parts
1270 fixCursorAfterDelete(cur.top(), old.top());
1277 // only do our magic if we changed paragraph
1281 // don't delete anything if this is the ONLY paragraph!
1282 if (old.lastpit() == 0)
1285 // Do not delete empty paragraphs with keepempty set.
1286 if (oldpar.allowEmpty())
1289 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1291 recordUndo(old, Undo::ATOMIC,
1292 max(old.pit() - 1, pit_type(0)),
1293 min(old.pit() + 1, old.lastpit()));
1294 ParagraphList & plist = old.text()->paragraphs();
1295 plist.erase(boost::next(plist.begin(), old.pit()));
1297 // see #warning above
1298 if (cur.depth() >= old.depth()) {
1299 CursorSlice & curslice = cur[old.depth() - 1];
1300 if (&curslice.inset() == &old.inset()
1301 && curslice.pit() > old.pit()) {
1303 // since a paragraph has been deleted, all the
1304 // insets after `old' have been copied and
1305 // their address has changed. Therefore we
1306 // need to `regenerate' cur. (JMarc)
1307 cur.updateInsets(&(cur.bottom().inset()));
1311 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1312 //ParIterator par_it(old);
1313 //updateLabels(old.buffer(), par_it);
1314 // So for now we do the full update:
1315 updateLabels(old.buffer());
1319 if (oldpar.stripLeadingSpaces())
1326 void LyXText::recUndo(pit_type first, pit_type last) const
1328 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1332 void LyXText::recUndo(pit_type par) const
1334 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1338 int defaultRowHeight()
1340 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);