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 // Inside inset, apply the inset's font attributes if any
289 layoutfont.realize(font_);
291 layoutfont.realize(defaultfont_);
293 // Now, reduce font against full layout font
294 font.reduce(layoutfont);
296 pars_[pit].setFont(pos, font);
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 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
321 BOOST_ASSERT(start != end);
323 BufferParams const & bufparams = bv()->buffer()->params();
324 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
326 for (pit_type pit = start; pit != end; ++pit) {
327 pars_[pit].applyLayout(lyxlayout);
328 if (lyxlayout->margintype == MARGIN_MANUAL)
329 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
334 // set layout over selection and make a total rebreak of those paragraphs
335 void LyXText::setLayout(LCursor & cur, string const & layout)
337 BOOST_ASSERT(this == cur.text());
338 // special handling of new environment insets
339 BufferView & bv = cur.bv();
340 BufferParams const & params = bv.buffer()->params();
341 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
342 if (lyxlayout->is_environment) {
343 // move everything in a new environment inset
344 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
345 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
346 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
347 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
348 InsetBase * inset = new InsetEnvironment(params, layout);
349 insertInset(cur, inset);
350 //inset->edit(cur, true);
351 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
355 pit_type start = cur.selBegin().pit();
356 pit_type end = cur.selEnd().pit() + 1;
357 pit_type undopit = undoSpan(end - 1);
358 recUndo(start, undopit - 1);
359 setLayout(start, end, layout);
360 updateCounters(cur.buffer());
367 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
368 Paragraph const & par, int max_depth)
370 if (par.layout()->labeltype == LABEL_BIBLIO)
372 int const depth = par.params().depth();
373 if (type == LyXText::INC_DEPTH && depth < max_depth)
375 if (type == LyXText::DEC_DEPTH && depth > 0)
384 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
386 BOOST_ASSERT(this == cur.text());
387 pit_type const beg = cur.selBegin().pit();
388 pit_type const end = cur.selEnd().pit() + 1;
389 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
391 for (pit_type pit = beg; pit != end; ++pit) {
392 if (::changeDepthAllowed(type, pars_[pit], max_depth))
394 max_depth = pars_[pit].getMaxDepthAfter();
400 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
402 BOOST_ASSERT(this == cur.text());
403 pit_type const beg = cur.selBegin().pit();
404 pit_type const end = cur.selEnd().pit() + 1;
405 recordUndoSelection(cur);
406 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
408 for (pit_type pit = beg; pit != end; ++pit) {
409 Paragraph & par = pars_[pit];
410 if (::changeDepthAllowed(type, par, max_depth)) {
411 int const depth = par.params().depth();
412 if (type == INC_DEPTH)
413 par.params().depth(depth + 1);
415 par.params().depth(depth - 1);
417 max_depth = par.getMaxDepthAfter();
419 // this handles the counter labels, and also fixes up
420 // depth values for follow-on (child) paragraphs
421 updateCounters(cur.buffer());
425 // set font over selection
426 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
428 BOOST_ASSERT(this == cur.text());
429 // if there is no selection just set the current_font
430 if (!cur.selection()) {
431 // Determine basis font
433 pit_type pit = cur.pit();
434 if (cur.pos() < pars_[pit].beginOfBody())
435 layoutfont = getLabelFont(pars_[pit]);
437 layoutfont = getLayoutFont(pit);
439 // Update current font
440 real_current_font.update(font,
441 cur.buffer().params().language,
444 // Reduce to implicit settings
445 current_font = real_current_font;
446 current_font.reduce(layoutfont);
447 // And resolve it completely
448 real_current_font.realize(layoutfont);
453 // Ok, we have a selection.
454 recordUndoSelection(cur);
456 DocIterator dit = cur.selectionBegin();
457 DocIterator ditend = cur.selectionEnd();
459 BufferParams const & params = cur.buffer().params();
461 // Don't use forwardChar here as ditend might have
462 // pos() == lastpos() and forwardChar would miss it.
463 // Can't use forwardPos either as this descends into
465 for (; dit != ditend; dit.forwardPosNoDescend()) {
466 if (dit.pos() != dit.lastpos()) {
467 LyXFont f = getFont(dit.paragraph(), dit.pos());
468 f.update(font, params.language, toggleall);
469 setCharFont(dit.pit(), dit.pos(), f);
475 // the cursor set functions have a special mechanism. When they
476 // realize you left an empty paragraph, they will delete it.
478 void LyXText::cursorHome(LCursor & cur)
480 BOOST_ASSERT(this == cur.text());
481 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
483 setCursor(cur, cur.pit(), row.pos());
487 void LyXText::cursorEnd(LCursor & cur)
489 BOOST_ASSERT(this == cur.text());
490 // if not on the last row of the par, put the cursor before
491 // the final space exept if I have a spanning inset or one string
492 // is so long that we force a break.
493 pos_type end = cur.textRow().endpos();
495 // empty text, end-1 is no valid position
497 bool boundary = false;
498 if (end != cur.lastpos()) {
499 if (!cur.paragraph().isLineSeparator(end-1)
500 && !cur.paragraph().isNewline(end-1))
505 setCursor(cur, cur.pit(), end, true, boundary);
509 void LyXText::cursorTop(LCursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 setCursor(cur, 0, 0);
516 void LyXText::cursorBottom(LCursor & cur)
518 BOOST_ASSERT(this == cur.text());
519 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
523 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
525 BOOST_ASSERT(this == cur.text());
526 // If the mask is completely neutral, tell user
527 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
528 // Could only happen with user style
529 cur.message(_("No font change defined. "
530 "Use Character under the Layout menu to define font change."));
534 // Try implicit word selection
535 // If there is a change in the language the implicit word selection
537 CursorSlice resetCursor = cur.top();
538 bool implicitSelection =
539 font.language() == ignore_language
540 && font.number() == LyXFont::IGNORE
541 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
544 setFont(cur, font, toggleall);
546 // Implicit selections are cleared afterwards
547 // and cursor is set to the original position.
548 if (implicitSelection) {
549 cur.clearSelection();
550 cur.top() = resetCursor;
556 string LyXText::getStringToIndex(LCursor const & cur)
558 BOOST_ASSERT(this == cur.text());
561 if (cur.selection()) {
562 idxstring = cur.selectionAsString(false);
564 // Try implicit word selection. If there is a change
565 // in the language the implicit word selection is
567 LCursor tmpcur = cur;
568 selectWord(tmpcur, lyx::PREVIOUS_WORD);
570 if (!tmpcur.selection())
571 cur.message(_("Nothing to index!"));
572 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
573 cur.message(_("Cannot index more than one paragraph!"));
575 idxstring = tmpcur.selectionAsString(false);
582 void LyXText::setParagraph(LCursor & cur,
583 Spacing const & spacing, LyXAlignment align,
584 string const & labelwidthstring, bool noindent)
586 BOOST_ASSERT(cur.text());
587 // make sure that the depth behind the selection are restored, too
588 pit_type undopit = undoSpan(cur.selEnd().pit());
589 recUndo(cur.selBegin().pit(), undopit - 1);
591 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
593 Paragraph & par = pars_[pit];
594 ParagraphParameters & params = par.params();
595 params.spacing(spacing);
597 // does the layout allow the new alignment?
598 LyXLayout_ptr const & layout = par.layout();
600 if (align == LYX_ALIGN_LAYOUT)
601 align = layout->align;
602 if (align & layout->alignpossible) {
603 if (align == layout->align)
604 params.align(LYX_ALIGN_LAYOUT);
608 par.setLabelWidthString(labelwidthstring);
609 params.noindent(noindent);
614 // this really should just insert the inset and not move the cursor.
615 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
617 BOOST_ASSERT(this == cur.text());
619 cur.paragraph().insertInset(cur.pos(), inset);
623 // needed to insert the selection
624 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
626 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
627 current_font, str, autoBreakRows_);
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, pos_type pos)
669 BOOST_ASSERT(par != int(paragraphs().size()));
673 // now some strict checking
674 Paragraph & para = getPar(par);
676 // None of these should happen, but we're scaredy-cats
678 lyxerr << "dont like -1" << endl;
682 if (pos > para.size()) {
683 lyxerr << "dont like 1, pos: " << pos
684 << " size: " << para.size()
685 << " par: " << par << endl;
691 void LyXText::setCursorIntern(LCursor & cur,
692 pit_type par, pos_type pos, bool setfont, bool boundary)
694 cur.boundary(boundary);
695 setCursor(cur.top(), par, pos);
702 void LyXText::setCurrentFont(LCursor & cur)
704 BOOST_ASSERT(this == cur.text());
705 pos_type pos = cur.pos();
706 Paragraph & par = cur.paragraph();
708 if (cur.boundary() && pos > 0)
712 if (pos == cur.lastpos())
714 else // potentional bug... BUG (Lgb)
715 if (par.isSeparator(pos)) {
716 if (pos > cur.textRow().pos() &&
717 bidi.level(pos) % 2 ==
718 bidi.level(pos - 1) % 2)
720 else if (pos + 1 < cur.lastpos())
725 BufferParams const & bufparams = cur.buffer().params();
726 current_font = par.getFontSettings(bufparams, pos);
727 real_current_font = getFont(par, pos);
729 if (cur.pos() == cur.lastpos()
730 && bidi.isBoundary(cur.buffer(), par, cur.pos())
731 && !cur.boundary()) {
732 Language const * lang = par.getParLanguage(bufparams);
733 current_font.setLanguage(lang);
734 current_font.setNumber(LyXFont::OFF);
735 real_current_font.setLanguage(lang);
736 real_current_font.setNumber(LyXFont::OFF);
741 // x is an absolute screen coord
742 // returns the column near the specified x-coordinate of the row
743 // x is set to the real beginning of this column
744 pos_type LyXText::getColumnNearX(pit_type const pit,
745 Row const & row, int & x, bool & boundary) const
747 int const xo = theCoords.get(this, pit).x_;
749 RowMetrics const r = computeRowMetrics(pit, row);
750 Paragraph const & par = pars_[pit];
752 pos_type vc = row.pos();
753 pos_type end = row.endpos();
755 LyXLayout_ptr const & layout = par.layout();
757 bool left_side = false;
759 pos_type body_pos = par.beginOfBody();
762 double last_tmpx = tmpx;
765 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
768 // check for empty row
774 while (vc < end && tmpx <= x) {
775 c = bidi.vis2log(vc);
777 if (body_pos > 0 && c == body_pos - 1) {
778 tmpx += r.label_hfill +
779 font_metrics::width(layout->labelsep, getLabelFont(par));
780 if (par.isLineSeparator(body_pos - 1))
781 tmpx -= singleWidth(par, body_pos - 1);
784 if (hfillExpansion(par, row, c)) {
785 tmpx += singleWidth(par, c);
789 tmpx += r.label_hfill;
790 } else if (par.isSeparator(c)) {
791 tmpx += singleWidth(par, c);
795 tmpx += singleWidth(par, c);
800 if ((tmpx + last_tmpx) / 2 > x) {
805 BOOST_ASSERT(vc <= end); // This shouldn't happen.
808 // This (rtl_support test) is not needed, but gives
809 // some speedup if rtl_support == false
810 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
812 // If lastrow is false, we don't need to compute
814 bool const rtl = lastrow ? isRTL(par) : false;
816 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
817 (!rtl && !left_side && vc == end && x > tmpx + 5)))
819 else if (vc == row.pos()) {
820 c = bidi.vis2log(vc);
821 if (bidi.level(c) % 2 == 1)
824 c = bidi.vis2log(vc - 1);
825 bool const rtl = (bidi.level(c) % 2 == 1);
826 if (left_side == rtl) {
828 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
832 // I believe this code is not needed anymore (Jug 20050717)
834 // The following code is necessary because the cursor position past
835 // the last char in a row is logically equivalent to that before
836 // the first char in the next row. That's why insets causing row
837 // divisions -- Newline and display-style insets -- must be treated
838 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
839 // Newline inset, air gap below:
840 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
841 if (bidi.level(end -1) % 2 == 0)
842 tmpx -= singleWidth(par, end - 1);
844 tmpx += singleWidth(par, end - 1);
848 // Air gap above display inset:
849 if (row.pos() < end && c >= end && end < par.size()
850 && par.isInset(end) && par.getInset(end)->display()) {
853 // Air gap below display inset:
854 if (row.pos() < end && c >= end && par.isInset(end - 1)
855 && par.getInset(end - 1)->display()) {
861 pos_type const col = c - row.pos();
863 if (!c || end == par.size())
866 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
871 return min(col, end - 1 - row.pos());
875 // y is screen coordinate
876 pit_type LyXText::getPitNearY(int y) const
878 BOOST_ASSERT(!paragraphs().empty());
879 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
880 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
882 << BOOST_CURRENT_FUNCTION
883 << ": y: " << y << " cache size: " << cc.size()
886 // look for highest numbered paragraph with y coordinate less than given y
889 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
890 CoordCache::InnerParPosCache::const_iterator et = cc.end();
891 for (; it != et; ++it) {
893 << BOOST_CURRENT_FUNCTION
894 << " examining: pit: " << it->first
895 << " y: " << it->second.y_
898 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
905 << BOOST_CURRENT_FUNCTION
906 << ": found best y: " << yy << " for pit: " << pit
913 Row const & LyXText::getRowNearY(int y, pit_type pit) const
915 Paragraph const & par = pars_[pit];
916 int yy = theCoords.get(this, pit).y_ - par.ascent();
917 BOOST_ASSERT(!par.rows().empty());
918 RowList::const_iterator rit = par.rows().begin();
919 RowList::const_iterator const rlast = boost::prior(par.rows().end());
920 for (; rit != rlast; yy += rit->height(), ++rit)
921 if (yy + rit->height() > y)
927 // x,y are absolute screen coordinates
928 // sets cursor recursively descending into nested editable insets
929 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
931 pit_type pit = getPitNearY(y);
932 BOOST_ASSERT(pit != -1);
933 Row const & row = getRowNearY(y, pit);
936 int xx = x; // is modified by getColumnNearX
937 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
943 // try to descend into nested insets
944 InsetBase * inset = checkInsetHit(x, y);
945 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
947 // Either we deconst editXY or better we move current_font
948 // and real_current_font to LCursor
953 // This should be just before or just behind the
954 // cursor position set above.
955 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
956 || inset == pars_[pit].getInset(pos));
957 // Make sure the cursor points to the position before
959 if (inset == pars_[pit].getInset(pos - 1))
961 inset = inset->editXY(cur, x, y);
962 if (cur.top().text() == this)
968 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
972 if (cur.pos() == cur.lastpos())
974 InsetBase * inset = cur.nextInset();
975 if (!isHighlyEditableInset(inset))
977 inset->edit(cur, front);
982 bool LyXText::cursorLeft(LCursor & cur)
984 if (!cur.boundary() && cur.pos() > 0 &&
985 cur.textRow().pos() == cur.pos() &&
986 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
987 !cur.paragraph().isNewline(cur.pos()-1)) {
988 return setCursor(cur, cur.pit(), cur.pos(), true, true);
990 if (cur.pos() != 0) {
991 bool boundary = cur.boundary();
992 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
993 if (!checkAndActivateInset(cur, false)) {
994 if (false && !boundary &&
995 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
997 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1002 if (cur.pit() != 0) {
1003 // Steps into the paragraph above
1004 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1010 bool LyXText::cursorRight(LCursor & cur)
1012 if (cur.pos() != cur.lastpos()) {
1014 return setCursor(cur, cur.pit(), cur.pos(),
1017 bool updateNeeded = false;
1018 if (!checkAndActivateInset(cur, true)) {
1019 if (cur.textRow().endpos() == cur.pos() + 1 &&
1020 cur.textRow().endpos() != cur.lastpos() &&
1021 !cur.paragraph().isLineSeparator(cur.pos()) &&
1022 !cur.paragraph().isNewline(cur.pos())) {
1025 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1026 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1028 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1030 return updateNeeded;
1033 if (cur.pit() != cur.lastpit())
1034 return setCursor(cur, cur.pit() + 1, 0);
1039 bool LyXText::cursorUp(LCursor & cur)
1041 Paragraph const & par = cur.paragraph();
1043 int const x = cur.targetX();
1045 if (cur.pos() && cur.boundary())
1046 row = par.pos2row(cur.pos()-1);
1048 row = par.pos2row(cur.pos());
1050 if (!cur.selection()) {
1051 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1053 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1054 cur.clearSelection();
1056 // This happens when you move out of an inset.
1057 // And to give the DEPM the possibility of doing
1058 // something we must provide it with two different
1060 LCursor dummy = cur;
1064 return deleteEmptyParagraphMechanism(dummy, old);
1067 bool updateNeeded = false;
1070 updateNeeded |= setCursor(cur, cur.pit(),
1071 x2pos(cur.pit(), row - 1, x));
1072 } else if (cur.pit() > 0) {
1074 //cannot use 'par' now
1075 updateNeeded |= setCursor(cur, cur.pit(),
1076 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1081 return updateNeeded;
1085 bool LyXText::cursorDown(LCursor & cur)
1087 Paragraph const & par = cur.paragraph();
1089 int const x = cur.targetX();
1091 if (cur.pos() && cur.boundary())
1092 row = par.pos2row(cur.pos()-1);
1094 row = par.pos2row(cur.pos());
1096 if (!cur.selection()) {
1097 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1099 editXY(cur, x, y + par.rows()[row].descent() + 1);
1100 cur.clearSelection();
1102 // This happens when you move out of an inset.
1103 // And to give the DEPM the possibility of doing
1104 // something we must provide it with two different
1106 LCursor dummy = cur;
1110 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1112 // Make sure that cur gets back whatever happened to dummy(Lgb)
1119 bool updateNeeded = false;
1121 if (row + 1 < int(par.rows().size())) {
1122 updateNeeded |= setCursor(cur, cur.pit(),
1123 x2pos(cur.pit(), row + 1, x));
1124 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1126 updateNeeded |= setCursor(cur, cur.pit(),
1127 x2pos(cur.pit(), 0, x));
1132 return updateNeeded;
1136 bool LyXText::cursorUpParagraph(LCursor & cur)
1138 bool updated = false;
1140 updated = setCursor(cur, cur.pit(), 0);
1141 else if (cur.pit() != 0)
1142 updated = setCursor(cur, cur.pit() - 1, 0);
1147 bool LyXText::cursorDownParagraph(LCursor & cur)
1149 bool updated = false;
1150 if (cur.pit() != cur.lastpit())
1151 updated = setCursor(cur, cur.pit() + 1, 0);
1153 updated = setCursor(cur, cur.pit(), cur.lastpos());
1158 // fix the cursor `cur' after a characters has been deleted at `where'
1159 // position. Called by deleteEmptyParagraphMechanism
1160 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1162 // Do nothing if cursor is not in the paragraph where the
1163 // deletion occured,
1164 if (cur.pit() != where.pit())
1167 // If cursor position is after the deletion place update it
1168 if (cur.pos() > where.pos())
1171 // Check also if we don't want to set the cursor on a spot behind the
1172 // pagragraph because we erased the last character.
1173 if (cur.pos() > cur.lastpos())
1174 cur.pos() = cur.lastpos();
1178 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1180 // Would be wrong to delete anything if we have a selection.
1181 if (cur.selection())
1184 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1185 // old should point to us
1186 BOOST_ASSERT(old.text() == this);
1188 Paragraph & oldpar = old.paragraph();
1190 // We allow all kinds of "mumbo-jumbo" when freespacing.
1191 if (oldpar.isFreeSpacing())
1194 /* Ok I'll put some comments here about what is missing.
1195 There are still some small problems that can lead to
1196 double spaces stored in the document file or space at
1197 the beginning of paragraphs(). This happens if you have
1198 the cursor between to spaces and then save. Or if you
1199 cut and paste and the selection have a space at the
1200 beginning and then save right after the paste. (Lgb)
1203 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1204 // delete the LineSeparator.
1207 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1208 // delete the LineSeparator.
1211 bool const same_inset = &old.inset() == &cur.inset();
1212 bool const same_par = same_inset && old.pit() == cur.pit();
1213 bool const same_par_pos = same_par && old.pos() == cur.pos();
1215 // If the chars around the old cursor were spaces, delete one of them.
1216 if (!same_par_pos) {
1217 // Only if the cursor has really moved.
1219 && old.pos() < oldpar.size()
1220 && oldpar.isLineSeparator(old.pos())
1221 && oldpar.isLineSeparator(old.pos() - 1)
1222 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1223 // We need to set the text to Change::INSERTED to
1224 // get it erased properly
1225 oldpar.setChange(old.pos() -1, Change::INSERTED);
1226 oldpar.erase(old.pos() - 1);
1227 #ifdef WITH_WARNINGS
1228 #warning This will not work anymore when we have multiple views of the same buffer
1229 // In this case, we will have to correct also the cursors held by
1230 // other bufferviews. It will probably be easier to do that in a more
1231 // automated way in CursorSlice code. (JMarc 26/09/2001)
1233 // correct all cursor parts
1235 fixCursorAfterDelete(cur.top(), old.top());
1242 // only do our magic if we changed paragraph
1246 // don't delete anything if this is the ONLY paragraph!
1247 if (old.lastpit() == 0)
1250 // Do not delete empty paragraphs with keepempty set.
1251 if (oldpar.allowEmpty())
1254 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1256 recordUndo(old, Undo::ATOMIC,
1257 old.pit(), min(old.pit() + 1, old.lastpit()));
1258 ParagraphList & plist = old.text()->paragraphs();
1259 plist.erase(plist.begin() + old.pit());
1261 // see #warning above
1262 if (cur.depth() >= old.depth()) {
1263 CursorSlice & curslice = cur[old.depth() - 1];
1264 if (&curslice.inset() == &old.inset()
1265 && curslice.pit() > old.pit()) {
1267 // since a paragraph has been deleted, all the
1268 // insets after `old' have been copied and
1269 // their address has changed. Therefore we
1270 // need to `regenerate' cur. (JMarc)
1271 cur.updateInsets(&(cur.bottom().inset()));
1275 updateCounters(old.buffer());
1279 if (oldpar.stripLeadingSpaces())
1286 void LyXText::recUndo(pit_type first, pit_type last) const
1288 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1292 void LyXText::recUndo(pit_type par) const
1294 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1298 int defaultRowHeight()
1300 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);