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;
67 LyXText::LyXText(BufferView * bv)
68 : maxwidth_(bv ? bv->workWidth() : 100),
69 background_color_(LColor::background),
75 void LyXText::init(BufferView * bv)
79 maxwidth_ = bv->workWidth();
84 pit_type const end = paragraphs().size();
85 for (pit_type pit = 0; pit != end; ++pit)
86 pars_[pit].rows().clear();
88 current_font = getFont(pars_[0], 0);
89 updateCounters(*bv->buffer());
93 bool LyXText::isMainText() const
95 return &bv()->buffer()->text() == this;
99 //takes screen x,y coordinates
100 InsetBase * LyXText::checkInsetHit(int x, int y) const
102 pit_type pit = getPitNearY(y);
103 BOOST_ASSERT(pit != -1);
105 Paragraph const & par = pars_[pit];
108 << BOOST_CURRENT_FUNCTION
113 InsetList::const_iterator iit = par.insetlist.begin();
114 InsetList::const_iterator iend = par.insetlist.end();
115 for (; iit != iend; ++iit) {
116 InsetBase * inset = iit->inset;
119 << BOOST_CURRENT_FUNCTION
120 << ": examining inset " << inset << endl;
122 if (theCoords.getInsets().has(inset))
124 << BOOST_CURRENT_FUNCTION
125 << ": xo: " << inset->xo() << "..."
126 << inset->xo() + inset->width()
127 << " yo: " << inset->yo() - inset->ascent()
129 << inset->yo() + inset->descent()
133 << BOOST_CURRENT_FUNCTION
134 << ": inset has no cached position" << endl;
136 if (inset->covers(x, y)) {
138 << BOOST_CURRENT_FUNCTION
139 << ": Hit inset: " << inset << endl;
144 << BOOST_CURRENT_FUNCTION
145 << ": No inset hit. " << endl;
151 // Gets the fully instantiated font at a given position in a paragraph
152 // Basically the same routine as Paragraph::getFont() in paragraph.C.
153 // The difference is that this one is used for displaying, and thus we
154 // are allowed to make cosmetic improvements. For instance make footnotes
156 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
158 BOOST_ASSERT(pos >= 0);
160 LyXLayout_ptr const & layout = par.layout();
164 BufferParams const & params = bv()->buffer()->params();
165 pos_type const body_pos = par.beginOfBody();
167 // We specialize the 95% common case:
168 if (!par.getDepth()) {
169 LyXFont f = par.getFontSettings(params, pos);
172 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
173 return f.realize(layout->reslabelfont);
175 return f.realize(layout->resfont);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 LyXFont font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
189 applyOuterFont(font);
191 // Realize with the fonts of lesser depth.
192 font.realize(defaultfont_);
197 // There are currently two font mechanisms in LyX:
198 // 1. The font attributes in a lyxtext, and
199 // 2. The inset-specific font properties, defined in an inset's
200 // metrics() and draw() methods and handed down the inset chain through
201 // the pi/mi parameters, and stored locally in a lyxtext in font_.
202 // This is where the two are integrated in the final fully realized
204 void LyXText::applyOuterFont(LyXFont & font) const {
206 lf.reduce(defaultfont_);
208 lf.setLanguage(font.language());
213 LyXFont LyXText::getLayoutFont(pit_type const pit) const
215 LyXLayout_ptr const & layout = pars_[pit].layout();
217 if (!pars_[pit].getDepth())
218 return layout->resfont;
220 LyXFont font = layout->font;
221 // Realize with the fonts of lesser depth.
222 //font.realize(outerFont(pit, paragraphs()));
223 font.realize(defaultfont_);
229 LyXFont LyXText::getLabelFont(Paragraph const & par) const
231 LyXLayout_ptr const & layout = par.layout();
234 return layout->reslabelfont;
236 LyXFont font = layout->labelfont;
237 // Realize with the fonts of lesser depth.
238 font.realize(defaultfont_);
244 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
247 LyXLayout_ptr const & layout = pars_[pit].layout();
249 // Get concrete layout font to reduce against
252 if (pos < pars_[pit].beginOfBody())
253 layoutfont = layout->labelfont;
255 layoutfont = layout->font;
257 // Realize against environment font information
258 if (pars_[pit].getDepth()) {
260 while (!layoutfont.resolved() &&
261 tp != pit_type(paragraphs().size()) &&
262 pars_[tp].getDepth()) {
263 tp = outerHook(tp, paragraphs());
264 if (tp != pit_type(paragraphs().size()))
265 layoutfont.realize(pars_[tp].layout()->font);
269 layoutfont.realize(defaultfont_);
271 // Now, reduce font against full layout font
272 font.reduce(layoutfont);
274 pars_[pit].setFont(pos, font);
279 // Asger is not sure we want to do this...
280 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
283 LyXLayout_ptr const & layout = par.layout();
284 pos_type const psize = par.size();
287 for (pos_type pos = 0; pos < psize; ++pos) {
288 if (pos < par.beginOfBody())
289 layoutfont = layout->labelfont;
291 layoutfont = layout->font;
293 LyXFont tmpfont = par.getFontSettings(params, pos);
294 tmpfont.reduce(layoutfont);
295 par.setFont(pos, tmpfont);
300 // return past-the-last paragraph influenced by a layout change on pit
301 pit_type LyXText::undoSpan(pit_type pit)
303 pit_type end = paragraphs().size();
304 pit_type nextpit = pit + 1;
307 //because of parindents
308 if (!pars_[pit].getDepth())
309 return boost::next(nextpit);
310 //because of depth constrains
311 for (; nextpit != end; ++pit, ++nextpit) {
312 if (!pars_[pit].getDepth())
319 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
321 BOOST_ASSERT(start != end);
322 pit_type undopit = undoSpan(end - 1);
323 recUndo(start, undopit - 1);
325 BufferParams const & bufparams = bv()->buffer()->params();
326 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
328 for (pit_type pit = start; pit != end; ++pit) {
329 pars_[pit].applyLayout(lyxlayout);
330 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
331 if (lyxlayout->margintype == MARGIN_MANUAL)
332 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
339 // set layout over selection and make a total rebreak of those paragraphs
340 void LyXText::setLayout(LCursor & cur, string const & layout)
342 BOOST_ASSERT(this == cur.text());
343 // special handling of new environment insets
344 BufferView & bv = cur.bv();
345 BufferParams const & params = bv.buffer()->params();
346 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
347 if (lyxlayout->is_environment) {
348 // move everything in a new environment inset
349 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
350 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
351 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
352 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
353 InsetBase * inset = new InsetEnvironment(params, layout);
354 insertInset(cur, inset);
355 //inset->edit(cur, true);
356 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
360 pit_type start = cur.selBegin().pit();
361 pit_type end = cur.selEnd().pit() + 1;
362 setLayout(start, end, layout);
363 updateCounters(cur.buffer());
370 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
371 Paragraph const & par, int max_depth)
373 if (par.layout()->labeltype == LABEL_BIBLIO)
375 int const depth = par.params().depth();
376 if (type == LyXText::INC_DEPTH && depth < max_depth)
378 if (type == LyXText::DEC_DEPTH && depth > 0)
387 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
389 BOOST_ASSERT(this == cur.text());
390 pit_type const beg = cur.selBegin().pit();
391 pit_type const end = cur.selEnd().pit() + 1;
392 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
394 for (pit_type pit = beg; pit != end; ++pit) {
395 if (::changeDepthAllowed(type, pars_[pit], max_depth))
397 max_depth = pars_[pit].getMaxDepthAfter();
403 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
405 BOOST_ASSERT(this == cur.text());
406 pit_type const beg = cur.selBegin().pit();
407 pit_type const end = cur.selEnd().pit() + 1;
408 recordUndoSelection(cur);
409 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
411 for (pit_type pit = beg; pit != end; ++pit) {
412 Paragraph & par = pars_[pit];
413 if (::changeDepthAllowed(type, par, max_depth)) {
414 int const depth = par.params().depth();
415 if (type == INC_DEPTH)
416 par.params().depth(depth + 1);
418 par.params().depth(depth - 1);
420 max_depth = par.getMaxDepthAfter();
422 // this handles the counter labels, and also fixes up
423 // depth values for follow-on (child) paragraphs
424 updateCounters(cur.buffer());
428 // set font over selection
429 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
431 BOOST_ASSERT(this == cur.text());
432 // if there is no selection just set the current_font
433 if (!cur.selection()) {
434 // Determine basis font
436 pit_type pit = cur.pit();
437 if (cur.pos() < pars_[pit].beginOfBody())
438 layoutfont = getLabelFont(pars_[pit]);
440 layoutfont = getLayoutFont(pit);
442 // Update current font
443 real_current_font.update(font,
444 cur.buffer().params().language,
447 // Reduce to implicit settings
448 current_font = real_current_font;
449 current_font.reduce(layoutfont);
450 // And resolve it completely
451 real_current_font.realize(layoutfont);
456 // Ok, we have a selection.
457 recordUndoSelection(cur);
459 DocIterator dit = cur.selectionBegin();
460 DocIterator ditend = cur.selectionEnd();
462 BufferParams const & params = cur.buffer().params();
464 // Don't use forwardChar here as ditend might have
465 // pos() == lastpos() and forwardChar would miss it.
466 // Can't use forwardPos either as this descends into
468 for (; dit != ditend; dit.forwardPosNoDescend()) {
469 if (dit.pos() != dit.lastpos()) {
470 LyXFont f = getFont(dit.paragraph(), dit.pos());
471 f.update(font, params.language, toggleall);
472 setCharFont(dit.pit(), dit.pos(), f);
478 // the cursor set functions have a special mechanism. When they
479 // realize you left an empty paragraph, they will delete it.
481 void LyXText::cursorHome(LCursor & cur)
483 BOOST_ASSERT(this == cur.text());
484 setCursor(cur, cur.pit(), cur.textRow().pos());
488 void LyXText::cursorEnd(LCursor & cur)
490 BOOST_ASSERT(this == cur.text());
491 // if not on the last row of the par, put the cursor before
493 // FIXME: does this final space exist?
494 pos_type const end = cur.textRow().endpos();
495 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
499 void LyXText::cursorTop(LCursor & cur)
501 BOOST_ASSERT(this == cur.text());
502 setCursor(cur, 0, 0);
506 void LyXText::cursorBottom(LCursor & cur)
508 BOOST_ASSERT(this == cur.text());
509 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
513 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
515 BOOST_ASSERT(this == cur.text());
516 // If the mask is completely neutral, tell user
517 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
518 // Could only happen with user style
519 cur.message(_("No font change defined. "
520 "Use Character under the Layout menu to define font change."));
524 // Try implicit word selection
525 // If there is a change in the language the implicit word selection
527 CursorSlice resetCursor = cur.top();
528 bool implicitSelection =
529 font.language() == ignore_language
530 && font.number() == LyXFont::IGNORE
531 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
534 setFont(cur, font, toggleall);
536 // Implicit selections are cleared afterwards
537 // and cursor is set to the original position.
538 if (implicitSelection) {
539 cur.clearSelection();
540 cur.top() = resetCursor;
546 string LyXText::getStringToIndex(LCursor const & cur)
548 BOOST_ASSERT(this == cur.text());
551 if (cur.selection()) {
552 idxstring = cur.selectionAsString(false);
554 // Try implicit word selection. If there is a change
555 // in the language the implicit word selection is
557 LCursor tmpcur = cur;
558 selectWord(tmpcur, lyx::PREVIOUS_WORD);
560 if (!tmpcur.selection())
561 cur.message(_("Nothing to index!"));
562 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
563 cur.message(_("Cannot index more than one paragraph!"));
565 idxstring = tmpcur.selectionAsString(false);
572 void LyXText::setParagraph(LCursor & cur,
573 Spacing const & spacing, LyXAlignment align,
574 string const & labelwidthstring, bool noindent)
576 BOOST_ASSERT(cur.text());
577 // make sure that the depth behind the selection are restored, too
578 pit_type undopit = undoSpan(cur.selEnd().pit());
579 recUndo(cur.selBegin().pit(), undopit - 1);
581 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
583 Paragraph & par = pars_[pit];
584 ParagraphParameters & params = par.params();
585 params.spacing(spacing);
587 // does the layout allow the new alignment?
588 LyXLayout_ptr const & layout = par.layout();
590 if (align == LYX_ALIGN_LAYOUT)
591 align = layout->align;
592 if (align & layout->alignpossible) {
593 if (align == layout->align)
594 params.align(LYX_ALIGN_LAYOUT);
598 par.setLabelWidthString(labelwidthstring);
599 params.noindent(noindent);
604 // this really should just insert the inset and not move the cursor.
605 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
607 BOOST_ASSERT(this == cur.text());
609 cur.paragraph().insertInset(cur.pos(), inset);
613 // needed to insert the selection
614 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
616 pit_type pit = cur.pit();
617 pos_type pos = cur.pos();
620 // only to be sure, should not be neccessary
621 cur.clearSelection();
622 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
626 setCursor(cur, cur.pit(), pos);
631 // turn double CR to single CR, others are converted into one
632 // blank. Then insertStringAsLines is called
633 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
635 string linestr = str;
636 bool newline_inserted = false;
638 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
639 if (linestr[i] == '\n') {
640 if (newline_inserted) {
641 // we know that \r will be ignored by
642 // insertStringAsLines. Of course, it is a dirty
643 // trick, but it works...
644 linestr[i - 1] = '\r';
648 newline_inserted = true;
650 } else if (IsPrintable(linestr[i])) {
651 newline_inserted = false;
654 insertStringAsLines(cur, linestr);
658 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
659 bool setfont, bool boundary)
662 setCursorIntern(cur, par, pos, setfont, boundary);
663 return deleteEmptyParagraphMechanism(cur, old);
667 void LyXText::setCursor(CursorSlice & cur, pit_type par,
668 pos_type pos, bool boundary)
670 BOOST_ASSERT(par != int(paragraphs().size()));
674 // now some strict checking
675 Paragraph & para = getPar(par);
677 // None of these should happen, but we're scaredy-cats
679 lyxerr << "dont like -1" << endl;
683 if (pos > para.size()) {
684 lyxerr << "dont like 1, pos: " << pos
685 << " size: " << para.size()
686 << " par: " << par << endl;
692 void LyXText::setCursorIntern(LCursor & cur,
693 pit_type par, pos_type pos, bool setfont, bool boundary)
695 cur.boundary(boundary);
696 setCursor(cur.top(), par, pos, boundary);
703 void LyXText::setCurrentFont(LCursor & cur)
705 BOOST_ASSERT(this == cur.text());
706 pos_type pos = cur.pos();
707 Paragraph & par = cur.paragraph();
709 if (cur.boundary() && pos > 0)
713 if (pos == cur.lastpos())
715 else // potentional bug... BUG (Lgb)
716 if (par.isSeparator(pos)) {
717 if (pos > cur.textRow().pos() &&
718 bidi.level(pos) % 2 ==
719 bidi.level(pos - 1) % 2)
721 else if (pos + 1 < cur.lastpos())
726 BufferParams const & bufparams = cur.buffer().params();
727 current_font = par.getFontSettings(bufparams, pos);
728 real_current_font = getFont(par, pos);
730 if (cur.pos() == cur.lastpos()
731 && bidi.isBoundary(cur.buffer(), par, cur.pos())
732 && !cur.boundary()) {
733 Language const * lang = par.getParLanguage(bufparams);
734 current_font.setLanguage(lang);
735 current_font.setNumber(LyXFont::OFF);
736 real_current_font.setLanguage(lang);
737 real_current_font.setNumber(LyXFont::OFF);
742 // x is an absolute screen coord
743 // returns the column near the specified x-coordinate of the row
744 // x is set to the real beginning of this column
745 pos_type LyXText::getColumnNearX(pit_type const pit,
746 Row const & row, int & x, bool & boundary) const
748 int const xo = theCoords.get(this, pit).x_;
750 RowMetrics const r = computeRowMetrics(pit, row);
751 Paragraph const & par = pars_[pit];
753 pos_type vc = row.pos();
754 pos_type end = row.endpos();
756 LyXLayout_ptr const & layout = par.layout();
758 bool left_side = false;
760 pos_type body_pos = par.beginOfBody();
763 double last_tmpx = tmpx;
766 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
769 // check for empty row
775 while (vc < end && tmpx <= x) {
776 c = bidi.vis2log(vc);
778 if (body_pos > 0 && c == body_pos - 1) {
779 tmpx += r.label_hfill +
780 font_metrics::width(layout->labelsep, getLabelFont(par));
781 if (par.isLineSeparator(body_pos - 1))
782 tmpx -= singleWidth(par, body_pos - 1);
785 if (hfillExpansion(par, row, c)) {
786 tmpx += singleWidth(par, c);
790 tmpx += r.label_hfill;
791 } else if (par.isSeparator(c)) {
792 tmpx += singleWidth(par, c);
796 tmpx += singleWidth(par, c);
801 if ((tmpx + last_tmpx) / 2 > x) {
806 BOOST_ASSERT(vc <= end); // This shouldn't happen.
809 // This (rtl_support test) is not needed, but gives
810 // some speedup if rtl_support == false
811 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
813 // If lastrow is false, we don't need to compute
815 bool const rtl = lastrow ? isRTL(par) : false;
817 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
818 (!rtl && !left_side && vc == end && x > tmpx + 5)))
820 else if (vc == row.pos()) {
821 c = bidi.vis2log(vc);
822 if (bidi.level(c) % 2 == 1)
825 c = bidi.vis2log(vc - 1);
826 bool const rtl = (bidi.level(c) % 2 == 1);
827 if (left_side == rtl) {
829 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
833 // The following code is necessary because the cursor position past
834 // the last char in a row is logically equivalent to that before
835 // the first char in the next row. That's why insets causing row
836 // divisions -- Newline and display-style insets -- must be treated
837 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
838 // Newline inset, air gap below:
839 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
840 if (bidi.level(end -1) % 2 == 0)
841 tmpx -= singleWidth(par, end - 1);
843 tmpx += singleWidth(par, end - 1);
847 // Air gap above display inset:
848 if (row.pos() < end && c >= end && end < par.size()
849 && par.isInset(end) && par.getInset(end)->display()) {
852 // Air gap below display inset:
853 if (row.pos() < end && c >= end && par.isInset(end - 1)
854 && par.getInset(end - 1)->display()) {
859 int const col = c - row.pos();
861 if (end == par.size())
864 if (!col && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
869 return min(col, end - 1 - row.pos());
873 // y is screen coordinate
874 pit_type LyXText::getPitNearY(int y) const
876 BOOST_ASSERT(!paragraphs().empty());
877 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
878 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
880 << BOOST_CURRENT_FUNCTION
881 << ": y: " << y << " cache size: " << cc.size()
884 // look for highest numbered paragraph with y coordinate less than given y
887 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
888 CoordCache::InnerParPosCache::const_iterator et = cc.end();
889 for (; it != et; ++it) {
891 << BOOST_CURRENT_FUNCTION
892 << " examining: pit: " << it->first
893 << " y: " << it->second.y_
896 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
903 << BOOST_CURRENT_FUNCTION
904 << ": found best y: " << yy << " for pit: " << pit
911 Row const & LyXText::getRowNearY(int y, pit_type pit) const
913 Paragraph const & par = pars_[pit];
914 int yy = theCoords.get(this, pit).y_ - par.ascent();
915 BOOST_ASSERT(!par.rows().empty());
916 RowList::const_iterator rit = par.rows().begin();
917 RowList::const_iterator const rlast = boost::prior(par.rows().end());
918 for (; rit != rlast; yy += rit->height(), ++rit)
919 if (yy + rit->height() > y)
925 // x,y are absolute screen coordinates
926 // sets cursor recursively descending into nested editable insets
927 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
929 pit_type pit = getPitNearY(y);
930 BOOST_ASSERT(pit != -1);
931 Row const & row = getRowNearY(y, pit);
934 int xx = x; // is modified by getColumnNearX
935 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
941 // try to descend into nested insets
942 InsetBase * inset = checkInsetHit(x, y);
943 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
945 // Either we deconst editXY or better we move current_font
946 // and real_current_font to LCursor
951 // This should be just before or just behind the
952 // cursor position set above.
953 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
954 || inset == pars_[pit].getInset(pos));
955 // Make sure the cursor points to the position before
957 if (inset == pars_[pit].getInset(pos - 1))
959 inset = inset->editXY(cur, x, y);
960 if (cur.top().text() == this)
966 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
970 if (cur.pos() == cur.lastpos())
972 InsetBase * inset = cur.nextInset();
973 if (!isHighlyEditableInset(inset))
975 inset->edit(cur, front);
980 bool LyXText::cursorLeft(LCursor & cur)
982 if (cur.pos() != 0) {
983 bool boundary = cur.boundary();
984 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
985 if (!checkAndActivateInset(cur, false)) {
986 if (false && !boundary &&
987 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
989 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
994 if (cur.pit() != 0) {
995 // Steps into the paragraph above
996 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1002 bool LyXText::cursorRight(LCursor & cur)
1004 if (cur.boundary()) {
1005 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1008 if (cur.pos() != cur.lastpos()) {
1009 bool updateNeeded = false;
1010 if (!checkAndActivateInset(cur, true)) {
1011 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1012 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1014 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1016 return updateNeeded;
1019 if (cur.pit() != cur.lastpit())
1020 return setCursor(cur, cur.pit() + 1, 0);
1025 bool LyXText::cursorUp(LCursor & cur)
1027 Paragraph const & par = cur.paragraph();
1029 int const x = cur.targetX();
1031 if (cur.pos() && cur.boundary())
1032 row = par.pos2row(cur.pos()-1);
1034 row = par.pos2row(cur.pos());
1036 if (!cur.selection()) {
1037 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1039 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1041 // This happens when you move out of an inset.
1042 // And to give the DEPM the possibility of doing
1043 // something we must provide it with two different
1045 LCursor dummy = cur;
1049 return deleteEmptyParagraphMechanism(dummy, old);
1052 bool updateNeeded = false;
1055 updateNeeded |= setCursor(cur, cur.pit(),
1056 x2pos(cur.pit(), row - 1, x));
1057 } else if (cur.pit() > 0) {
1059 //cannot use 'par' now
1060 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1065 return updateNeeded;
1069 bool LyXText::cursorDown(LCursor & cur)
1071 Paragraph const & par = cur.paragraph();
1073 int const x = cur.targetX();
1075 if (cur.pos() && cur.boundary())
1076 row = par.pos2row(cur.pos()-1);
1078 row = par.pos2row(cur.pos());
1080 if (!cur.selection()) {
1081 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1083 editXY(cur, x, y + par.rows()[row].descent() + 1);
1085 // This happens when you move out of an inset.
1086 // And to give the DEPM the possibility of doing
1087 // something we must provide it with two different
1089 LCursor dummy = cur;
1093 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1095 // Make sure that cur gets back whatever happened to dummy(Lgb)
1103 bool updateNeeded = false;
1105 if (row + 1 < int(par.rows().size())) {
1106 updateNeeded |= setCursor(cur, cur.pit(),
1107 x2pos(cur.pit(), row + 1, x));
1108 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1110 updateNeeded |= setCursor(cur, cur.pit(),
1111 x2pos(cur.pit(), 0, x));
1116 return updateNeeded;
1120 bool LyXText::cursorUpParagraph(LCursor & cur)
1122 bool updated = false;
1124 updated = setCursor(cur, cur.pit(), 0);
1125 else if (cur.pit() != 0)
1126 updated = setCursor(cur, cur.pit() - 1, 0);
1131 bool LyXText::cursorDownParagraph(LCursor & cur)
1133 bool updated = false;
1134 if (cur.pit() != cur.lastpit())
1135 updated = setCursor(cur, cur.pit() + 1, 0);
1137 updated = setCursor(cur, cur.pit(), cur.lastpos());
1142 // fix the cursor `cur' after a characters has been deleted at `where'
1143 // position. Called by deleteEmptyParagraphMechanism
1144 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1146 // Do nothing if cursor is not in the paragraph where the
1147 // deletion occured,
1148 if (cur.pit() != where.pit())
1151 // If cursor position is after the deletion place update it
1152 if (cur.pos() > where.pos())
1155 // Check also if we don't want to set the cursor on a spot behind the
1156 // pagragraph because we erased the last character.
1157 if (cur.pos() > cur.lastpos())
1158 cur.pos() = cur.lastpos();
1162 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1164 // Would be wrong to delete anything if we have a selection.
1165 if (cur.selection())
1168 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1169 Paragraph const & oldpar = pars_[old.pit()];
1171 // We allow all kinds of "mumbo-jumbo" when freespacing.
1172 if (oldpar.isFreeSpacing())
1175 /* Ok I'll put some comments here about what is missing.
1176 I have fixed BackSpace (and thus Delete) to not delete
1177 double-spaces automagically. I have also changed Cut,
1178 Copy and Paste to hopefully do some sensible things.
1179 There are still some small problems that can lead to
1180 double spaces stored in the document file or space at
1181 the beginning of paragraphs(). This happens if you have
1182 the cursor between to spaces and then save. Or if you
1183 cut and paste and the selection have a space at the
1184 beginning and then save right after the paste. I am
1185 sure none of these are very hard to fix, but I will
1186 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1187 that I can get some feedback. (Lgb)
1190 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1191 // delete the LineSeparator.
1194 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1195 // delete the LineSeparator.
1198 // If the chars around the old cursor were spaces, delete one of them.
1199 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1201 // Only if the cursor has really moved.
1203 && old.pos() < oldpar.size()
1204 && oldpar.isLineSeparator(old.pos())
1205 && oldpar.isLineSeparator(old.pos() - 1)) {
1206 // We need to set the text to Change::INSERTED to
1207 // get it erased properly
1208 pars_[old.pit()].setChange(old.pos() -1,
1210 pars_[old.pit()].erase(old.pos() - 1);
1211 #ifdef WITH_WARNINGS
1212 #warning This will not work anymore when we have multiple views of the same buffer
1213 // In this case, we will have to correct also the cursors held by
1214 // other bufferviews. It will probably be easier to do that in a more
1215 // automated way in CursorSlice code. (JMarc 26/09/2001)
1217 // correct all cursor parts
1218 fixCursorAfterDelete(cur.top(), old.top());
1219 #ifdef WITH_WARNINGS
1220 #warning DEPM, look here
1222 //fixCursorAfterDelete(cur.anchor(), old.top());
1227 // only do our magic if we changed paragraph
1228 if (old.pit() == cur.pit())
1231 // don't delete anything if this is the ONLY paragraph!
1232 if (pars_.size() == 1)
1235 // Do not delete empty paragraphs with keepempty set.
1236 if (oldpar.allowEmpty())
1239 // record if we have deleted a paragraph
1240 // we can't possibly have deleted a paragraph before this point
1241 bool deleted = false;
1243 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1244 // ok, we will delete something
1247 bool selection_position_was_oldcursor_position =
1248 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1250 // This is a bit of a overkill. We change the old and the cur par
1251 // at max, certainly not everything in between...
1252 recUndo(old.pit(), cur.pit());
1255 pars_.erase(pars_.begin() + old.pit());
1257 // Update cursor par offset if necessary.
1258 // Some 'iterator registration' would be nice that takes care of
1259 // such events. Maybe even signal/slot?
1260 if (cur.pit() > old.pit())
1262 #ifdef WITH_WARNINGS
1263 #warning DEPM, look here
1265 // if (cur.anchor().pit() > old.pit())
1266 // --cur.anchor().pit();
1268 if (selection_position_was_oldcursor_position) {
1269 // correct selection
1277 if (pars_[old.pit()].stripLeadingSpaces())
1284 ParagraphList & LyXText::paragraphs() const
1286 return const_cast<ParagraphList &>(pars_);
1290 void LyXText::recUndo(pit_type first, pit_type last) const
1292 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1296 void LyXText::recUndo(pit_type par) const
1298 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1302 int defaultRowHeight()
1304 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);