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;
69 LyXText::LyXText(BufferView * bv)
70 : maxwidth_(bv ? bv->workWidth() : 100),
71 current_font(LyXFont::ALL_INHERIT),
72 background_color_(LColor::background),
78 void LyXText::init(BufferView * bv)
82 maxwidth_ = bv->workWidth();
87 pit_type const end = paragraphs().size();
88 for (pit_type pit = 0; pit != end; ++pit)
89 pars_[pit].rows().clear();
91 current_font = getFont(pars_[0], 0);
92 updateCounters(*bv->buffer());
96 bool LyXText::isMainText() const
98 return &bv()->buffer()->text() == this;
102 //takes screen x,y coordinates
103 InsetBase * LyXText::checkInsetHit(int x, int y) const
105 pit_type pit = getPitNearY(y);
106 BOOST_ASSERT(pit != -1);
108 Paragraph const & par = pars_[pit];
111 << BOOST_CURRENT_FUNCTION
116 InsetList::const_iterator iit = par.insetlist.begin();
117 InsetList::const_iterator iend = par.insetlist.end();
118 for (; iit != iend; ++iit) {
119 InsetBase * inset = iit->inset;
122 << BOOST_CURRENT_FUNCTION
123 << ": examining inset " << inset << endl;
125 if (theCoords.getInsets().has(inset))
127 << BOOST_CURRENT_FUNCTION
128 << ": xo: " << inset->xo() << "..."
129 << inset->xo() + inset->width()
130 << " yo: " << inset->yo() - inset->ascent()
132 << inset->yo() + inset->descent()
136 << BOOST_CURRENT_FUNCTION
137 << ": inset has no cached position" << endl;
139 if (inset->covers(x, y)) {
141 << BOOST_CURRENT_FUNCTION
142 << ": Hit inset: " << inset << endl;
147 << BOOST_CURRENT_FUNCTION
148 << ": No inset hit. " << endl;
154 // Gets the fully instantiated font at a given position in a paragraph
155 // Basically the same routine as Paragraph::getFont() in paragraph.C.
156 // The difference is that this one is used for displaying, and thus we
157 // are allowed to make cosmetic improvements. For instance make footnotes
159 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
161 BOOST_ASSERT(pos >= 0);
163 LyXLayout_ptr const & layout = par.layout();
167 BufferParams const & params = bv()->buffer()->params();
168 pos_type const body_pos = par.beginOfBody();
170 // We specialize the 95% common case:
171 if (!par.getDepth()) {
172 LyXFont f = par.getFontSettings(params, pos);
175 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
176 return f.realize(layout->reslabelfont);
178 return f.realize(layout->resfont);
181 // The uncommon case need not be optimized as much
184 layoutfont = layout->labelfont;
186 layoutfont = layout->font;
188 LyXFont font = par.getFontSettings(params, pos);
189 font.realize(layoutfont);
192 applyOuterFont(font);
194 // Find the pit value belonging to paragraph. This will not break
195 // even if pars_ would not be a vector anymore.
196 // Performance appears acceptable.
198 pit_type pit = pars_.size();
199 for (pit_type it = 0; it < pit; ++it)
200 if (&pars_[it] == &par) {
204 // Realize against environment font information
205 if (pit < pars_.size())
206 font.realize(outerFont(pit, pars_));
208 // Realize with the fonts of lesser depth.
209 font.realize(defaultfont_);
214 // There are currently two font mechanisms in LyX:
215 // 1. The font attributes in a lyxtext, and
216 // 2. The inset-specific font properties, defined in an inset's
217 // metrics() and draw() methods and handed down the inset chain through
218 // the pi/mi parameters, and stored locally in a lyxtext in font_.
219 // This is where the two are integrated in the final fully realized
221 void LyXText::applyOuterFont(LyXFont & font) const {
223 lf.reduce(defaultfont_);
225 lf.setLanguage(font.language());
230 LyXFont LyXText::getLayoutFont(pit_type const pit) const
232 LyXLayout_ptr const & layout = pars_[pit].layout();
234 if (!pars_[pit].getDepth())
235 return layout->resfont;
237 LyXFont font = layout->font;
238 // Realize with the fonts of lesser depth.
239 //font.realize(outerFont(pit, paragraphs()));
240 font.realize(defaultfont_);
246 LyXFont LyXText::getLabelFont(Paragraph const & par) const
248 LyXLayout_ptr const & layout = par.layout();
251 return layout->reslabelfont;
253 LyXFont font = layout->labelfont;
254 // Realize with the fonts of lesser depth.
255 font.realize(defaultfont_);
261 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
264 LyXLayout_ptr const & layout = pars_[pit].layout();
266 // Get concrete layout font to reduce against
269 if (pos < pars_[pit].beginOfBody())
270 layoutfont = layout->labelfont;
272 layoutfont = layout->font;
274 // Realize against environment font information
275 if (pars_[pit].getDepth()) {
277 while (!layoutfont.resolved() &&
278 tp != pit_type(paragraphs().size()) &&
279 pars_[tp].getDepth()) {
280 tp = outerHook(tp, paragraphs());
281 if (tp != pit_type(paragraphs().size()))
282 layoutfont.realize(pars_[tp].layout()->font);
286 layoutfont.realize(defaultfont_);
288 // Now, reduce font against full layout font
289 font.reduce(layoutfont);
291 pars_[pit].setFont(pos, font);
295 // return past-the-last paragraph influenced by a layout change on pit
296 pit_type LyXText::undoSpan(pit_type pit)
298 pit_type end = paragraphs().size();
299 pit_type nextpit = pit + 1;
302 //because of parindents
303 if (!pars_[pit].getDepth())
304 return boost::next(nextpit);
305 //because of depth constrains
306 for (; nextpit != end; ++pit, ++nextpit) {
307 if (!pars_[pit].getDepth())
314 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
316 BOOST_ASSERT(start != end);
318 BufferParams const & bufparams = bv()->buffer()->params();
319 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
321 for (pit_type pit = start; pit != end; ++pit) {
322 pars_[pit].applyLayout(lyxlayout);
323 if (lyxlayout->margintype == MARGIN_MANUAL)
324 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
329 // set layout over selection and make a total rebreak of those paragraphs
330 void LyXText::setLayout(LCursor & cur, string const & layout)
332 BOOST_ASSERT(this == cur.text());
333 // special handling of new environment insets
334 BufferView & bv = cur.bv();
335 BufferParams const & params = bv.buffer()->params();
336 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
337 if (lyxlayout->is_environment) {
338 // move everything in a new environment inset
339 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
340 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
341 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
342 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
343 InsetBase * inset = new InsetEnvironment(params, layout);
344 insertInset(cur, inset);
345 //inset->edit(cur, true);
346 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
350 pit_type start = cur.selBegin().pit();
351 pit_type end = cur.selEnd().pit() + 1;
352 pit_type undopit = undoSpan(end - 1);
353 recUndo(start, undopit - 1);
354 setLayout(start, end, layout);
355 updateCounters(cur.buffer());
362 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
363 Paragraph const & par, int max_depth)
365 if (par.layout()->labeltype == LABEL_BIBLIO)
367 int const depth = par.params().depth();
368 if (type == LyXText::INC_DEPTH && depth < max_depth)
370 if (type == LyXText::DEC_DEPTH && depth > 0)
379 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
381 BOOST_ASSERT(this == cur.text());
382 pit_type const beg = cur.selBegin().pit();
383 pit_type const end = cur.selEnd().pit() + 1;
384 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
386 for (pit_type pit = beg; pit != end; ++pit) {
387 if (::changeDepthAllowed(type, pars_[pit], max_depth))
389 max_depth = pars_[pit].getMaxDepthAfter();
395 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
397 BOOST_ASSERT(this == cur.text());
398 pit_type const beg = cur.selBegin().pit();
399 pit_type const end = cur.selEnd().pit() + 1;
400 recordUndoSelection(cur);
401 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
403 for (pit_type pit = beg; pit != end; ++pit) {
404 Paragraph & par = pars_[pit];
405 if (::changeDepthAllowed(type, par, max_depth)) {
406 int const depth = par.params().depth();
407 if (type == INC_DEPTH)
408 par.params().depth(depth + 1);
410 par.params().depth(depth - 1);
412 max_depth = par.getMaxDepthAfter();
414 // this handles the counter labels, and also fixes up
415 // depth values for follow-on (child) paragraphs
416 updateCounters(cur.buffer());
420 // set font over selection
421 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
423 BOOST_ASSERT(this == cur.text());
424 // if there is no selection just set the current_font
425 if (!cur.selection()) {
426 // Determine basis font
428 pit_type pit = cur.pit();
429 if (cur.pos() < pars_[pit].beginOfBody())
430 layoutfont = getLabelFont(pars_[pit]);
432 layoutfont = getLayoutFont(pit);
434 // Update current font
435 real_current_font.update(font,
436 cur.buffer().params().language,
439 // Reduce to implicit settings
440 current_font = real_current_font;
441 current_font.reduce(layoutfont);
442 // And resolve it completely
443 real_current_font.realize(layoutfont);
448 // Ok, we have a selection.
449 recordUndoSelection(cur);
451 DocIterator dit = cur.selectionBegin();
452 DocIterator ditend = cur.selectionEnd();
454 BufferParams const & params = cur.buffer().params();
456 // Don't use forwardChar here as ditend might have
457 // pos() == lastpos() and forwardChar would miss it.
458 // Can't use forwardPos either as this descends into
460 for (; dit != ditend; dit.forwardPosNoDescend()) {
461 if (dit.pos() != dit.lastpos()) {
462 LyXFont f = getFont(dit.paragraph(), dit.pos());
463 f.update(font, params.language, toggleall);
464 setCharFont(dit.pit(), dit.pos(), f);
470 // the cursor set functions have a special mechanism. When they
471 // realize you left an empty paragraph, they will delete it.
473 void LyXText::cursorHome(LCursor & cur)
475 BOOST_ASSERT(this == cur.text());
476 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
478 setCursor(cur, cur.pit(), row.pos());
482 void LyXText::cursorEnd(LCursor & cur)
484 BOOST_ASSERT(this == cur.text());
485 // if not on the last row of the par, put the cursor before
486 // the final space exept if I have a spanning inset or one string
487 // is so long that we force a break.
488 pos_type end = cur.textRow().endpos();
490 // empty text, end-1 is no valid position
492 bool boundary = false;
493 if (end != cur.lastpos()) {
494 if (!cur.paragraph().isLineSeparator(end-1)
495 && !cur.paragraph().isNewline(end-1))
500 setCursor(cur, cur.pit(), end, true, boundary);
504 void LyXText::cursorTop(LCursor & cur)
506 BOOST_ASSERT(this == cur.text());
507 setCursor(cur, 0, 0);
511 void LyXText::cursorBottom(LCursor & cur)
513 BOOST_ASSERT(this == cur.text());
514 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
518 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
520 BOOST_ASSERT(this == cur.text());
521 // If the mask is completely neutral, tell user
522 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
523 // Could only happen with user style
524 cur.message(_("No font change defined. "
525 "Use Character under the Layout menu to define font change."));
529 // Try implicit word selection
530 // If there is a change in the language the implicit word selection
532 CursorSlice resetCursor = cur.top();
533 bool implicitSelection =
534 font.language() == ignore_language
535 && font.number() == LyXFont::IGNORE
536 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
539 setFont(cur, font, toggleall);
541 // Implicit selections are cleared afterwards
542 // and cursor is set to the original position.
543 if (implicitSelection) {
544 cur.clearSelection();
545 cur.top() = resetCursor;
551 string LyXText::getStringToIndex(LCursor const & cur)
553 BOOST_ASSERT(this == cur.text());
556 if (cur.selection()) {
557 idxstring = cur.selectionAsString(false);
559 // Try implicit word selection. If there is a change
560 // in the language the implicit word selection is
562 LCursor tmpcur = cur;
563 selectWord(tmpcur, lyx::PREVIOUS_WORD);
565 if (!tmpcur.selection())
566 cur.message(_("Nothing to index!"));
567 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
568 cur.message(_("Cannot index more than one paragraph!"));
570 idxstring = tmpcur.selectionAsString(false);
577 void LyXText::setParagraph(LCursor & cur,
578 Spacing const & spacing, LyXAlignment align,
579 string const & labelwidthstring, bool noindent)
581 BOOST_ASSERT(cur.text());
582 // make sure that the depth behind the selection are restored, too
583 pit_type undopit = undoSpan(cur.selEnd().pit());
584 recUndo(cur.selBegin().pit(), undopit - 1);
586 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
588 Paragraph & par = pars_[pit];
589 ParagraphParameters & params = par.params();
590 params.spacing(spacing);
592 // does the layout allow the new alignment?
593 LyXLayout_ptr const & layout = par.layout();
595 if (align == LYX_ALIGN_LAYOUT)
596 align = layout->align;
597 if (align & layout->alignpossible) {
598 if (align == layout->align)
599 params.align(LYX_ALIGN_LAYOUT);
603 par.setLabelWidthString(labelwidthstring);
604 params.noindent(noindent);
609 // this really should just insert the inset and not move the cursor.
610 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
612 BOOST_ASSERT(this == cur.text());
614 cur.paragraph().insertInset(cur.pos(), inset);
618 // needed to insert the selection
619 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
621 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
622 current_font, str, autoBreakRows_);
626 // turn double CR to single CR, others are converted into one
627 // blank. Then insertStringAsLines is called
628 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
630 string linestr = str;
631 bool newline_inserted = false;
633 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
634 if (linestr[i] == '\n') {
635 if (newline_inserted) {
636 // we know that \r will be ignored by
637 // insertStringAsLines. Of course, it is a dirty
638 // trick, but it works...
639 linestr[i - 1] = '\r';
643 newline_inserted = true;
645 } else if (IsPrintable(linestr[i])) {
646 newline_inserted = false;
649 insertStringAsLines(cur, linestr);
653 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
654 bool setfont, bool boundary)
657 setCursorIntern(cur, par, pos, setfont, boundary);
658 return deleteEmptyParagraphMechanism(cur, old);
662 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
664 BOOST_ASSERT(par != int(paragraphs().size()));
668 // now some strict checking
669 Paragraph & para = getPar(par);
671 // None of these should happen, but we're scaredy-cats
673 lyxerr << "dont like -1" << endl;
677 if (pos > para.size()) {
678 lyxerr << "dont like 1, pos: " << pos
679 << " size: " << para.size()
680 << " par: " << par << endl;
686 void LyXText::setCursorIntern(LCursor & cur,
687 pit_type par, pos_type pos, bool setfont, bool boundary)
689 cur.boundary(boundary);
690 setCursor(cur.top(), par, pos);
697 void LyXText::setCurrentFont(LCursor & cur)
699 BOOST_ASSERT(this == cur.text());
700 pos_type pos = cur.pos();
701 Paragraph & par = cur.paragraph();
703 if (cur.boundary() && pos > 0)
707 if (pos == cur.lastpos())
709 else // potentional bug... BUG (Lgb)
710 if (par.isSeparator(pos)) {
711 if (pos > cur.textRow().pos() &&
712 bidi.level(pos) % 2 ==
713 bidi.level(pos - 1) % 2)
715 else if (pos + 1 < cur.lastpos())
720 BufferParams const & bufparams = cur.buffer().params();
721 current_font = par.getFontSettings(bufparams, pos);
722 real_current_font = getFont(par, pos);
724 if (cur.pos() == cur.lastpos()
725 && bidi.isBoundary(cur.buffer(), par, cur.pos())
726 && !cur.boundary()) {
727 Language const * lang = par.getParLanguage(bufparams);
728 current_font.setLanguage(lang);
729 current_font.setNumber(LyXFont::OFF);
730 real_current_font.setLanguage(lang);
731 real_current_font.setNumber(LyXFont::OFF);
736 // x is an absolute screen coord
737 // returns the column near the specified x-coordinate of the row
738 // x is set to the real beginning of this column
739 pos_type LyXText::getColumnNearX(pit_type const pit,
740 Row const & row, int & x, bool & boundary) const
742 int const xo = theCoords.get(this, pit).x_;
744 RowMetrics const r = computeRowMetrics(pit, row);
745 Paragraph const & par = pars_[pit];
747 pos_type vc = row.pos();
748 pos_type end = row.endpos();
750 LyXLayout_ptr const & layout = par.layout();
752 bool left_side = false;
754 pos_type body_pos = par.beginOfBody();
757 double last_tmpx = tmpx;
760 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
763 // check for empty row
769 while (vc < end && tmpx <= x) {
770 c = bidi.vis2log(vc);
772 if (body_pos > 0 && c == body_pos - 1) {
773 tmpx += r.label_hfill +
774 font_metrics::width(layout->labelsep, getLabelFont(par));
775 if (par.isLineSeparator(body_pos - 1))
776 tmpx -= singleWidth(par, body_pos - 1);
779 if (hfillExpansion(par, row, c)) {
780 tmpx += singleWidth(par, c);
784 tmpx += r.label_hfill;
785 } else if (par.isSeparator(c)) {
786 tmpx += singleWidth(par, c);
790 tmpx += singleWidth(par, c);
795 if ((tmpx + last_tmpx) / 2 > x) {
800 BOOST_ASSERT(vc <= end); // This shouldn't happen.
803 // This (rtl_support test) is not needed, but gives
804 // some speedup if rtl_support == false
805 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
807 // If lastrow is false, we don't need to compute
809 bool const rtl = lastrow ? isRTL(par) : false;
811 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
812 (!rtl && !left_side && vc == end && x > tmpx + 5)))
814 else if (vc == row.pos()) {
815 c = bidi.vis2log(vc);
816 if (bidi.level(c) % 2 == 1)
819 c = bidi.vis2log(vc - 1);
820 bool const rtl = (bidi.level(c) % 2 == 1);
821 if (left_side == rtl) {
823 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
827 // I believe this code is not needed anymore (Jug 20050717)
829 // The following code is necessary because the cursor position past
830 // the last char in a row is logically equivalent to that before
831 // the first char in the next row. That's why insets causing row
832 // divisions -- Newline and display-style insets -- must be treated
833 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
834 // Newline inset, air gap below:
835 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
836 if (bidi.level(end -1) % 2 == 0)
837 tmpx -= singleWidth(par, end - 1);
839 tmpx += singleWidth(par, end - 1);
843 // Air gap above display inset:
844 if (row.pos() < end && c >= end && end < par.size()
845 && par.isInset(end) && par.getInset(end)->display()) {
848 // Air gap below display inset:
849 if (row.pos() < end && c >= end && par.isInset(end - 1)
850 && par.getInset(end - 1)->display()) {
856 pos_type const col = c - row.pos();
858 if (!c || end == par.size())
861 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
866 return min(col, end - 1 - row.pos());
870 // y is screen coordinate
871 pit_type LyXText::getPitNearY(int y) const
873 BOOST_ASSERT(!paragraphs().empty());
874 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
875 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
877 << BOOST_CURRENT_FUNCTION
878 << ": y: " << y << " cache size: " << cc.size()
881 // look for highest numbered paragraph with y coordinate less than given y
884 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
885 CoordCache::InnerParPosCache::const_iterator et = cc.end();
886 for (; it != et; ++it) {
888 << BOOST_CURRENT_FUNCTION
889 << " examining: pit: " << it->first
890 << " y: " << it->second.y_
893 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
900 << BOOST_CURRENT_FUNCTION
901 << ": found best y: " << yy << " for pit: " << pit
908 Row const & LyXText::getRowNearY(int y, pit_type pit) const
910 Paragraph const & par = pars_[pit];
911 int yy = theCoords.get(this, pit).y_ - par.ascent();
912 BOOST_ASSERT(!par.rows().empty());
913 RowList::const_iterator rit = par.rows().begin();
914 RowList::const_iterator const rlast = boost::prior(par.rows().end());
915 for (; rit != rlast; yy += rit->height(), ++rit)
916 if (yy + rit->height() > y)
922 // x,y are absolute screen coordinates
923 // sets cursor recursively descending into nested editable insets
924 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
926 pit_type pit = getPitNearY(y);
927 BOOST_ASSERT(pit != -1);
928 Row const & row = getRowNearY(y, pit);
931 int xx = x; // is modified by getColumnNearX
932 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
938 // try to descend into nested insets
939 InsetBase * inset = checkInsetHit(x, y);
940 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
942 // Either we deconst editXY or better we move current_font
943 // and real_current_font to LCursor
948 // This should be just before or just behind the
949 // cursor position set above.
950 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
951 || inset == pars_[pit].getInset(pos));
952 // Make sure the cursor points to the position before
954 if (inset == pars_[pit].getInset(pos - 1))
956 inset = inset->editXY(cur, x, y);
957 if (cur.top().text() == this)
963 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
967 if (cur.pos() == cur.lastpos())
969 InsetBase * inset = cur.nextInset();
970 if (!isHighlyEditableInset(inset))
972 inset->edit(cur, front);
977 bool LyXText::cursorLeft(LCursor & cur)
979 if (!cur.boundary() && cur.pos() > 0 &&
980 cur.textRow().pos() == cur.pos() &&
981 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
982 !cur.paragraph().isNewline(cur.pos()-1)) {
983 return setCursor(cur, cur.pit(), cur.pos(), true, true);
985 if (cur.pos() != 0) {
986 bool boundary = cur.boundary();
987 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
988 if (!checkAndActivateInset(cur, false)) {
989 if (false && !boundary &&
990 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
992 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
997 if (cur.pit() != 0) {
998 // Steps into the paragraph above
999 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1005 bool LyXText::cursorRight(LCursor & cur)
1007 if (cur.pos() != cur.lastpos()) {
1009 return setCursor(cur, cur.pit(), cur.pos(),
1012 bool updateNeeded = false;
1013 if (!checkAndActivateInset(cur, true)) {
1014 if (cur.textRow().endpos() == cur.pos() + 1 &&
1015 cur.textRow().endpos() != cur.lastpos() &&
1016 !cur.paragraph().isLineSeparator(cur.pos()) &&
1017 !cur.paragraph().isNewline(cur.pos())) {
1020 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1021 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1023 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1025 return updateNeeded;
1028 if (cur.pit() != cur.lastpit())
1029 return setCursor(cur, cur.pit() + 1, 0);
1034 bool LyXText::cursorUp(LCursor & cur)
1036 Paragraph const & par = cur.paragraph();
1038 int const x = cur.targetX();
1040 if (cur.pos() && cur.boundary())
1041 row = par.pos2row(cur.pos()-1);
1043 row = par.pos2row(cur.pos());
1045 if (!cur.selection()) {
1046 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1048 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1049 cur.clearSelection();
1051 // This happens when you move out of an inset.
1052 // And to give the DEPM the possibility of doing
1053 // something we must provide it with two different
1055 LCursor dummy = cur;
1059 return deleteEmptyParagraphMechanism(dummy, old);
1062 bool updateNeeded = false;
1065 updateNeeded |= setCursor(cur, cur.pit(),
1066 x2pos(cur.pit(), row - 1, x));
1067 } else if (cur.pit() > 0) {
1069 //cannot use 'par' now
1070 updateNeeded |= setCursor(cur, cur.pit(),
1071 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1076 return updateNeeded;
1080 bool LyXText::cursorDown(LCursor & cur)
1082 Paragraph const & par = cur.paragraph();
1084 int const x = cur.targetX();
1086 if (cur.pos() && cur.boundary())
1087 row = par.pos2row(cur.pos()-1);
1089 row = par.pos2row(cur.pos());
1091 if (!cur.selection()) {
1092 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1094 editXY(cur, x, y + par.rows()[row].descent() + 1);
1095 cur.clearSelection();
1097 // This happens when you move out of an inset.
1098 // And to give the DEPM the possibility of doing
1099 // something we must provide it with two different
1101 LCursor dummy = cur;
1105 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1107 // Make sure that cur gets back whatever happened to dummy(Lgb)
1114 bool updateNeeded = false;
1116 if (row + 1 < int(par.rows().size())) {
1117 updateNeeded |= setCursor(cur, cur.pit(),
1118 x2pos(cur.pit(), row + 1, x));
1119 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1121 updateNeeded |= setCursor(cur, cur.pit(),
1122 x2pos(cur.pit(), 0, x));
1127 return updateNeeded;
1131 bool LyXText::cursorUpParagraph(LCursor & cur)
1133 bool updated = false;
1135 updated = setCursor(cur, cur.pit(), 0);
1136 else if (cur.pit() != 0)
1137 updated = setCursor(cur, cur.pit() - 1, 0);
1142 bool LyXText::cursorDownParagraph(LCursor & cur)
1144 bool updated = false;
1145 if (cur.pit() != cur.lastpit())
1146 updated = setCursor(cur, cur.pit() + 1, 0);
1148 updated = setCursor(cur, cur.pit(), cur.lastpos());
1153 // fix the cursor `cur' after a characters has been deleted at `where'
1154 // position. Called by deleteEmptyParagraphMechanism
1155 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1157 // Do nothing if cursor is not in the paragraph where the
1158 // deletion occured,
1159 if (cur.pit() != where.pit())
1162 // If cursor position is after the deletion place update it
1163 if (cur.pos() > where.pos())
1166 // Check also if we don't want to set the cursor on a spot behind the
1167 // pagragraph because we erased the last character.
1168 if (cur.pos() > cur.lastpos())
1169 cur.pos() = cur.lastpos();
1173 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1175 // Would be wrong to delete anything if we have a selection.
1176 if (cur.selection())
1179 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1180 // old should point to us
1181 BOOST_ASSERT(old.text() == this);
1183 Paragraph & oldpar = old.paragraph();
1185 // We allow all kinds of "mumbo-jumbo" when freespacing.
1186 if (oldpar.isFreeSpacing())
1189 /* Ok I'll put some comments here about what is missing.
1190 There are still some small problems that can lead to
1191 double spaces stored in the document file or space at
1192 the beginning of paragraphs(). This happens if you have
1193 the cursor between to spaces and then save. Or if you
1194 cut and paste and the selection have a space at the
1195 beginning and then save right after the paste. (Lgb)
1198 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1199 // delete the LineSeparator.
1202 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1203 // delete the LineSeparator.
1206 bool const same_inset = &old.inset() == &cur.inset();
1207 bool const same_par = same_inset && old.pit() == cur.pit();
1208 bool const same_par_pos = same_par && old.pos() == cur.pos();
1210 // If the chars around the old cursor were spaces, delete one of them.
1211 if (!same_par_pos) {
1212 // Only if the cursor has really moved.
1214 && old.pos() < oldpar.size()
1215 && oldpar.isLineSeparator(old.pos())
1216 && oldpar.isLineSeparator(old.pos() - 1)
1217 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1218 // We need to set the text to Change::INSERTED to
1219 // get it erased properly
1220 oldpar.setChange(old.pos() -1, Change::INSERTED);
1221 oldpar.erase(old.pos() - 1);
1222 #ifdef WITH_WARNINGS
1223 #warning This will not work anymore when we have multiple views of the same buffer
1224 // In this case, we will have to correct also the cursors held by
1225 // other bufferviews. It will probably be easier to do that in a more
1226 // automated way in CursorSlice code. (JMarc 26/09/2001)
1228 // correct all cursor parts
1230 fixCursorAfterDelete(cur.top(), old.top());
1237 // only do our magic if we changed paragraph
1241 // don't delete anything if this is the ONLY paragraph!
1242 if (old.lastpit() == 0)
1245 // Do not delete empty paragraphs with keepempty set.
1246 if (oldpar.allowEmpty())
1249 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1251 recordUndo(old, Undo::ATOMIC,
1252 old.pit(), min(old.pit() + 1, old.lastpit()));
1253 ParagraphList & plist = old.text()->paragraphs();
1254 plist.erase(plist.begin() + old.pit());
1256 // see #warning above
1257 if (cur.depth() >= old.depth()) {
1258 CursorSlice & curslice = cur[old.depth() - 1];
1259 if (&curslice.inset() == &old.inset()
1260 && curslice.pit() > old.pit()) {
1262 // since a paragraph has been deleted, all the
1263 // insets after `old' have been copied and
1264 // their address has changed. Therefore we
1265 // need to `regenerate' cur. (JMarc)
1266 cur.updateInsets(&(cur.bottom().inset()));
1270 updateCounters(old.buffer());
1274 if (oldpar.stripLeadingSpaces())
1281 void LyXText::recUndo(pit_type first, pit_type last) const
1283 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1287 void LyXText::recUndo(pit_type par) const
1289 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1293 int defaultRowHeight()
1295 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);