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);
173 applyOuterFont(font);
175 // Realize with the fonts of lesser depth.
176 font.realize(defaultfont_);
181 // There are currently two font mechanisms in LyX:
182 // 1. The font attributes in a lyxtext, and
183 // 2. The inset-specific font properties, defined in an inset's
184 // metrics() and draw() methods and handed down the inset chain through
185 // the pi/mi parameters, and stored locally in a lyxtext in font_.
186 // This is where the two are integrated in the final fully realized
188 void LyXText::applyOuterFont(LyXFont & font) const {
190 lf.reduce(defaultfont_);
192 lf.setLanguage(font.language());
197 LyXFont LyXText::getLayoutFont(pit_type const pit) const
199 LyXLayout_ptr const & layout = pars_[pit].layout();
201 if (!pars_[pit].getDepth())
202 return layout->resfont;
204 LyXFont font = layout->font;
205 // Realize with the fonts of lesser depth.
206 //font.realize(outerFont(pit, paragraphs()));
207 font.realize(defaultfont_);
213 LyXFont LyXText::getLabelFont(Paragraph const & par) const
215 LyXLayout_ptr const & layout = par.layout();
218 return layout->reslabelfont;
220 LyXFont font = layout->labelfont;
221 // Realize with the fonts of lesser depth.
222 font.realize(defaultfont_);
228 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
231 LyXLayout_ptr const & layout = pars_[pit].layout();
233 // Get concrete layout font to reduce against
236 if (pos < pars_[pit].beginOfBody())
237 layoutfont = layout->labelfont;
239 layoutfont = layout->font;
241 // Realize against environment font information
242 if (pars_[pit].getDepth()) {
244 while (!layoutfont.resolved() &&
245 tp != pit_type(paragraphs().size()) &&
246 pars_[tp].getDepth()) {
247 tp = outerHook(tp, paragraphs());
248 if (tp != pit_type(paragraphs().size()))
249 layoutfont.realize(pars_[tp].layout()->font);
253 layoutfont.realize(defaultfont_);
255 // Now, reduce font against full layout font
256 font.reduce(layoutfont);
258 pars_[pit].setFont(pos, font);
263 // Asger is not sure we want to do this...
264 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
267 LyXLayout_ptr const & layout = par.layout();
268 pos_type const psize = par.size();
271 for (pos_type pos = 0; pos < psize; ++pos) {
272 if (pos < par.beginOfBody())
273 layoutfont = layout->labelfont;
275 layoutfont = layout->font;
277 LyXFont tmpfont = par.getFontSettings(params, pos);
278 tmpfont.reduce(layoutfont);
279 par.setFont(pos, tmpfont);
284 // return past-the-last paragraph influenced by a layout change on pit
285 pit_type LyXText::undoSpan(pit_type pit)
287 pit_type end = paragraphs().size();
288 pit_type nextpit = pit + 1;
291 //because of parindents
292 if (!pars_[pit].getDepth())
293 return boost::next(nextpit);
294 //because of depth constrains
295 for (; nextpit != end; ++pit, ++nextpit) {
296 if (!pars_[pit].getDepth())
303 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
305 BOOST_ASSERT(start != end);
306 pit_type undopit = undoSpan(end - 1);
307 recUndo(start, undopit - 1);
309 BufferParams const & bufparams = bv()->buffer()->params();
310 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
312 for (pit_type pit = start; pit != end; ++pit) {
313 pars_[pit].applyLayout(lyxlayout);
314 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
315 if (lyxlayout->margintype == MARGIN_MANUAL)
316 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
323 // set layout over selection and make a total rebreak of those paragraphs
324 void LyXText::setLayout(LCursor & cur, string const & layout)
326 BOOST_ASSERT(this == cur.text());
327 // special handling of new environment insets
328 BufferView & bv = cur.bv();
329 BufferParams const & params = bv.buffer()->params();
330 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
331 if (lyxlayout->is_environment) {
332 // move everything in a new environment inset
333 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
334 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
335 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
336 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
337 InsetBase * inset = new InsetEnvironment(params, layout);
338 insertInset(cur, inset);
339 //inset->edit(cur, true);
340 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
344 pit_type start = cur.selBegin().pit();
345 pit_type end = cur.selEnd().pit() + 1;
346 setLayout(start, end, layout);
347 updateCounters(cur.buffer());
354 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
355 Paragraph const & par, int max_depth)
357 if (par.layout()->labeltype == LABEL_BIBLIO)
359 int const depth = par.params().depth();
360 if (type == LyXText::INC_DEPTH && depth < max_depth)
362 if (type == LyXText::DEC_DEPTH && depth > 0)
371 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
373 BOOST_ASSERT(this == cur.text());
374 pit_type const beg = cur.selBegin().pit();
375 pit_type const end = cur.selEnd().pit() + 1;
376 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
378 for (pit_type pit = beg; pit != end; ++pit) {
379 if (::changeDepthAllowed(type, pars_[pit], max_depth))
381 max_depth = pars_[pit].getMaxDepthAfter();
387 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
389 BOOST_ASSERT(this == cur.text());
390 pit_type const beg = cur.selBegin().pit();
391 pit_type const end = cur.selEnd().pit() + 1;
392 recordUndoSelection(cur);
393 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
395 for (pit_type pit = beg; pit != end; ++pit) {
396 Paragraph & par = pars_[pit];
397 if (::changeDepthAllowed(type, par, max_depth)) {
398 int const depth = par.params().depth();
399 if (type == INC_DEPTH)
400 par.params().depth(depth + 1);
402 par.params().depth(depth - 1);
404 max_depth = par.getMaxDepthAfter();
406 // this handles the counter labels, and also fixes up
407 // depth values for follow-on (child) paragraphs
408 updateCounters(cur.buffer());
412 // set font over selection
413 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
415 BOOST_ASSERT(this == cur.text());
416 // if there is no selection just set the current_font
417 if (!cur.selection()) {
418 // Determine basis font
420 pit_type pit = cur.pit();
421 if (cur.pos() < pars_[pit].beginOfBody())
422 layoutfont = getLabelFont(pars_[pit]);
424 layoutfont = getLayoutFont(pit);
426 // Update current font
427 real_current_font.update(font,
428 cur.buffer().params().language,
431 // Reduce to implicit settings
432 current_font = real_current_font;
433 current_font.reduce(layoutfont);
434 // And resolve it completely
435 real_current_font.realize(layoutfont);
440 // Ok, we have a selection.
441 recordUndoSelection(cur);
443 DocIterator dit = cur.selectionBegin();
444 DocIterator ditend = cur.selectionEnd();
446 BufferParams const & params = cur.buffer().params();
448 // Don't use forwardChar here as ditend might have
449 // pos() == lastpos() and forwardChar would miss it.
450 // Can't use forwardPos either as this descends into
452 for (; dit != ditend; dit.forwardPosNoDescend()) {
453 if (dit.pos() != dit.lastpos()) {
454 LyXFont f = getFont(dit.paragraph(), dit.pos());
455 f.update(font, params.language, toggleall);
456 setCharFont(dit.pit(), dit.pos(), f);
462 // the cursor set functions have a special mechanism. When they
463 // realize you left an empty paragraph, they will delete it.
465 void LyXText::cursorHome(LCursor & cur)
467 BOOST_ASSERT(this == cur.text());
468 setCursor(cur, cur.pit(), cur.textRow().pos());
472 void LyXText::cursorEnd(LCursor & cur)
474 BOOST_ASSERT(this == cur.text());
475 // if not on the last row of the par, put the cursor before
477 // FIXME: does this final space exist?
478 pos_type const end = cur.textRow().endpos();
479 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
483 void LyXText::cursorTop(LCursor & cur)
485 BOOST_ASSERT(this == cur.text());
486 setCursor(cur, 0, 0);
490 void LyXText::cursorBottom(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
497 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
499 BOOST_ASSERT(this == cur.text());
500 // If the mask is completely neutral, tell user
501 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
502 // Could only happen with user style
503 cur.message(_("No font change defined. "
504 "Use Character under the Layout menu to define font change."));
508 // Try implicit word selection
509 // If there is a change in the language the implicit word selection
511 CursorSlice resetCursor = cur.top();
512 bool implicitSelection =
513 font.language() == ignore_language
514 && font.number() == LyXFont::IGNORE
515 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
518 setFont(cur, font, toggleall);
520 // Implicit selections are cleared afterwards
521 // and cursor is set to the original position.
522 if (implicitSelection) {
523 cur.clearSelection();
524 cur.top() = resetCursor;
530 string LyXText::getStringToIndex(LCursor & cur)
532 BOOST_ASSERT(this == cur.text());
533 // Try implicit word selection
534 // If there is a change in the language the implicit word selection
536 CursorSlice const reset_cursor = cur.top();
537 bool const implicitSelection =
538 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
541 if (!cur.selection())
542 cur.message(_("Nothing to index!"));
543 else if (cur.selBegin().pit() != cur.selEnd().pit())
544 cur.message(_("Cannot index more than one paragraph!"));
546 idxstring = cur.selectionAsString(false);
548 // Reset cursors to their original position.
549 cur.top() = reset_cursor;
552 // Clear the implicit selection.
553 if (implicitSelection)
554 cur.clearSelection();
560 void LyXText::setParagraph(LCursor & cur,
561 Spacing const & spacing, LyXAlignment align,
562 string const & labelwidthstring, bool noindent)
564 BOOST_ASSERT(cur.text());
565 // make sure that the depth behind the selection are restored, too
566 pit_type undopit = undoSpan(cur.selEnd().pit());
567 recUndo(cur.selBegin().pit(), undopit - 1);
569 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
571 Paragraph & par = pars_[pit];
572 ParagraphParameters & params = par.params();
573 params.spacing(spacing);
575 // does the layout allow the new alignment?
576 LyXLayout_ptr const & layout = par.layout();
578 if (align == LYX_ALIGN_LAYOUT)
579 align = layout->align;
580 if (align & layout->alignpossible) {
581 if (align == layout->align)
582 params.align(LYX_ALIGN_LAYOUT);
586 par.setLabelWidthString(labelwidthstring);
587 params.noindent(noindent);
592 // this really should just insert the inset and not move the cursor.
593 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
595 BOOST_ASSERT(this == cur.text());
597 cur.paragraph().insertInset(cur.pos(), inset);
601 // needed to insert the selection
602 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
604 pit_type pit = cur.pit();
605 pos_type pos = cur.pos();
608 // only to be sure, should not be neccessary
609 cur.clearSelection();
610 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
614 setCursor(cur, cur.pit(), pos);
619 // turn double CR to single CR, others are converted into one
620 // blank. Then insertStringAsLines is called
621 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
623 string linestr = str;
624 bool newline_inserted = false;
626 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
627 if (linestr[i] == '\n') {
628 if (newline_inserted) {
629 // we know that \r will be ignored by
630 // insertStringAsLines. Of course, it is a dirty
631 // trick, but it works...
632 linestr[i - 1] = '\r';
636 newline_inserted = true;
638 } else if (IsPrintable(linestr[i])) {
639 newline_inserted = false;
642 insertStringAsLines(cur, linestr);
646 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
647 bool setfont, bool boundary)
650 setCursorIntern(cur, par, pos, setfont, boundary);
651 return deleteEmptyParagraphMechanism(cur, old);
655 void LyXText::setCursor(CursorSlice & cur, pit_type par,
656 pos_type pos, bool boundary)
658 BOOST_ASSERT(par != int(paragraphs().size()));
661 cur.boundary() = boundary;
663 // now some strict checking
664 Paragraph & para = getPar(par);
666 // None of these should happen, but we're scaredy-cats
668 lyxerr << "dont like -1" << endl;
672 if (pos > para.size()) {
673 lyxerr << "dont like 1, pos: " << pos
674 << " size: " << para.size()
675 << " par: " << par << endl;
681 void LyXText::setCursorIntern(LCursor & cur,
682 pit_type par, pos_type pos, bool setfont, bool boundary)
684 setCursor(cur.top(), par, pos, boundary);
691 void LyXText::setCurrentFont(LCursor & cur)
693 BOOST_ASSERT(this == cur.text());
694 pos_type pos = cur.pos();
695 Paragraph & par = cur.paragraph();
697 if (cur.boundary() && pos > 0)
701 if (pos == cur.lastpos())
703 else // potentional bug... BUG (Lgb)
704 if (par.isSeparator(pos)) {
705 if (pos > cur.textRow().pos() &&
706 bidi.level(pos) % 2 ==
707 bidi.level(pos - 1) % 2)
709 else if (pos + 1 < cur.lastpos())
714 BufferParams const & bufparams = cur.buffer().params();
715 current_font = par.getFontSettings(bufparams, pos);
716 real_current_font = getFont(par, pos);
718 if (cur.pos() == cur.lastpos()
719 && bidi.isBoundary(cur.buffer(), par, cur.pos())
720 && !cur.boundary()) {
721 Language const * lang = par.getParLanguage(bufparams);
722 current_font.setLanguage(lang);
723 current_font.setNumber(LyXFont::OFF);
724 real_current_font.setLanguage(lang);
725 real_current_font.setNumber(LyXFont::OFF);
730 // x is an absolute screen coord
731 // returns the column near the specified x-coordinate of the row
732 // x is set to the real beginning of this column
733 pos_type LyXText::getColumnNearX(pit_type const pit,
734 Row const & row, int & x, bool & boundary) const
736 int const xo = theCoords.get(this, pit).x_;
738 RowMetrics const r = computeRowMetrics(pit, row);
739 Paragraph const & par = pars_[pit];
741 pos_type vc = row.pos();
742 pos_type end = row.endpos();
744 LyXLayout_ptr const & layout = par.layout();
746 bool left_side = false;
748 pos_type body_pos = par.beginOfBody();
751 double last_tmpx = tmpx;
754 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
757 // check for empty row
763 while (vc < end && tmpx <= x) {
764 c = bidi.vis2log(vc);
766 if (body_pos > 0 && c == body_pos - 1) {
767 tmpx += r.label_hfill +
768 font_metrics::width(layout->labelsep, getLabelFont(par));
769 if (par.isLineSeparator(body_pos - 1))
770 tmpx -= singleWidth(par, body_pos - 1);
773 if (hfillExpansion(par, row, c)) {
774 tmpx += singleWidth(par, c);
778 tmpx += r.label_hfill;
779 } else if (par.isSeparator(c)) {
780 tmpx += singleWidth(par, c);
784 tmpx += singleWidth(par, c);
789 if ((tmpx + last_tmpx) / 2 > x) {
794 BOOST_ASSERT(vc <= end); // This shouldn't happen.
797 // This (rtl_support test) is not needed, but gives
798 // some speedup if rtl_support == false
799 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
801 // If lastrow is false, we don't need to compute
803 bool const rtl = lastrow ? isRTL(par) : false;
805 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
806 (!rtl && !left_side && vc == end && x > tmpx + 5)))
808 else if (vc == row.pos()) {
809 c = bidi.vis2log(vc);
810 if (bidi.level(c) % 2 == 1)
813 c = bidi.vis2log(vc - 1);
814 bool const rtl = (bidi.level(c) % 2 == 1);
815 if (left_side == rtl) {
817 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
821 // The following code is necessary because the cursor position past
822 // the last char in a row is logically equivalent to that before
823 // the first char in the next row. That's why insets causing row
824 // divisions -- Newline and display-style insets -- must be treated
825 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
826 // Newline inset, air gap below:
827 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
828 if (bidi.level(end -1) % 2 == 0)
829 tmpx -= singleWidth(par, end - 1);
831 tmpx += singleWidth(par, end - 1);
834 // Air gap above display inset:
835 if (row.pos() < end && c >= end && end < par.size()
836 && par.isInset(end) && par.getInset(end)->display()) {
839 // Air gap below display inset:
840 if (row.pos() < end && c >= end && par.isInset(end - 1)
841 && par.getInset(end - 1)->display()) {
846 return c - row.pos();
850 // y is screen coordinate
851 pit_type LyXText::getPitNearY(int y) const
853 BOOST_ASSERT(!paragraphs().empty());
854 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
855 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
856 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
857 << cc.size() << endl;
859 // look for highest numbered paragraph with y coordinate less than given y
862 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
863 CoordCache::InnerParPosCache::const_iterator et = cc.end();
864 for (; it != et; ++it) {
865 lyxerr << " examining: pit: " << it->first << " y: "
866 << it->second.y_ << endl;
867 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
873 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
878 Row const & LyXText::getRowNearY(int y, pit_type pit) const
880 Paragraph const & par = pars_[pit];
881 int yy = theCoords.get(this, pit).y_ - par.ascent();
882 BOOST_ASSERT(!par.rows().empty());
883 RowList::const_iterator rit = par.rows().begin();
884 RowList::const_iterator const rlast = boost::prior(par.rows().end());
885 for (; rit != rlast; yy += rit->height(), ++rit)
886 if (yy + rit->height() > y)
892 // x,y are absolute screen coordinates
893 // sets cursor recursively descending into nested editable insets
894 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
896 pit_type pit = getPitNearY(y);
897 BOOST_ASSERT(pit != -1);
898 Row const & row = getRowNearY(y, pit);
901 int xx = x; // is modified by getColumnNearX
902 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
905 cur.boundary() = bound;
908 // try to descend into nested insets
909 InsetBase * inset = checkInsetHit(x, y);
910 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
912 // Either we deconst editXY or better we move current_font
913 // and real_current_font to LCursor
914 const_cast<LyXText *>(this)->setCurrentFont(cur);
918 // This should be just before or just behind the
919 // cursor position set above.
920 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
921 || inset == pars_[pit].getInset(pos));
922 // Make sure the cursor points to the position before
924 if (inset == pars_[pit].getInset(pos - 1))
926 inset = inset->editXY(cur, x, y);
927 if (cur.top().text() == this)
928 const_cast<LyXText *>(this)->setCurrentFont(cur);
933 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
937 if (cur.pos() == cur.lastpos())
939 InsetBase * inset = cur.nextInset();
940 if (!isHighlyEditableInset(inset))
942 inset->edit(cur, front);
947 bool LyXText::cursorLeft(LCursor & cur)
949 if (cur.pos() != 0) {
950 bool boundary = cur.boundary();
951 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
952 if (!checkAndActivateInset(cur, false)) {
953 if (false && !boundary &&
954 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
956 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
961 if (cur.pit() != 0) {
962 // Steps into the paragraph above
963 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
969 bool LyXText::cursorRight(LCursor & cur)
971 if (false && cur.boundary()) {
972 return setCursor(cur, cur.pit(), cur.pos(), true, false);
975 if (cur.pos() != cur.lastpos()) {
976 bool updateNeeded = false;
977 if (!checkAndActivateInset(cur, true)) {
978 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
979 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
981 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
986 if (cur.pit() != cur.lastpit())
987 return setCursor(cur, cur.pit() + 1, 0);
992 bool LyXText::cursorUp(LCursor & cur)
994 Paragraph const & par = cur.paragraph();
995 int const row = par.pos2row(cur.pos());
996 int const x = cur.targetX();
998 if (!cur.selection()) {
999 int const y = bv_funcs::getPos(cur).y_;
1001 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1003 // This happens when you move out of an inset.
1004 // And to give the DEPM the possibility of doing
1005 // something we must provide it with two different
1007 LCursor dummy = cur;
1011 return deleteEmptyParagraphMechanism(dummy, old);
1014 bool updateNeeded = false;
1017 updateNeeded |= setCursor(cur, cur.pit(),
1018 x2pos(cur.pit(), row - 1, x));
1019 } else if (cur.pit() > 0) {
1021 //cannot use 'par' now
1022 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1027 return updateNeeded;
1031 bool LyXText::cursorDown(LCursor & cur)
1033 Paragraph const & par = cur.paragraph();
1034 int const row = par.pos2row(cur.pos());
1035 int const x = cur.targetX();
1037 if (!cur.selection()) {
1038 int const y = bv_funcs::getPos(cur).y_;
1040 editXY(cur, x, y + par.rows()[row].descent() + 1);
1042 // This happens when you move out of an inset.
1043 // And to give the DEPM the possibility of doing
1044 // something we must provide it with two different
1046 LCursor dummy = cur;
1050 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1052 // Make sure that cur gets back whatever happened to dummy(Lgb)
1060 bool updateNeeded = false;
1062 if (row + 1 < int(par.rows().size())) {
1063 updateNeeded |= setCursor(cur, cur.pit(),
1064 x2pos(cur.pit(), row + 1, x));
1065 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1067 updateNeeded |= setCursor(cur, cur.pit(),
1068 x2pos(cur.pit(), 0, x));
1073 return updateNeeded;
1077 bool LyXText::cursorUpParagraph(LCursor & cur)
1079 bool updated = false;
1081 updated = setCursor(cur, cur.pit(), 0);
1082 else if (cur.pit() != 0)
1083 updated = setCursor(cur, cur.pit() - 1, 0);
1088 bool LyXText::cursorDownParagraph(LCursor & cur)
1090 bool updated = false;
1091 if (cur.pit() != cur.lastpit())
1092 updated = setCursor(cur, cur.pit() + 1, 0);
1094 updated = setCursor(cur, cur.pit(), cur.lastpos());
1099 // fix the cursor `cur' after a characters has been deleted at `where'
1100 // position. Called by deleteEmptyParagraphMechanism
1101 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1103 // Do nothing if cursor is not in the paragraph where the
1104 // deletion occured,
1105 if (cur.pit() != where.pit())
1108 // If cursor position is after the deletion place update it
1109 if (cur.pos() > where.pos())
1112 // Check also if we don't want to set the cursor on a spot behind the
1113 // pagragraph because we erased the last character.
1114 if (cur.pos() > cur.lastpos())
1115 cur.pos() = cur.lastpos();
1119 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1121 // Would be wrong to delete anything if we have a selection.
1122 if (cur.selection())
1125 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1126 Paragraph const & oldpar = pars_[old.pit()];
1128 // We allow all kinds of "mumbo-jumbo" when freespacing.
1129 if (oldpar.isFreeSpacing())
1132 /* Ok I'll put some comments here about what is missing.
1133 I have fixed BackSpace (and thus Delete) to not delete
1134 double-spaces automagically. I have also changed Cut,
1135 Copy and Paste to hopefully do some sensible things.
1136 There are still some small problems that can lead to
1137 double spaces stored in the document file or space at
1138 the beginning of paragraphs(). This happens if you have
1139 the cursor between to spaces and then save. Or if you
1140 cut and paste and the selection have a space at the
1141 beginning and then save right after the paste. I am
1142 sure none of these are very hard to fix, but I will
1143 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1144 that I can get some feedback. (Lgb)
1147 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1148 // delete the LineSeparator.
1151 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1152 // delete the LineSeparator.
1155 // If the chars around the old cursor were spaces, delete one of them.
1156 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1158 // Only if the cursor has really moved.
1160 && old.pos() < oldpar.size()
1161 && oldpar.isLineSeparator(old.pos())
1162 && oldpar.isLineSeparator(old.pos() - 1)) {
1163 // We need to set the text to Change::INSERTED to
1164 // get it erased properly
1165 pars_[old.pit()].setChange(old.pos() -1,
1167 pars_[old.pit()].erase(old.pos() - 1);
1168 #ifdef WITH_WARNINGS
1169 #warning This will not work anymore when we have multiple views of the same buffer
1170 // In this case, we will have to correct also the cursors held by
1171 // other bufferviews. It will probably be easier to do that in a more
1172 // automated way in CursorSlice code. (JMarc 26/09/2001)
1174 // correct all cursor parts
1175 fixCursorAfterDelete(cur.top(), old.top());
1176 #ifdef WITH_WARNINGS
1177 #warning DEPM, look here
1179 //fixCursorAfterDelete(cur.anchor(), old.top());
1184 // only do our magic if we changed paragraph
1185 if (old.pit() == cur.pit())
1188 // don't delete anything if this is the ONLY paragraph!
1189 if (pars_.size() == 1)
1192 // Do not delete empty paragraphs with keepempty set.
1193 if (oldpar.allowEmpty())
1196 // record if we have deleted a paragraph
1197 // we can't possibly have deleted a paragraph before this point
1198 bool deleted = false;
1200 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1201 // ok, we will delete something
1204 bool selection_position_was_oldcursor_position =
1205 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1207 // This is a bit of a overkill. We change the old and the cur par
1208 // at max, certainly not everything in between...
1209 recUndo(old.pit(), cur.pit());
1212 pars_.erase(pars_.begin() + old.pit());
1214 // Update cursor par offset if necessary.
1215 // Some 'iterator registration' would be nice that takes care of
1216 // such events. Maybe even signal/slot?
1217 if (cur.pit() > old.pit())
1219 #ifdef WITH_WARNINGS
1220 #warning DEPM, look here
1222 // if (cur.anchor().pit() > old.pit())
1223 // --cur.anchor().pit();
1225 if (selection_position_was_oldcursor_position) {
1226 // correct selection
1234 if (pars_[old.pit()].stripLeadingSpaces())
1241 ParagraphList & LyXText::paragraphs() const
1243 return const_cast<ParagraphList &>(pars_);
1247 void LyXText::recUndo(pit_type first, pit_type last) const
1249 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1253 void LyXText::recUndo(pit_type par) const
1255 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1259 int defaultRowHeight()
1261 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);