3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
29 #include "coordcache.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
52 #include "insets/insetenv.h"
54 #include "support/textutils.h"
56 #include <boost/current_function.hpp>
64 using std::ostringstream;
70 LyXText::LyXText(BufferView * bv)
71 : maxwidth_(bv ? bv->workWidth() : 100),
72 current_font(LyXFont::ALL_INHERIT),
73 background_color_(LColor::background),
79 void LyXText::init(BufferView * bv)
83 maxwidth_ = bv->workWidth();
88 pit_type const end = paragraphs().size();
89 for (pit_type pit = 0; pit != end; ++pit)
90 pars_[pit].rows().clear();
92 current_font = getFont(pars_[0], 0);
93 updateLabels(*bv->buffer());
97 bool LyXText::isMainText() const
99 return &bv()->buffer()->text() == this;
103 //takes screen x,y coordinates
104 InsetBase * LyXText::checkInsetHit(int x, int y) const
106 pit_type pit = getPitNearY(y);
107 BOOST_ASSERT(pit != -1);
109 Paragraph const & par = pars_[pit];
112 << BOOST_CURRENT_FUNCTION
117 InsetList::const_iterator iit = par.insetlist.begin();
118 InsetList::const_iterator iend = par.insetlist.end();
119 for (; iit != iend; ++iit) {
120 InsetBase * inset = iit->inset;
123 << BOOST_CURRENT_FUNCTION
124 << ": examining inset " << inset << endl;
126 if (theCoords.getInsets().has(inset))
128 << BOOST_CURRENT_FUNCTION
129 << ": xo: " << inset->xo() << "..."
130 << inset->xo() + inset->width()
131 << " yo: " << inset->yo() - inset->ascent()
133 << inset->yo() + inset->descent()
137 << BOOST_CURRENT_FUNCTION
138 << ": inset has no cached position" << endl;
140 if (inset->covers(x, y)) {
142 << BOOST_CURRENT_FUNCTION
143 << ": Hit inset: " << inset << endl;
148 << BOOST_CURRENT_FUNCTION
149 << ": No inset hit. " << endl;
155 // Gets the fully instantiated font at a given position in a paragraph
156 // Basically the same routine as Paragraph::getFont() in paragraph.C.
157 // The difference is that this one is used for displaying, and thus we
158 // are allowed to make cosmetic improvements. For instance make footnotes
160 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
162 BOOST_ASSERT(pos >= 0);
164 LyXLayout_ptr const & layout = par.layout();
168 BufferParams const & params = bv()->buffer()->params();
169 pos_type const body_pos = par.beginOfBody();
171 // We specialize the 95% common case:
172 if (!par.getDepth()) {
173 LyXFont f = par.getFontSettings(params, pos);
178 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
179 lf = layout->labelfont;
180 rlf = layout->reslabelfont;
183 rlf = layout->resfont;
185 // In case the default family has been customized
186 if (lf.family() == LyXFont::INHERIT_FAMILY)
187 rlf.setFamily(params.getFont().family());
188 return f.realize(rlf);
191 // The uncommon case need not be optimized as much
194 layoutfont = layout->labelfont;
196 layoutfont = layout->font;
198 LyXFont font = par.getFontSettings(params, pos);
199 font.realize(layoutfont);
202 applyOuterFont(font);
204 // Find the pit value belonging to paragraph. This will not break
205 // even if pars_ would not be a vector anymore.
206 // Performance appears acceptable.
208 pit_type pit = pars_.size();
209 for (pit_type it = 0; it < pit; ++it)
210 if (&pars_[it] == &par) {
214 // Realize against environment font information
215 // NOTE: the cast to pit_type should be removed when pit_type
216 // changes to a unsigned integer.
217 if (pit < pit_type(pars_.size()))
218 font.realize(outerFont(pit, pars_));
220 // Realize with the fonts of lesser depth.
221 font.realize(params.getFont());
226 // There are currently two font mechanisms in LyX:
227 // 1. The font attributes in a lyxtext, and
228 // 2. The inset-specific font properties, defined in an inset's
229 // metrics() and draw() methods and handed down the inset chain through
230 // the pi/mi parameters, and stored locally in a lyxtext in font_.
231 // This is where the two are integrated in the final fully realized
233 void LyXText::applyOuterFont(LyXFont & font) const {
235 lf.reduce(bv()->buffer()->params().getFont());
237 lf.setLanguage(font.language());
242 LyXFont LyXText::getLayoutFont(pit_type const pit) const
244 LyXLayout_ptr const & layout = pars_[pit].layout();
246 if (!pars_[pit].getDepth()) {
247 LyXFont lf = layout->resfont;
248 // In case the default family has been customized
249 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
250 lf.setFamily(bv()->buffer()->params().getFont().family());
254 LyXFont font = layout->font;
255 // Realize with the fonts of lesser depth.
256 //font.realize(outerFont(pit, paragraphs()));
257 font.realize(bv()->buffer()->params().getFont());
263 LyXFont LyXText::getLabelFont(Paragraph const & par) const
265 LyXLayout_ptr const & layout = par.layout();
267 if (!par.getDepth()) {
268 LyXFont lf = layout->reslabelfont;
269 // In case the default family has been customized
270 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
271 lf.setFamily(bv()->buffer()->params().getFont().family());
275 LyXFont font = layout->labelfont;
276 // Realize with the fonts of lesser depth.
277 font.realize(bv()->buffer()->params().getFont());
283 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
286 LyXLayout_ptr const & layout = pars_[pit].layout();
288 // Get concrete layout font to reduce against
291 if (pos < pars_[pit].beginOfBody())
292 layoutfont = layout->labelfont;
294 layoutfont = layout->font;
296 // Realize against environment font information
297 if (pars_[pit].getDepth()) {
299 while (!layoutfont.resolved() &&
300 tp != pit_type(paragraphs().size()) &&
301 pars_[tp].getDepth()) {
302 tp = outerHook(tp, paragraphs());
303 if (tp != pit_type(paragraphs().size()))
304 layoutfont.realize(pars_[tp].layout()->font);
308 // Inside inset, apply the inset's font attributes if any
311 layoutfont.realize(font_);
313 layoutfont.realize(bv()->buffer()->params().getFont());
315 // Now, reduce font against full layout font
316 font.reduce(layoutfont);
318 pars_[pit].setFont(pos, font);
322 // return past-the-last paragraph influenced by a layout change on pit
323 pit_type LyXText::undoSpan(pit_type pit)
325 pit_type end = paragraphs().size();
326 pit_type nextpit = pit + 1;
329 //because of parindents
330 if (!pars_[pit].getDepth())
331 return boost::next(nextpit);
332 //because of depth constrains
333 for (; nextpit != end; ++pit, ++nextpit) {
334 if (!pars_[pit].getDepth())
341 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
343 BOOST_ASSERT(start != end);
345 BufferParams const & bufparams = bv()->buffer()->params();
346 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
348 for (pit_type pit = start; pit != end; ++pit) {
349 pars_[pit].applyLayout(lyxlayout);
350 if (lyxlayout->margintype == MARGIN_MANUAL)
351 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
356 // set layout over selection and make a total rebreak of those paragraphs
357 void LyXText::setLayout(LCursor & cur, string const & layout)
359 BOOST_ASSERT(this == cur.text());
360 // special handling of new environment insets
361 BufferView & bv = cur.bv();
362 BufferParams const & params = bv.buffer()->params();
363 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
364 if (lyxlayout->is_environment) {
365 // move everything in a new environment inset
366 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
367 bv.owner()->dispatch(FuncRequest(LFUN_LINE_BEGIN));
368 bv.owner()->dispatch(FuncRequest(LFUN_LINE_END_SELECT));
369 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
370 InsetBase * inset = new InsetEnvironment(params, layout);
371 insertInset(cur, inset);
372 //inset->edit(cur, true);
373 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
377 pit_type start = cur.selBegin().pit();
378 pit_type end = cur.selEnd().pit() + 1;
379 pit_type undopit = undoSpan(end - 1);
380 recUndo(start, undopit - 1);
381 setLayout(start, end, layout);
382 updateLabels(cur.buffer());
389 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
390 Paragraph const & par, int max_depth)
392 if (par.layout()->labeltype == LABEL_BIBLIO)
394 int const depth = par.params().depth();
395 if (type == LyXText::INC_DEPTH && depth < max_depth)
397 if (type == LyXText::DEC_DEPTH && depth > 0)
406 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
408 BOOST_ASSERT(this == cur.text());
409 pit_type const beg = cur.selBegin().pit();
410 pit_type const end = cur.selEnd().pit() + 1;
411 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
413 for (pit_type pit = beg; pit != end; ++pit) {
414 if (::changeDepthAllowed(type, pars_[pit], max_depth))
416 max_depth = pars_[pit].getMaxDepthAfter();
422 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
424 BOOST_ASSERT(this == cur.text());
425 pit_type const beg = cur.selBegin().pit();
426 pit_type const end = cur.selEnd().pit() + 1;
427 recordUndoSelection(cur);
428 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
430 for (pit_type pit = beg; pit != end; ++pit) {
431 Paragraph & par = pars_[pit];
432 if (::changeDepthAllowed(type, par, max_depth)) {
433 int const depth = par.params().depth();
434 if (type == INC_DEPTH)
435 par.params().depth(depth + 1);
437 par.params().depth(depth - 1);
439 max_depth = par.getMaxDepthAfter();
441 // this handles the counter labels, and also fixes up
442 // depth values for follow-on (child) paragraphs
443 updateLabels(cur.buffer());
447 // set font over selection
448 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
450 BOOST_ASSERT(this == cur.text());
451 // if there is no selection just set the current_font
452 if (!cur.selection()) {
453 // Determine basis font
455 pit_type pit = cur.pit();
456 if (cur.pos() < pars_[pit].beginOfBody())
457 layoutfont = getLabelFont(pars_[pit]);
459 layoutfont = getLayoutFont(pit);
461 // Update current font
462 real_current_font.update(font,
463 cur.buffer().params().language,
466 // Reduce to implicit settings
467 current_font = real_current_font;
468 current_font.reduce(layoutfont);
469 // And resolve it completely
470 real_current_font.realize(layoutfont);
475 // Ok, we have a selection.
476 recordUndoSelection(cur);
478 DocIterator dit = cur.selectionBegin();
479 DocIterator ditend = cur.selectionEnd();
481 BufferParams const & params = cur.buffer().params();
483 // Don't use forwardChar here as ditend might have
484 // pos() == lastpos() and forwardChar would miss it.
485 // Can't use forwardPos either as this descends into
487 for (; dit != ditend; dit.forwardPosNoDescend()) {
488 if (dit.pos() != dit.lastpos()) {
489 LyXFont f = getFont(dit.paragraph(), dit.pos());
490 f.update(font, params.language, toggleall);
491 setCharFont(dit.pit(), dit.pos(), f);
497 // the cursor set functions have a special mechanism. When they
498 // realize you left an empty paragraph, they will delete it.
500 bool LyXText::cursorHome(LCursor & cur)
502 BOOST_ASSERT(this == cur.text());
503 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
505 return setCursor(cur, cur.pit(), row.pos());
509 bool LyXText::cursorEnd(LCursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 // if not on the last row of the par, put the cursor before
513 // the final space exept if I have a spanning inset or one string
514 // is so long that we force a break.
515 pos_type end = cur.textRow().endpos();
517 // empty text, end-1 is no valid position
519 bool boundary = false;
520 if (end != cur.lastpos()) {
521 if (!cur.paragraph().isLineSeparator(end-1)
522 && !cur.paragraph().isNewline(end-1))
527 return setCursor(cur, cur.pit(), end, true, boundary);
531 bool LyXText::cursorTop(LCursor & cur)
533 BOOST_ASSERT(this == cur.text());
534 return setCursor(cur, 0, 0);
538 bool LyXText::cursorBottom(LCursor & cur)
540 BOOST_ASSERT(this == cur.text());
541 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
545 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
547 BOOST_ASSERT(this == cur.text());
548 // If the mask is completely neutral, tell user
549 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
550 // Could only happen with user style
551 cur.message(_("No font change defined. "
552 "Use Character under the Layout menu to define font change."));
556 // Try implicit word selection
557 // If there is a change in the language the implicit word selection
559 CursorSlice resetCursor = cur.top();
560 bool implicitSelection =
561 font.language() == ignore_language
562 && font.number() == LyXFont::IGNORE
563 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
566 setFont(cur, font, toggleall);
568 // Implicit selections are cleared afterwards
569 // and cursor is set to the original position.
570 if (implicitSelection) {
571 cur.clearSelection();
572 cur.top() = resetCursor;
578 string LyXText::getStringToIndex(LCursor const & cur)
580 BOOST_ASSERT(this == cur.text());
583 if (cur.selection()) {
584 idxstring = cur.selectionAsString(false);
586 // Try implicit word selection. If there is a change
587 // in the language the implicit word selection is
589 LCursor tmpcur = cur;
590 selectWord(tmpcur, lyx::PREVIOUS_WORD);
592 if (!tmpcur.selection())
593 cur.message(_("Nothing to index!"));
594 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
595 cur.message(_("Cannot index more than one paragraph!"));
597 idxstring = tmpcur.selectionAsString(false);
604 void LyXText::setParagraph(LCursor & cur,
605 Spacing const & spacing, LyXAlignment align,
606 string const & labelwidthstring, bool noindent)
608 BOOST_ASSERT(cur.text());
609 // make sure that the depth behind the selection are restored, too
610 pit_type undopit = undoSpan(cur.selEnd().pit());
611 recUndo(cur.selBegin().pit(), undopit - 1);
613 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
615 Paragraph & par = pars_[pit];
616 ParagraphParameters & params = par.params();
617 params.spacing(spacing);
619 // does the layout allow the new alignment?
620 LyXLayout_ptr const & layout = par.layout();
622 if (align == LYX_ALIGN_LAYOUT)
623 align = layout->align;
624 if (align & layout->alignpossible) {
625 if (align == layout->align)
626 params.align(LYX_ALIGN_LAYOUT);
630 par.setLabelWidthString(labelwidthstring);
631 params.noindent(noindent);
636 // this really should just insert the inset and not move the cursor.
637 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
639 BOOST_ASSERT(this == cur.text());
641 cur.paragraph().insertInset(cur.pos(), inset);
645 // needed to insert the selection
646 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
648 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
649 current_font, str, autoBreakRows_);
653 // turn double CR to single CR, others are converted into one
654 // blank. Then insertStringAsLines is called
655 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
657 string linestr = str;
658 bool newline_inserted = false;
660 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
661 if (linestr[i] == '\n') {
662 if (newline_inserted) {
663 // we know that \r will be ignored by
664 // insertStringAsLines. Of course, it is a dirty
665 // trick, but it works...
666 linestr[i - 1] = '\r';
670 newline_inserted = true;
672 } else if (isPrintable(linestr[i])) {
673 newline_inserted = false;
676 insertStringAsLines(cur, linestr);
680 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
681 bool setfont, bool boundary)
684 setCursorIntern(cur, par, pos, setfont, boundary);
685 return deleteEmptyParagraphMechanism(cur, old);
689 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
691 BOOST_ASSERT(par != int(paragraphs().size()));
695 // now some strict checking
696 Paragraph & para = getPar(par);
698 // None of these should happen, but we're scaredy-cats
700 lyxerr << "dont like -1" << endl;
704 if (pos > para.size()) {
705 lyxerr << "dont like 1, pos: " << pos
706 << " size: " << para.size()
707 << " par: " << par << endl;
713 void LyXText::setCursorIntern(LCursor & cur,
714 pit_type par, pos_type pos, bool setfont, bool boundary)
716 cur.boundary(boundary);
717 setCursor(cur.top(), par, pos);
724 void LyXText::setCurrentFont(LCursor & cur)
726 BOOST_ASSERT(this == cur.text());
727 pos_type pos = cur.pos();
728 Paragraph & par = cur.paragraph();
730 if (cur.boundary() && pos > 0)
734 if (pos == cur.lastpos())
736 else // potentional bug... BUG (Lgb)
737 if (par.isSeparator(pos)) {
738 if (pos > cur.textRow().pos() &&
739 bidi.level(pos) % 2 ==
740 bidi.level(pos - 1) % 2)
742 else if (pos + 1 < cur.lastpos())
747 BufferParams const & bufparams = cur.buffer().params();
748 current_font = par.getFontSettings(bufparams, pos);
749 real_current_font = getFont(par, pos);
751 if (cur.pos() == cur.lastpos()
752 && bidi.isBoundary(cur.buffer(), par, cur.pos())
753 && !cur.boundary()) {
754 Language const * lang = par.getParLanguage(bufparams);
755 current_font.setLanguage(lang);
756 current_font.setNumber(LyXFont::OFF);
757 real_current_font.setLanguage(lang);
758 real_current_font.setNumber(LyXFont::OFF);
763 // x is an absolute screen coord
764 // returns the column near the specified x-coordinate of the row
765 // x is set to the real beginning of this column
766 pos_type LyXText::getColumnNearX(pit_type const pit,
767 Row const & row, int & x, bool & boundary) const
769 int const xo = theCoords.get(this, pit).x_;
771 RowMetrics const r = computeRowMetrics(pit, row);
772 Paragraph const & par = pars_[pit];
774 pos_type vc = row.pos();
775 pos_type end = row.endpos();
777 LyXLayout_ptr const & layout = par.layout();
779 bool left_side = false;
781 pos_type body_pos = par.beginOfBody();
784 double last_tmpx = tmpx;
787 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
790 // check for empty row
796 while (vc < end && tmpx <= x) {
797 c = bidi.vis2log(vc);
799 if (body_pos > 0 && c == body_pos - 1) {
800 tmpx += r.label_hfill +
801 font_metrics::width(layout->labelsep, getLabelFont(par));
802 if (par.isLineSeparator(body_pos - 1))
803 tmpx -= singleWidth(par, body_pos - 1);
806 if (hfillExpansion(par, row, c)) {
807 tmpx += singleWidth(par, c);
811 tmpx += r.label_hfill;
812 } else if (par.isSeparator(c)) {
813 tmpx += singleWidth(par, c);
817 tmpx += singleWidth(par, c);
822 if ((tmpx + last_tmpx) / 2 > x) {
827 BOOST_ASSERT(vc <= end); // This shouldn't happen.
830 // This (rtl_support test) is not needed, but gives
831 // some speedup if rtl_support == false
832 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
834 // If lastrow is false, we don't need to compute
836 bool const rtl = lastrow ? isRTL(par) : false;
838 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
839 (!rtl && !left_side && vc == end && x > tmpx + 5)))
841 else if (vc == row.pos()) {
842 c = bidi.vis2log(vc);
843 if (bidi.level(c) % 2 == 1)
846 c = bidi.vis2log(vc - 1);
847 bool const rtl = (bidi.level(c) % 2 == 1);
848 if (left_side == rtl) {
850 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
854 // I believe this code is not needed anymore (Jug 20050717)
856 // The following code is necessary because the cursor position past
857 // the last char in a row is logically equivalent to that before
858 // the first char in the next row. That's why insets causing row
859 // divisions -- Newline and display-style insets -- must be treated
860 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
861 // Newline inset, air gap below:
862 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
863 if (bidi.level(end -1) % 2 == 0)
864 tmpx -= singleWidth(par, end - 1);
866 tmpx += singleWidth(par, end - 1);
870 // Air gap above display inset:
871 if (row.pos() < end && c >= end && end < par.size()
872 && par.isInset(end) && par.getInset(end)->display()) {
875 // Air gap below display inset:
876 if (row.pos() < end && c >= end && par.isInset(end - 1)
877 && par.getInset(end - 1)->display()) {
883 pos_type const col = c - row.pos();
885 if (!c || end == par.size())
888 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
893 return min(col, end - 1 - row.pos());
897 // y is screen coordinate
898 pit_type LyXText::getPitNearY(int y) const
900 BOOST_ASSERT(!paragraphs().empty());
901 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
902 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
904 << BOOST_CURRENT_FUNCTION
905 << ": y: " << y << " cache size: " << cc.size()
908 // look for highest numbered paragraph with y coordinate less than given y
911 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
912 CoordCache::InnerParPosCache::const_iterator et = cc.end();
913 for (; it != et; ++it) {
915 << BOOST_CURRENT_FUNCTION
916 << " examining: pit: " << it->first
917 << " y: " << it->second.y_
920 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
927 << BOOST_CURRENT_FUNCTION
928 << ": found best y: " << yy << " for pit: " << pit
935 Row const & LyXText::getRowNearY(int y, pit_type pit) const
937 Paragraph const & par = pars_[pit];
938 int yy = theCoords.get(this, pit).y_ - par.ascent();
939 BOOST_ASSERT(!par.rows().empty());
940 RowList::const_iterator rit = par.rows().begin();
941 RowList::const_iterator const rlast = boost::prior(par.rows().end());
942 for (; rit != rlast; yy += rit->height(), ++rit)
943 if (yy + rit->height() > y)
949 // x,y are absolute screen coordinates
950 // sets cursor recursively descending into nested editable insets
951 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
953 pit_type pit = getPitNearY(y);
954 BOOST_ASSERT(pit != -1);
955 Row const & row = getRowNearY(y, pit);
958 int xx = x; // is modified by getColumnNearX
959 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
965 // try to descend into nested insets
966 InsetBase * inset = checkInsetHit(x, y);
967 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
969 // Either we deconst editXY or better we move current_font
970 // and real_current_font to LCursor
975 // This should be just before or just behind the
976 // cursor position set above.
977 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
978 || inset == pars_[pit].getInset(pos));
979 // Make sure the cursor points to the position before
981 if (inset == pars_[pit].getInset(pos - 1))
983 inset = inset->editXY(cur, x, y);
984 if (cur.top().text() == this)
990 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
994 if (cur.pos() == cur.lastpos())
996 InsetBase * inset = cur.nextInset();
997 if (!isHighlyEditableInset(inset))
999 inset->edit(cur, front);
1004 bool LyXText::cursorLeft(LCursor & cur)
1006 if (!cur.boundary() && cur.pos() > 0 &&
1007 cur.textRow().pos() == cur.pos() &&
1008 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1009 !cur.paragraph().isNewline(cur.pos()-1)) {
1010 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1012 if (cur.pos() != 0) {
1013 bool boundary = cur.boundary();
1014 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1015 if (!checkAndActivateInset(cur, false)) {
1016 if (false && !boundary &&
1017 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1019 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1021 return updateNeeded;
1024 if (cur.pit() != 0) {
1025 // Steps into the paragraph above
1026 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1032 bool LyXText::cursorRight(LCursor & cur)
1034 if (cur.pos() != cur.lastpos()) {
1036 return setCursor(cur, cur.pit(), cur.pos(),
1039 bool updateNeeded = false;
1040 if (!checkAndActivateInset(cur, true)) {
1041 if (cur.textRow().endpos() == cur.pos() + 1 &&
1042 cur.textRow().endpos() != cur.lastpos() &&
1043 !cur.paragraph().isLineSeparator(cur.pos()) &&
1044 !cur.paragraph().isNewline(cur.pos())) {
1047 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1048 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1050 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1052 return updateNeeded;
1055 if (cur.pit() != cur.lastpit())
1056 return setCursor(cur, cur.pit() + 1, 0);
1061 bool LyXText::cursorUp(LCursor & cur)
1063 Paragraph const & par = cur.paragraph();
1065 int const x = cur.targetX();
1067 if (cur.pos() && cur.boundary())
1068 row = par.pos2row(cur.pos()-1);
1070 row = par.pos2row(cur.pos());
1072 if (!cur.selection()) {
1073 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1075 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1076 cur.clearSelection();
1078 // This happens when you move out of an inset.
1079 // And to give the DEPM the possibility of doing
1080 // something we must provide it with two different
1082 LCursor dummy = cur;
1086 return deleteEmptyParagraphMechanism(dummy, old);
1089 bool updateNeeded = false;
1092 updateNeeded |= setCursor(cur, cur.pit(),
1093 x2pos(cur.pit(), row - 1, x));
1094 } else if (cur.pit() > 0) {
1096 //cannot use 'par' now
1097 updateNeeded |= setCursor(cur, cur.pit(),
1098 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1103 return updateNeeded;
1107 bool LyXText::cursorDown(LCursor & cur)
1109 Paragraph const & par = cur.paragraph();
1111 int const x = cur.targetX();
1113 if (cur.pos() && cur.boundary())
1114 row = par.pos2row(cur.pos()-1);
1116 row = par.pos2row(cur.pos());
1118 if (!cur.selection()) {
1119 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1121 editXY(cur, x, y + par.rows()[row].descent() + 1);
1122 cur.clearSelection();
1124 // This happens when you move out of an inset.
1125 // And to give the DEPM the possibility of doing
1126 // something we must provide it with two different
1128 LCursor dummy = cur;
1132 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1134 // Make sure that cur gets back whatever happened to dummy(Lgb)
1141 bool updateNeeded = false;
1143 if (row + 1 < int(par.rows().size())) {
1144 updateNeeded |= setCursor(cur, cur.pit(),
1145 x2pos(cur.pit(), row + 1, x));
1146 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1148 updateNeeded |= setCursor(cur, cur.pit(),
1149 x2pos(cur.pit(), 0, x));
1154 return updateNeeded;
1158 bool LyXText::cursorUpParagraph(LCursor & cur)
1160 bool updated = false;
1162 updated = setCursor(cur, cur.pit(), 0);
1163 else if (cur.pit() != 0)
1164 updated = setCursor(cur, cur.pit() - 1, 0);
1169 bool LyXText::cursorDownParagraph(LCursor & cur)
1171 bool updated = false;
1172 if (cur.pit() != cur.lastpit())
1173 updated = setCursor(cur, cur.pit() + 1, 0);
1175 updated = setCursor(cur, cur.pit(), cur.lastpos());
1180 // fix the cursor `cur' after a characters has been deleted at `where'
1181 // position. Called by deleteEmptyParagraphMechanism
1182 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1184 // Do nothing if cursor is not in the paragraph where the
1185 // deletion occured,
1186 if (cur.pit() != where.pit())
1189 // If cursor position is after the deletion place update it
1190 if (cur.pos() > where.pos())
1193 // Check also if we don't want to set the cursor on a spot behind the
1194 // pagragraph because we erased the last character.
1195 if (cur.pos() > cur.lastpos())
1196 cur.pos() = cur.lastpos();
1200 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1202 // Would be wrong to delete anything if we have a selection.
1203 if (cur.selection())
1206 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1207 // old should point to us
1208 BOOST_ASSERT(old.text() == this);
1210 Paragraph & oldpar = old.paragraph();
1212 // We allow all kinds of "mumbo-jumbo" when freespacing.
1213 if (oldpar.isFreeSpacing())
1216 /* Ok I'll put some comments here about what is missing.
1217 There are still some small problems that can lead to
1218 double spaces stored in the document file or space at
1219 the beginning of paragraphs(). This happens if you have
1220 the cursor between to spaces and then save. Or if you
1221 cut and paste and the selection have a space at the
1222 beginning and then save right after the paste. (Lgb)
1225 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1226 // delete the LineSeparator.
1229 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1230 // delete the LineSeparator.
1233 bool const same_inset = &old.inset() == &cur.inset();
1234 bool const same_par = same_inset && old.pit() == cur.pit();
1235 bool const same_par_pos = same_par && old.pos() == cur.pos();
1237 // If the chars around the old cursor were spaces, delete one of them.
1238 if (!same_par_pos) {
1239 // Only if the cursor has really moved.
1241 && old.pos() < oldpar.size()
1242 && oldpar.isLineSeparator(old.pos())
1243 && oldpar.isLineSeparator(old.pos() - 1)
1244 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1245 // We need to set the text to Change::INSERTED to
1246 // get it erased properly
1247 oldpar.setChange(old.pos() -1, Change::INSERTED);
1248 oldpar.erase(old.pos() - 1);
1249 #ifdef WITH_WARNINGS
1250 #warning This will not work anymore when we have multiple views of the same buffer
1251 // In this case, we will have to correct also the cursors held by
1252 // other bufferviews. It will probably be easier to do that in a more
1253 // automated way in CursorSlice code. (JMarc 26/09/2001)
1255 // correct all cursor parts
1257 fixCursorAfterDelete(cur.top(), old.top());
1264 // only do our magic if we changed paragraph
1268 // don't delete anything if this is the ONLY paragraph!
1269 if (old.lastpit() == 0)
1272 // Do not delete empty paragraphs with keepempty set.
1273 if (oldpar.allowEmpty())
1276 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1278 recordUndo(old, Undo::ATOMIC,
1279 max(old.pit() - 1, pit_type(0)),
1280 min(old.pit() + 1, old.lastpit()));
1281 ParagraphList & plist = old.text()->paragraphs();
1282 plist.erase(boost::next(plist.begin(), old.pit()));
1284 // see #warning above
1285 if (cur.depth() >= old.depth()) {
1286 CursorSlice & curslice = cur[old.depth() - 1];
1287 if (&curslice.inset() == &old.inset()
1288 && curslice.pit() > old.pit()) {
1290 // since a paragraph has been deleted, all the
1291 // insets after `old' have been copied and
1292 // their address has changed. Therefore we
1293 // need to `regenerate' cur. (JMarc)
1294 cur.updateInsets(&(cur.bottom().inset()));
1298 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1299 //ParIterator par_it(old);
1300 //updateLabels(old.buffer(), par_it);
1301 // So for now we do the full update:
1302 updateLabels(old.buffer());
1306 if (oldpar.stripLeadingSpaces())
1313 void LyXText::recUndo(pit_type first, pit_type last) const
1315 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1319 void LyXText::recUndo(pit_type par) const
1321 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1325 int defaultRowHeight()
1327 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);