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"
62 using std::ostringstream;
66 LyXText::LyXText(BufferView * bv)
67 : maxwidth_(bv ? bv->workWidth() : 100),
68 background_color_(LColor::background),
74 void LyXText::init(BufferView * bv)
78 maxwidth_ = bv->workWidth();
83 pit_type const end = paragraphs().size();
84 for (pit_type pit = 0; pit != end; ++pit)
85 pars_[pit].rows().clear();
87 current_font = getFont(pars_[0], 0);
88 updateCounters(*bv->buffer());
92 bool LyXText::isMainText() const
94 return &bv()->buffer()->text() == this;
98 //takes screen x,y coordinates
99 InsetBase * LyXText::checkInsetHit(int x, int y) const
101 pit_type pit = getPitNearY(y);
102 BOOST_ASSERT(pit != -1);
104 Paragraph const & par = pars_[pit];
106 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
107 lyxerr << " pit: " << pit << endl;
108 InsetList::const_iterator iit = par.insetlist.begin();
109 InsetList::const_iterator iend = par.insetlist.end();
110 for (; iit != iend; ++iit) {
111 InsetBase * inset = iit->inset;
113 lyxerr << "examining inset " << inset << endl;
114 if (theCoords.getInsets().has(inset))
116 << " xo: " << inset->xo() << "..."
117 << inset->xo() + inset->width()
118 << " yo: " << inset->yo() - inset->ascent()
120 << inset->yo() + inset->descent() << endl;
122 lyxerr << " inset has no cached position" << endl;
124 if (inset->covers(x, y)) {
125 lyxerr << "Hit inset: " << inset << endl;
129 lyxerr << "No inset hit. " << endl;
135 // Gets the fully instantiated font at a given position in a paragraph
136 // Basically the same routine as Paragraph::getFont() in paragraph.C.
137 // The difference is that this one is used for displaying, and thus we
138 // are allowed to make cosmetic improvements. For instance make footnotes
140 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
142 BOOST_ASSERT(pos >= 0);
144 LyXLayout_ptr const & layout = par.layout();
148 BufferParams const & params = bv()->buffer()->params();
149 pos_type const body_pos = par.beginOfBody();
151 // We specialize the 95% common case:
152 if (!par.getDepth()) {
153 LyXFont f = par.getFontSettings(params, pos);
156 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
157 return f.realize(layout->reslabelfont);
159 return f.realize(layout->resfont);
162 // The uncommon case need not be optimized as much
165 layoutfont = layout->labelfont;
167 layoutfont = layout->font;
169 LyXFont font = par.getFontSettings(params, pos);
170 font.realize(layoutfont);
175 // Realize with the fonts of lesser depth.
176 font.realize(defaultfont_);
182 LyXFont LyXText::getLayoutFont(pit_type const pit) const
184 LyXLayout_ptr const & layout = pars_[pit].layout();
186 if (!pars_[pit].getDepth())
187 return layout->resfont;
189 LyXFont font = layout->font;
190 // Realize with the fonts of lesser depth.
191 //font.realize(outerFont(pit, paragraphs()));
192 font.realize(defaultfont_);
198 LyXFont LyXText::getLabelFont(Paragraph const & par) const
200 LyXLayout_ptr const & layout = par.layout();
203 return layout->reslabelfont;
205 LyXFont font = layout->labelfont;
206 // Realize with the fonts of lesser depth.
207 font.realize(defaultfont_);
213 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
216 LyXLayout_ptr const & layout = pars_[pit].layout();
218 // Get concrete layout font to reduce against
221 if (pos < pars_[pit].beginOfBody())
222 layoutfont = layout->labelfont;
224 layoutfont = layout->font;
226 // Realize against environment font information
227 if (pars_[pit].getDepth()) {
229 while (!layoutfont.resolved() &&
230 tp != pit_type(paragraphs().size()) &&
231 pars_[tp].getDepth()) {
232 tp = outerHook(tp, paragraphs());
233 if (tp != pit_type(paragraphs().size()))
234 layoutfont.realize(pars_[tp].layout()->font);
238 layoutfont.realize(defaultfont_);
240 // Now, reduce font against full layout font
241 font.reduce(layoutfont);
243 pars_[pit].setFont(pos, font);
248 // Asger is not sure we want to do this...
249 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
252 LyXLayout_ptr const & layout = par.layout();
253 pos_type const psize = par.size();
256 for (pos_type pos = 0; pos < psize; ++pos) {
257 if (pos < par.beginOfBody())
258 layoutfont = layout->labelfont;
260 layoutfont = layout->font;
262 LyXFont tmpfont = par.getFontSettings(params, pos);
263 tmpfont.reduce(layoutfont);
264 par.setFont(pos, tmpfont);
269 // return past-the-last paragraph influenced by a layout change on pit
270 pit_type LyXText::undoSpan(pit_type pit)
272 pit_type end = paragraphs().size();
273 pit_type nextpit = pit + 1;
276 //because of parindents
277 if (!pars_[pit].getDepth())
278 return boost::next(nextpit);
279 //because of depth constrains
280 for (; nextpit != end; ++pit, ++nextpit) {
281 if (!pars_[pit].getDepth())
288 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
290 BOOST_ASSERT(start != end);
291 pit_type undopit = undoSpan(end - 1);
292 recUndo(start, undopit - 1);
294 BufferParams const & bufparams = bv()->buffer()->params();
295 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
297 for (pit_type pit = start; pit != end; ++pit) {
298 pars_[pit].applyLayout(lyxlayout);
299 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
300 if (lyxlayout->margintype == MARGIN_MANUAL)
301 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
308 // set layout over selection and make a total rebreak of those paragraphs
309 void LyXText::setLayout(LCursor & cur, string const & layout)
311 BOOST_ASSERT(this == cur.text());
312 // special handling of new environment insets
313 BufferView & bv = cur.bv();
314 BufferParams const & params = bv.buffer()->params();
315 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
316 if (lyxlayout->is_environment) {
317 // move everything in a new environment inset
318 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
319 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
320 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
321 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
322 InsetBase * inset = new InsetEnvironment(params, layout);
323 insertInset(cur, inset);
324 //inset->edit(cur, true);
325 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
329 pit_type start = cur.selBegin().pit();
330 pit_type end = cur.selEnd().pit() + 1;
331 setLayout(start, end, layout);
332 updateCounters(cur.buffer());
339 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
340 Paragraph const & par, int max_depth)
342 if (par.layout()->labeltype == LABEL_BIBLIO)
344 int const depth = par.params().depth();
345 if (type == LyXText::INC_DEPTH && depth < max_depth)
347 if (type == LyXText::DEC_DEPTH && depth > 0)
356 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
358 BOOST_ASSERT(this == cur.text());
359 pit_type const beg = cur.selBegin().pit();
360 pit_type const end = cur.selEnd().pit() + 1;
361 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
363 for (pit_type pit = beg; pit != end; ++pit) {
364 if (::changeDepthAllowed(type, pars_[pit], max_depth))
366 max_depth = pars_[pit].getMaxDepthAfter();
372 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
374 BOOST_ASSERT(this == cur.text());
375 pit_type const beg = cur.selBegin().pit();
376 pit_type const end = cur.selEnd().pit() + 1;
377 recordUndoSelection(cur);
378 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
380 for (pit_type pit = beg; pit != end; ++pit) {
381 Paragraph & par = pars_[pit];
382 if (::changeDepthAllowed(type, par, max_depth)) {
383 int const depth = par.params().depth();
384 if (type == INC_DEPTH)
385 par.params().depth(depth + 1);
387 par.params().depth(depth - 1);
389 max_depth = par.getMaxDepthAfter();
391 // this handles the counter labels, and also fixes up
392 // depth values for follow-on (child) paragraphs
393 updateCounters(cur.buffer());
397 // set font over selection
398 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
400 BOOST_ASSERT(this == cur.text());
401 // if there is no selection just set the current_font
402 if (!cur.selection()) {
403 // Determine basis font
405 pit_type pit = cur.pit();
406 if (cur.pos() < pars_[pit].beginOfBody())
407 layoutfont = getLabelFont(pars_[pit]);
409 layoutfont = getLayoutFont(pit);
411 // Update current font
412 real_current_font.update(font,
413 cur.buffer().params().language,
416 // Reduce to implicit settings
417 current_font = real_current_font;
418 current_font.reduce(layoutfont);
419 // And resolve it completely
420 real_current_font.realize(layoutfont);
425 // Ok, we have a selection.
426 recordUndoSelection(cur);
428 DocIterator dit = cur.selectionBegin();
429 DocIterator ditend = cur.selectionEnd();
431 BufferParams const & params = cur.buffer().params();
433 // Don't use forwardChar here as ditend might have
434 // pos() == lastpos() and forwardChar would miss it.
435 // Can't use forwardPos either as this descends into
437 for (; dit != ditend; dit.forwardPosNoDescend()) {
438 if (dit.pos() != dit.lastpos()) {
439 LyXFont f = getFont(dit.paragraph(), dit.pos());
440 f.update(font, params.language, toggleall);
441 setCharFont(dit.pit(), dit.pos(), f);
447 // the cursor set functions have a special mechanism. When they
448 // realize you left an empty paragraph, they will delete it.
450 void LyXText::cursorHome(LCursor & cur)
452 BOOST_ASSERT(this == cur.text());
453 setCursor(cur, cur.pit(), cur.textRow().pos());
457 void LyXText::cursorEnd(LCursor & cur)
459 BOOST_ASSERT(this == cur.text());
460 // if not on the last row of the par, put the cursor before
462 // FIXME: does this final space exist?
463 pos_type const end = cur.textRow().endpos();
464 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
468 void LyXText::cursorTop(LCursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 setCursor(cur, 0, 0);
475 void LyXText::cursorBottom(LCursor & cur)
477 BOOST_ASSERT(this == cur.text());
478 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
482 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
484 BOOST_ASSERT(this == cur.text());
485 // If the mask is completely neutral, tell user
486 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
487 // Could only happen with user style
488 cur.message(_("No font change defined. "
489 "Use Character under the Layout menu to define font change."));
493 // Try implicit word selection
494 // If there is a change in the language the implicit word selection
496 CursorSlice resetCursor = cur.top();
497 bool implicitSelection =
498 font.language() == ignore_language
499 && font.number() == LyXFont::IGNORE
500 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
503 setFont(cur, font, toggleall);
505 // Implicit selections are cleared afterwards
506 // and cursor is set to the original position.
507 if (implicitSelection) {
508 cur.clearSelection();
509 cur.top() = resetCursor;
515 string LyXText::getStringToIndex(LCursor & cur)
517 BOOST_ASSERT(this == cur.text());
518 // Try implicit word selection
519 // If there is a change in the language the implicit word selection
521 CursorSlice const reset_cursor = cur.top();
522 bool const implicitSelection =
523 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
526 if (!cur.selection())
527 cur.message(_("Nothing to index!"));
528 else if (cur.selBegin().pit() != cur.selEnd().pit())
529 cur.message(_("Cannot index more than one paragraph!"));
531 idxstring = cur.selectionAsString(false);
533 // Reset cursors to their original position.
534 cur.top() = reset_cursor;
537 // Clear the implicit selection.
538 if (implicitSelection)
539 cur.clearSelection();
545 void LyXText::setParagraph(LCursor & cur,
546 Spacing const & spacing, LyXAlignment align,
547 string const & labelwidthstring, bool noindent)
549 BOOST_ASSERT(cur.text());
550 // make sure that the depth behind the selection are restored, too
551 pit_type undopit = undoSpan(cur.selEnd().pit());
552 recUndo(cur.selBegin().pit(), undopit - 1);
554 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
556 Paragraph & par = pars_[pit];
557 ParagraphParameters & params = par.params();
558 params.spacing(spacing);
560 // does the layout allow the new alignment?
561 LyXLayout_ptr const & layout = par.layout();
563 if (align == LYX_ALIGN_LAYOUT)
564 align = layout->align;
565 if (align & layout->alignpossible) {
566 if (align == layout->align)
567 params.align(LYX_ALIGN_LAYOUT);
571 par.setLabelWidthString(labelwidthstring);
572 params.noindent(noindent);
577 // this really should just insert the inset and not move the cursor.
578 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
580 BOOST_ASSERT(this == cur.text());
582 cur.paragraph().insertInset(cur.pos(), inset);
586 // needed to insert the selection
587 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
589 pit_type pit = cur.pit();
590 pos_type pos = cur.pos();
593 // only to be sure, should not be neccessary
594 cur.clearSelection();
595 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
599 setCursor(cur, cur.pit(), pos);
604 // turn double CR to single CR, others are converted into one
605 // blank. Then insertStringAsLines is called
606 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
608 string linestr = str;
609 bool newline_inserted = false;
611 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
612 if (linestr[i] == '\n') {
613 if (newline_inserted) {
614 // we know that \r will be ignored by
615 // insertStringAsLines. Of course, it is a dirty
616 // trick, but it works...
617 linestr[i - 1] = '\r';
621 newline_inserted = true;
623 } else if (IsPrintable(linestr[i])) {
624 newline_inserted = false;
627 insertStringAsLines(cur, linestr);
631 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
632 bool setfont, bool boundary)
635 setCursorIntern(cur, par, pos, setfont, boundary);
636 return deleteEmptyParagraphMechanism(cur, old);
640 void LyXText::setCursor(CursorSlice & cur, pit_type par,
641 pos_type pos, bool boundary)
643 BOOST_ASSERT(par != int(paragraphs().size()));
646 cur.boundary() = boundary;
648 // now some strict checking
649 Paragraph & para = getPar(par);
651 // None of these should happen, but we're scaredy-cats
653 lyxerr << "dont like -1" << endl;
657 if (pos > para.size()) {
658 lyxerr << "dont like 1, pos: " << pos
659 << " size: " << para.size()
660 << " par: " << par << endl;
666 void LyXText::setCursorIntern(LCursor & cur,
667 pit_type par, pos_type pos, bool setfont, bool boundary)
669 setCursor(cur.top(), par, pos, boundary);
676 void LyXText::setCurrentFont(LCursor & cur)
678 BOOST_ASSERT(this == cur.text());
679 pos_type pos = cur.pos();
680 Paragraph & par = cur.paragraph();
682 if (cur.boundary() && pos > 0)
686 if (pos == cur.lastpos())
688 else // potentional bug... BUG (Lgb)
689 if (par.isSeparator(pos)) {
690 if (pos > cur.textRow().pos() &&
691 bidi.level(pos) % 2 ==
692 bidi.level(pos - 1) % 2)
694 else if (pos + 1 < cur.lastpos())
699 BufferParams const & bufparams = cur.buffer().params();
700 current_font = par.getFontSettings(bufparams, pos);
701 real_current_font = getFont(par, pos);
703 if (cur.pos() == cur.lastpos()
704 && bidi.isBoundary(cur.buffer(), par, cur.pos())
705 && !cur.boundary()) {
706 Language const * lang = par.getParLanguage(bufparams);
707 current_font.setLanguage(lang);
708 current_font.setNumber(LyXFont::OFF);
709 real_current_font.setLanguage(lang);
710 real_current_font.setNumber(LyXFont::OFF);
715 // x is an absolute screen coord
716 // returns the column near the specified x-coordinate of the row
717 // x is set to the real beginning of this column
718 pos_type LyXText::getColumnNearX(pit_type const pit,
719 Row const & row, int & x, bool & boundary) const
721 int const xo = theCoords.get(this, pit).x_;
723 RowMetrics const r = computeRowMetrics(pit, row);
724 Paragraph const & par = pars_[pit];
726 pos_type vc = row.pos();
727 pos_type end = row.endpos();
729 LyXLayout_ptr const & layout = par.layout();
731 bool left_side = false;
733 pos_type body_pos = par.beginOfBody();
736 double last_tmpx = tmpx;
739 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
742 // check for empty row
748 while (vc < end && tmpx <= x) {
749 c = bidi.vis2log(vc);
751 if (body_pos > 0 && c == body_pos - 1) {
752 tmpx += r.label_hfill +
753 font_metrics::width(layout->labelsep, getLabelFont(par));
754 if (par.isLineSeparator(body_pos - 1))
755 tmpx -= singleWidth(par, body_pos - 1);
758 if (hfillExpansion(par, row, c)) {
759 tmpx += singleWidth(par, c);
763 tmpx += r.label_hfill;
764 } else if (par.isSeparator(c)) {
765 tmpx += singleWidth(par, c);
769 tmpx += singleWidth(par, c);
774 if ((tmpx + last_tmpx) / 2 > x) {
779 BOOST_ASSERT(vc <= end); // This shouldn't happen.
782 // This (rtl_support test) is not needed, but gives
783 // some speedup if rtl_support == false
784 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
786 // If lastrow is false, we don't need to compute
788 bool const rtl = lastrow ? isRTL(par) : false;
790 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
791 (!rtl && !left_side && vc == end && x > tmpx + 5)))
793 else if (vc == row.pos()) {
794 c = bidi.vis2log(vc);
795 if (bidi.level(c) % 2 == 1)
798 c = bidi.vis2log(vc - 1);
799 bool const rtl = (bidi.level(c) % 2 == 1);
800 if (left_side == rtl) {
802 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
806 // The following code is necessary because the cursor position past
807 // the last char in a row is logically equivalent to that before
808 // the first char in the next row. That's why insets causing row
809 // divisions -- Newline and display-style insets -- must be treated
810 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
811 // Newline inset, air gap below:
812 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
813 if (bidi.level(end -1) % 2 == 0)
814 tmpx -= singleWidth(par, end - 1);
816 tmpx += singleWidth(par, end - 1);
819 // Air gap above display inset:
820 if (row.pos() < end && c >= end && end < par.size()
821 && par.isInset(end) && par.getInset(end)->display()) {
824 // Air gap below display inset:
825 if (row.pos() < end && c >= end && par.isInset(end - 1)
826 && par.getInset(end - 1)->display()) {
831 return c - row.pos();
835 // y is screen coordinate
836 pit_type LyXText::getPitNearY(int y) const
838 BOOST_ASSERT(!paragraphs().empty());
839 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
840 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
841 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
842 << cc.size() << endl;
844 // look for highest numbered paragraph with y coordinate less than given y
847 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
848 CoordCache::InnerParPosCache::const_iterator et = cc.end();
849 for (; it != et; ++it) {
850 lyxerr << " examining: pit: " << it->first << " y: "
851 << it->second.y_ << endl;
852 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
858 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
863 Row const & LyXText::getRowNearY(int y, pit_type pit) const
865 Paragraph const & par = pars_[pit];
866 int yy = theCoords.get(this, pit).y_ - par.ascent();
867 BOOST_ASSERT(!par.rows().empty());
868 RowList::const_iterator rit = par.rows().begin();
869 RowList::const_iterator const rlast = boost::prior(par.rows().end());
870 for (; rit != rlast; yy += rit->height(), ++rit)
871 if (yy + rit->height() > y)
877 // x,y are absolute screen coordinates
878 // sets cursor recursively descending into nested editable insets
879 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
881 pit_type pit = getPitNearY(y);
882 BOOST_ASSERT(pit != -1);
883 Row const & row = getRowNearY(y, pit);
886 int xx = x; // is modified by getColumnNearX
887 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
890 cur.boundary() = bound;
893 // try to descend into nested insets
894 InsetBase * inset = checkInsetHit(x, y);
895 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
897 // Either we deconst editXY or better we move current_font
898 // and real_current_font to LCursor
899 const_cast<LyXText *>(this)->setCurrentFont(cur);
903 // This should be just before or just behind the
904 // cursor position set above.
905 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
906 || inset == pars_[pit].getInset(pos));
907 // Make sure the cursor points to the position before
909 if (inset == pars_[pit].getInset(pos - 1))
911 inset = inset->editXY(cur, x, y);
912 if (cur.top().text() == this)
913 const_cast<LyXText *>(this)->setCurrentFont(cur);
918 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
922 if (cur.pos() == cur.lastpos())
924 InsetBase * inset = cur.nextInset();
925 if (!isHighlyEditableInset(inset))
927 inset->edit(cur, front);
932 bool LyXText::cursorLeft(LCursor & cur)
934 if (cur.pos() != 0) {
935 bool boundary = cur.boundary();
936 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
937 if (!checkAndActivateInset(cur, false)) {
938 if (false && !boundary &&
939 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
941 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
946 if (cur.pit() != 0) {
947 // Steps into the paragraph above
948 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
954 bool LyXText::cursorRight(LCursor & cur)
956 if (false && cur.boundary()) {
957 return setCursor(cur, cur.pit(), cur.pos(), true, false);
960 if (cur.pos() != cur.lastpos()) {
961 bool updateNeeded = false;
962 if (!checkAndActivateInset(cur, true)) {
963 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
964 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
966 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
971 if (cur.pit() != cur.lastpit())
972 return setCursor(cur, cur.pit() + 1, 0);
977 bool LyXText::cursorUp(LCursor & cur)
979 Paragraph const & par = cur.paragraph();
980 int const row = par.pos2row(cur.pos());
981 int const x = cur.targetX();
983 if (!cur.selection()) {
984 int const y = bv_funcs::getPos(cur).y_;
986 editXY(cur, x, y - par.rows()[row].ascent() - 1);
988 // This happens when you move out of an inset.
989 // And to give the DEPM the possibility of doing
990 // something we must provide it with two different
996 return deleteEmptyParagraphMechanism(dummy, old);
999 bool updateNeeded = false;
1002 updateNeeded |= setCursor(cur, cur.pit(),
1003 x2pos(cur.pit(), row - 1, x));
1004 } else if (cur.pit() > 0) {
1006 //cannot use 'par' now
1007 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1012 return updateNeeded;
1016 bool LyXText::cursorDown(LCursor & cur)
1018 Paragraph const & par = cur.paragraph();
1019 int const row = par.pos2row(cur.pos());
1020 int const x = cur.targetX();
1022 if (!cur.selection()) {
1023 int const y = bv_funcs::getPos(cur).y_;
1025 editXY(cur, x, y + par.rows()[row].descent() + 1);
1027 // This happens when you move out of an inset.
1028 // And to give the DEPM the possibility of doing
1029 // something we must provide it with two different
1031 LCursor dummy = cur;
1035 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1037 // Make sure that cur gets back whatever happened to dummy(Lgb)
1045 bool updateNeeded = false;
1047 if (row + 1 < int(par.rows().size())) {
1048 updateNeeded |= setCursor(cur, cur.pit(),
1049 x2pos(cur.pit(), row + 1, x));
1050 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1052 updateNeeded |= setCursor(cur, cur.pit(),
1053 x2pos(cur.pit(), 0, x));
1058 return updateNeeded;
1062 bool LyXText::cursorUpParagraph(LCursor & cur)
1064 bool updated = false;
1066 updated = setCursor(cur, cur.pit(), 0);
1067 else if (cur.pit() != 0)
1068 updated = setCursor(cur, cur.pit() - 1, 0);
1073 bool LyXText::cursorDownParagraph(LCursor & cur)
1075 bool updated = false;
1076 if (cur.pit() != cur.lastpit())
1077 updated = setCursor(cur, cur.pit() + 1, 0);
1079 updated = setCursor(cur, cur.pit(), cur.lastpos());
1084 // fix the cursor `cur' after a characters has been deleted at `where'
1085 // position. Called by deleteEmptyParagraphMechanism
1086 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1088 // Do nothing if cursor is not in the paragraph where the
1089 // deletion occured,
1090 if (cur.pit() != where.pit())
1093 // If cursor position is after the deletion place update it
1094 if (cur.pos() > where.pos())
1097 // Check also if we don't want to set the cursor on a spot behind the
1098 // pagragraph because we erased the last character.
1099 if (cur.pos() > cur.lastpos())
1100 cur.pos() = cur.lastpos();
1104 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1106 // Would be wrong to delete anything if we have a selection.
1107 if (cur.selection())
1110 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1111 Paragraph const & oldpar = pars_[old.pit()];
1113 // We allow all kinds of "mumbo-jumbo" when freespacing.
1114 if (oldpar.isFreeSpacing())
1117 /* Ok I'll put some comments here about what is missing.
1118 I have fixed BackSpace (and thus Delete) to not delete
1119 double-spaces automagically. I have also changed Cut,
1120 Copy and Paste to hopefully do some sensible things.
1121 There are still some small problems that can lead to
1122 double spaces stored in the document file or space at
1123 the beginning of paragraphs(). This happens if you have
1124 the cursor between to spaces and then save. Or if you
1125 cut and paste and the selection have a space at the
1126 beginning and then save right after the paste. I am
1127 sure none of these are very hard to fix, but I will
1128 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1129 that I can get some feedback. (Lgb)
1132 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1133 // delete the LineSeparator.
1136 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1137 // delete the LineSeparator.
1140 // If the chars around the old cursor were spaces, delete one of them.
1141 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1143 // Only if the cursor has really moved.
1145 && old.pos() < oldpar.size()
1146 && oldpar.isLineSeparator(old.pos())
1147 && oldpar.isLineSeparator(old.pos() - 1)) {
1148 // We need to set the text to Change::INSERTED to
1149 // get it erased properly
1150 pars_[old.pit()].setChange(old.pos() -1,
1152 pars_[old.pit()].erase(old.pos() - 1);
1153 #ifdef WITH_WARNINGS
1154 #warning This will not work anymore when we have multiple views of the same buffer
1155 // In this case, we will have to correct also the cursors held by
1156 // other bufferviews. It will probably be easier to do that in a more
1157 // automated way in CursorSlice code. (JMarc 26/09/2001)
1159 // correct all cursor parts
1160 fixCursorAfterDelete(cur.top(), old.top());
1161 #ifdef WITH_WARNINGS
1162 #warning DEPM, look here
1164 //fixCursorAfterDelete(cur.anchor(), old.top());
1169 // only do our magic if we changed paragraph
1170 if (old.pit() == cur.pit())
1173 // don't delete anything if this is the ONLY paragraph!
1174 if (pars_.size() == 1)
1177 // Do not delete empty paragraphs with keepempty set.
1178 if (oldpar.allowEmpty())
1181 // record if we have deleted a paragraph
1182 // we can't possibly have deleted a paragraph before this point
1183 bool deleted = false;
1185 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1186 // ok, we will delete something
1189 bool selection_position_was_oldcursor_position =
1190 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1192 // This is a bit of a overkill. We change the old and the cur par
1193 // at max, certainly not everything in between...
1194 recUndo(old.pit(), cur.pit());
1197 pars_.erase(pars_.begin() + old.pit());
1199 // Update cursor par offset if necessary.
1200 // Some 'iterator registration' would be nice that takes care of
1201 // such events. Maybe even signal/slot?
1202 if (cur.pit() > old.pit())
1204 #ifdef WITH_WARNINGS
1205 #warning DEPM, look here
1207 // if (cur.anchor().pit() > old.pit())
1208 // --cur.anchor().pit();
1210 if (selection_position_was_oldcursor_position) {
1211 // correct selection
1219 if (pars_[old.pit()].stripLeadingSpaces())
1226 ParagraphList & LyXText::paragraphs() const
1228 return const_cast<ParagraphList &>(pars_);
1232 void LyXText::recUndo(pit_type first, pit_type last) const
1234 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1238 void LyXText::recUndo(pit_type par) const
1240 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1244 int defaultRowHeight()
1246 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);