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 background_color_(LColor::background),
77 void LyXText::init(BufferView * bv)
81 maxwidth_ = bv->workWidth();
86 pit_type const end = paragraphs().size();
87 for (pit_type pit = 0; pit != end; ++pit)
88 pars_[pit].rows().clear();
90 current_font = getFont(pars_[0], 0);
91 updateCounters(*bv->buffer());
95 bool LyXText::isMainText() const
97 return &bv()->buffer()->text() == this;
101 //takes screen x,y coordinates
102 InsetBase * LyXText::checkInsetHit(int x, int y) const
104 pit_type pit = getPitNearY(y);
105 BOOST_ASSERT(pit != -1);
107 Paragraph const & par = pars_[pit];
110 << BOOST_CURRENT_FUNCTION
115 InsetList::const_iterator iit = par.insetlist.begin();
116 InsetList::const_iterator iend = par.insetlist.end();
117 for (; iit != iend; ++iit) {
118 InsetBase * inset = iit->inset;
121 << BOOST_CURRENT_FUNCTION
122 << ": examining inset " << inset << endl;
124 if (theCoords.getInsets().has(inset))
126 << BOOST_CURRENT_FUNCTION
127 << ": xo: " << inset->xo() << "..."
128 << inset->xo() + inset->width()
129 << " yo: " << inset->yo() - inset->ascent()
131 << inset->yo() + inset->descent()
135 << BOOST_CURRENT_FUNCTION
136 << ": inset has no cached position" << endl;
138 if (inset->covers(x, y)) {
140 << BOOST_CURRENT_FUNCTION
141 << ": Hit inset: " << inset << endl;
146 << BOOST_CURRENT_FUNCTION
147 << ": No inset hit. " << endl;
153 // Gets the fully instantiated font at a given position in a paragraph
154 // Basically the same routine as Paragraph::getFont() in paragraph.C.
155 // The difference is that this one is used for displaying, and thus we
156 // are allowed to make cosmetic improvements. For instance make footnotes
158 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
160 BOOST_ASSERT(pos >= 0);
162 LyXLayout_ptr const & layout = par.layout();
166 BufferParams const & params = bv()->buffer()->params();
167 pos_type const body_pos = par.beginOfBody();
169 // We specialize the 95% common case:
170 if (!par.getDepth()) {
171 LyXFont f = par.getFontSettings(params, pos);
174 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
175 return f.realize(layout->reslabelfont);
177 return f.realize(layout->resfont);
180 // The uncommon case need not be optimized as much
183 layoutfont = layout->labelfont;
185 layoutfont = layout->font;
187 LyXFont font = par.getFontSettings(params, pos);
188 font.realize(layoutfont);
191 applyOuterFont(font);
193 // Realize with the fonts of lesser depth.
194 font.realize(defaultfont_);
199 // There are currently two font mechanisms in LyX:
200 // 1. The font attributes in a lyxtext, and
201 // 2. The inset-specific font properties, defined in an inset's
202 // metrics() and draw() methods and handed down the inset chain through
203 // the pi/mi parameters, and stored locally in a lyxtext in font_.
204 // This is where the two are integrated in the final fully realized
206 void LyXText::applyOuterFont(LyXFont & font) const {
208 lf.reduce(defaultfont_);
210 lf.setLanguage(font.language());
215 LyXFont LyXText::getLayoutFont(pit_type const pit) const
217 LyXLayout_ptr const & layout = pars_[pit].layout();
219 if (!pars_[pit].getDepth())
220 return layout->resfont;
222 LyXFont font = layout->font;
223 // Realize with the fonts of lesser depth.
224 //font.realize(outerFont(pit, paragraphs()));
225 font.realize(defaultfont_);
231 LyXFont LyXText::getLabelFont(Paragraph const & par) const
233 LyXLayout_ptr const & layout = par.layout();
236 return layout->reslabelfont;
238 LyXFont font = layout->labelfont;
239 // Realize with the fonts of lesser depth.
240 font.realize(defaultfont_);
246 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
249 LyXLayout_ptr const & layout = pars_[pit].layout();
251 // Get concrete layout font to reduce against
254 if (pos < pars_[pit].beginOfBody())
255 layoutfont = layout->labelfont;
257 layoutfont = layout->font;
259 // Realize against environment font information
260 if (pars_[pit].getDepth()) {
262 while (!layoutfont.resolved() &&
263 tp != pit_type(paragraphs().size()) &&
264 pars_[tp].getDepth()) {
265 tp = outerHook(tp, paragraphs());
266 if (tp != pit_type(paragraphs().size()))
267 layoutfont.realize(pars_[tp].layout()->font);
271 layoutfont.realize(defaultfont_);
273 // Now, reduce font against full layout font
274 font.reduce(layoutfont);
276 pars_[pit].setFont(pos, font);
281 // Asger is not sure we want to do this...
282 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
285 LyXLayout_ptr const & layout = par.layout();
286 pos_type const psize = par.size();
289 for (pos_type pos = 0; pos < psize; ++pos) {
290 if (pos < par.beginOfBody())
291 layoutfont = layout->labelfont;
293 layoutfont = layout->font;
295 LyXFont tmpfont = par.getFontSettings(params, pos);
296 tmpfont.reduce(layoutfont);
297 par.setFont(pos, tmpfont);
302 // return past-the-last paragraph influenced by a layout change on pit
303 pit_type LyXText::undoSpan(pit_type pit)
305 pit_type end = paragraphs().size();
306 pit_type nextpit = pit + 1;
309 //because of parindents
310 if (!pars_[pit].getDepth())
311 return boost::next(nextpit);
312 //because of depth constrains
313 for (; nextpit != end; ++pit, ++nextpit) {
314 if (!pars_[pit].getDepth())
321 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
323 BOOST_ASSERT(start != end);
324 pit_type undopit = undoSpan(end - 1);
325 recUndo(start, undopit - 1);
327 BufferParams const & bufparams = bv()->buffer()->params();
328 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
330 for (pit_type pit = start; pit != end; ++pit) {
331 pars_[pit].applyLayout(lyxlayout);
332 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
333 if (lyxlayout->margintype == MARGIN_MANUAL)
334 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
341 // set layout over selection and make a total rebreak of those paragraphs
342 void LyXText::setLayout(LCursor & cur, string const & layout)
344 BOOST_ASSERT(this == cur.text());
345 // special handling of new environment insets
346 BufferView & bv = cur.bv();
347 BufferParams const & params = bv.buffer()->params();
348 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
349 if (lyxlayout->is_environment) {
350 // move everything in a new environment inset
351 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
352 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
353 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
354 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
355 InsetBase * inset = new InsetEnvironment(params, layout);
356 insertInset(cur, inset);
357 //inset->edit(cur, true);
358 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
362 pit_type start = cur.selBegin().pit();
363 pit_type end = cur.selEnd().pit() + 1;
364 setLayout(start, end, layout);
365 updateCounters(cur.buffer());
372 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
373 Paragraph const & par, int max_depth)
375 if (par.layout()->labeltype == LABEL_BIBLIO)
377 int const depth = par.params().depth();
378 if (type == LyXText::INC_DEPTH && depth < max_depth)
380 if (type == LyXText::DEC_DEPTH && depth > 0)
389 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
391 BOOST_ASSERT(this == cur.text());
392 pit_type const beg = cur.selBegin().pit();
393 pit_type const end = cur.selEnd().pit() + 1;
394 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
396 for (pit_type pit = beg; pit != end; ++pit) {
397 if (::changeDepthAllowed(type, pars_[pit], max_depth))
399 max_depth = pars_[pit].getMaxDepthAfter();
405 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
407 BOOST_ASSERT(this == cur.text());
408 pit_type const beg = cur.selBegin().pit();
409 pit_type const end = cur.selEnd().pit() + 1;
410 recordUndoSelection(cur);
411 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
413 for (pit_type pit = beg; pit != end; ++pit) {
414 Paragraph & par = pars_[pit];
415 if (::changeDepthAllowed(type, par, max_depth)) {
416 int const depth = par.params().depth();
417 if (type == INC_DEPTH)
418 par.params().depth(depth + 1);
420 par.params().depth(depth - 1);
422 max_depth = par.getMaxDepthAfter();
424 // this handles the counter labels, and also fixes up
425 // depth values for follow-on (child) paragraphs
426 updateCounters(cur.buffer());
430 // set font over selection
431 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
433 BOOST_ASSERT(this == cur.text());
434 // if there is no selection just set the current_font
435 if (!cur.selection()) {
436 // Determine basis font
438 pit_type pit = cur.pit();
439 if (cur.pos() < pars_[pit].beginOfBody())
440 layoutfont = getLabelFont(pars_[pit]);
442 layoutfont = getLayoutFont(pit);
444 // Update current font
445 real_current_font.update(font,
446 cur.buffer().params().language,
449 // Reduce to implicit settings
450 current_font = real_current_font;
451 current_font.reduce(layoutfont);
452 // And resolve it completely
453 real_current_font.realize(layoutfont);
458 // Ok, we have a selection.
459 recordUndoSelection(cur);
461 DocIterator dit = cur.selectionBegin();
462 DocIterator ditend = cur.selectionEnd();
464 BufferParams const & params = cur.buffer().params();
466 // Don't use forwardChar here as ditend might have
467 // pos() == lastpos() and forwardChar would miss it.
468 // Can't use forwardPos either as this descends into
470 for (; dit != ditend; dit.forwardPosNoDescend()) {
471 if (dit.pos() != dit.lastpos()) {
472 LyXFont f = getFont(dit.paragraph(), dit.pos());
473 f.update(font, params.language, toggleall);
474 setCharFont(dit.pit(), dit.pos(), f);
480 // the cursor set functions have a special mechanism. When they
481 // realize you left an empty paragraph, they will delete it.
483 void LyXText::cursorHome(LCursor & cur)
485 BOOST_ASSERT(this == cur.text());
486 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
488 setCursor(cur, cur.pit(), row.pos());
492 void LyXText::cursorEnd(LCursor & cur)
494 BOOST_ASSERT(this == cur.text());
495 // if not on the last row of the par, put the cursor before
496 // the final space exept if I have a spanning inset or one string
497 // is so long that we force a break.
498 pos_type end = cur.textRow().endpos();
500 // empty text, end-1 is no valid position
502 bool boundary = false;
503 if (end != cur.lastpos()) {
504 if (!cur.paragraph().isLineSeparator(end-1)
505 && !cur.paragraph().isNewline(end-1))
510 setCursor(cur, cur.pit(), end, true, boundary);
514 void LyXText::cursorTop(LCursor & cur)
516 BOOST_ASSERT(this == cur.text());
517 setCursor(cur, 0, 0);
521 void LyXText::cursorBottom(LCursor & cur)
523 BOOST_ASSERT(this == cur.text());
524 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
528 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
530 BOOST_ASSERT(this == cur.text());
531 // If the mask is completely neutral, tell user
532 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
533 // Could only happen with user style
534 cur.message(_("No font change defined. "
535 "Use Character under the Layout menu to define font change."));
539 // Try implicit word selection
540 // If there is a change in the language the implicit word selection
542 CursorSlice resetCursor = cur.top();
543 bool implicitSelection =
544 font.language() == ignore_language
545 && font.number() == LyXFont::IGNORE
546 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
549 setFont(cur, font, toggleall);
551 // Implicit selections are cleared afterwards
552 // and cursor is set to the original position.
553 if (implicitSelection) {
554 cur.clearSelection();
555 cur.top() = resetCursor;
561 string LyXText::getStringToIndex(LCursor const & cur)
563 BOOST_ASSERT(this == cur.text());
566 if (cur.selection()) {
567 idxstring = cur.selectionAsString(false);
569 // Try implicit word selection. If there is a change
570 // in the language the implicit word selection is
572 LCursor tmpcur = cur;
573 selectWord(tmpcur, lyx::PREVIOUS_WORD);
575 if (!tmpcur.selection())
576 cur.message(_("Nothing to index!"));
577 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
578 cur.message(_("Cannot index more than one paragraph!"));
580 idxstring = tmpcur.selectionAsString(false);
587 void LyXText::setParagraph(LCursor & cur,
588 Spacing const & spacing, LyXAlignment align,
589 string const & labelwidthstring, bool noindent)
591 BOOST_ASSERT(cur.text());
592 // make sure that the depth behind the selection are restored, too
593 pit_type undopit = undoSpan(cur.selEnd().pit());
594 recUndo(cur.selBegin().pit(), undopit - 1);
596 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
598 Paragraph & par = pars_[pit];
599 ParagraphParameters & params = par.params();
600 params.spacing(spacing);
602 // does the layout allow the new alignment?
603 LyXLayout_ptr const & layout = par.layout();
605 if (align == LYX_ALIGN_LAYOUT)
606 align = layout->align;
607 if (align & layout->alignpossible) {
608 if (align == layout->align)
609 params.align(LYX_ALIGN_LAYOUT);
613 par.setLabelWidthString(labelwidthstring);
614 params.noindent(noindent);
619 // this really should just insert the inset and not move the cursor.
620 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
622 BOOST_ASSERT(this == cur.text());
624 cur.paragraph().insertInset(cur.pos(), inset);
628 // needed to insert the selection
629 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
631 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
632 current_font, str, autoBreakRows_);
636 // turn double CR to single CR, others are converted into one
637 // blank. Then insertStringAsLines is called
638 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
640 string linestr = str;
641 bool newline_inserted = false;
643 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
644 if (linestr[i] == '\n') {
645 if (newline_inserted) {
646 // we know that \r will be ignored by
647 // insertStringAsLines. Of course, it is a dirty
648 // trick, but it works...
649 linestr[i - 1] = '\r';
653 newline_inserted = true;
655 } else if (IsPrintable(linestr[i])) {
656 newline_inserted = false;
659 insertStringAsLines(cur, linestr);
663 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
664 bool setfont, bool boundary)
667 setCursorIntern(cur, par, pos, setfont, boundary);
668 return deleteEmptyParagraphMechanism(cur, old);
672 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
674 BOOST_ASSERT(par != int(paragraphs().size()));
678 // now some strict checking
679 Paragraph & para = getPar(par);
681 // None of these should happen, but we're scaredy-cats
683 lyxerr << "dont like -1" << endl;
687 if (pos > para.size()) {
688 lyxerr << "dont like 1, pos: " << pos
689 << " size: " << para.size()
690 << " par: " << par << endl;
696 void LyXText::setCursorIntern(LCursor & cur,
697 pit_type par, pos_type pos, bool setfont, bool boundary)
699 cur.boundary(boundary);
700 setCursor(cur.top(), par, pos);
707 void LyXText::setCurrentFont(LCursor & cur)
709 BOOST_ASSERT(this == cur.text());
710 pos_type pos = cur.pos();
711 Paragraph & par = cur.paragraph();
713 if (cur.boundary() && pos > 0)
717 if (pos == cur.lastpos())
719 else // potentional bug... BUG (Lgb)
720 if (par.isSeparator(pos)) {
721 if (pos > cur.textRow().pos() &&
722 bidi.level(pos) % 2 ==
723 bidi.level(pos - 1) % 2)
725 else if (pos + 1 < cur.lastpos())
730 BufferParams const & bufparams = cur.buffer().params();
731 current_font = par.getFontSettings(bufparams, pos);
732 real_current_font = getFont(par, pos);
734 if (cur.pos() == cur.lastpos()
735 && bidi.isBoundary(cur.buffer(), par, cur.pos())
736 && !cur.boundary()) {
737 Language const * lang = par.getParLanguage(bufparams);
738 current_font.setLanguage(lang);
739 current_font.setNumber(LyXFont::OFF);
740 real_current_font.setLanguage(lang);
741 real_current_font.setNumber(LyXFont::OFF);
746 // x is an absolute screen coord
747 // returns the column near the specified x-coordinate of the row
748 // x is set to the real beginning of this column
749 pos_type LyXText::getColumnNearX(pit_type const pit,
750 Row const & row, int & x, bool & boundary) const
752 int const xo = theCoords.get(this, pit).x_;
754 RowMetrics const r = computeRowMetrics(pit, row);
755 Paragraph const & par = pars_[pit];
757 pos_type vc = row.pos();
758 pos_type end = row.endpos();
760 LyXLayout_ptr const & layout = par.layout();
762 bool left_side = false;
764 pos_type body_pos = par.beginOfBody();
767 double last_tmpx = tmpx;
770 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
773 // check for empty row
779 while (vc < end && tmpx <= x) {
780 c = bidi.vis2log(vc);
782 if (body_pos > 0 && c == body_pos - 1) {
783 tmpx += r.label_hfill +
784 font_metrics::width(layout->labelsep, getLabelFont(par));
785 if (par.isLineSeparator(body_pos - 1))
786 tmpx -= singleWidth(par, body_pos - 1);
789 if (hfillExpansion(par, row, c)) {
790 tmpx += singleWidth(par, c);
794 tmpx += r.label_hfill;
795 } else if (par.isSeparator(c)) {
796 tmpx += singleWidth(par, c);
800 tmpx += singleWidth(par, c);
805 if ((tmpx + last_tmpx) / 2 > x) {
810 BOOST_ASSERT(vc <= end); // This shouldn't happen.
813 // This (rtl_support test) is not needed, but gives
814 // some speedup if rtl_support == false
815 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
817 // If lastrow is false, we don't need to compute
819 bool const rtl = lastrow ? isRTL(par) : false;
821 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
822 (!rtl && !left_side && vc == end && x > tmpx + 5)))
824 else if (vc == row.pos()) {
825 c = bidi.vis2log(vc);
826 if (bidi.level(c) % 2 == 1)
829 c = bidi.vis2log(vc - 1);
830 bool const rtl = (bidi.level(c) % 2 == 1);
831 if (left_side == rtl) {
833 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
837 // I believe this code is not needed anymore (Jug 20050717)
839 // The following code is necessary because the cursor position past
840 // the last char in a row is logically equivalent to that before
841 // the first char in the next row. That's why insets causing row
842 // divisions -- Newline and display-style insets -- must be treated
843 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
844 // Newline inset, air gap below:
845 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
846 if (bidi.level(end -1) % 2 == 0)
847 tmpx -= singleWidth(par, end - 1);
849 tmpx += singleWidth(par, end - 1);
853 // Air gap above display inset:
854 if (row.pos() < end && c >= end && end < par.size()
855 && par.isInset(end) && par.getInset(end)->display()) {
858 // Air gap below display inset:
859 if (row.pos() < end && c >= end && par.isInset(end - 1)
860 && par.getInset(end - 1)->display()) {
866 pos_type const col = c - row.pos();
868 if (!c || end == par.size())
871 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
876 return min(col, end - 1 - row.pos());
880 // y is screen coordinate
881 pit_type LyXText::getPitNearY(int y) const
883 BOOST_ASSERT(!paragraphs().empty());
884 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
885 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
887 << BOOST_CURRENT_FUNCTION
888 << ": y: " << y << " cache size: " << cc.size()
891 // look for highest numbered paragraph with y coordinate less than given y
894 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
895 CoordCache::InnerParPosCache::const_iterator et = cc.end();
896 for (; it != et; ++it) {
898 << BOOST_CURRENT_FUNCTION
899 << " examining: pit: " << it->first
900 << " y: " << it->second.y_
903 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
910 << BOOST_CURRENT_FUNCTION
911 << ": found best y: " << yy << " for pit: " << pit
918 Row const & LyXText::getRowNearY(int y, pit_type pit) const
920 Paragraph const & par = pars_[pit];
921 int yy = theCoords.get(this, pit).y_ - par.ascent();
922 BOOST_ASSERT(!par.rows().empty());
923 RowList::const_iterator rit = par.rows().begin();
924 RowList::const_iterator const rlast = boost::prior(par.rows().end());
925 for (; rit != rlast; yy += rit->height(), ++rit)
926 if (yy + rit->height() > y)
932 // x,y are absolute screen coordinates
933 // sets cursor recursively descending into nested editable insets
934 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
936 pit_type pit = getPitNearY(y);
937 BOOST_ASSERT(pit != -1);
938 Row const & row = getRowNearY(y, pit);
941 int xx = x; // is modified by getColumnNearX
942 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
948 // try to descend into nested insets
949 InsetBase * inset = checkInsetHit(x, y);
950 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
952 // Either we deconst editXY or better we move current_font
953 // and real_current_font to LCursor
958 // This should be just before or just behind the
959 // cursor position set above.
960 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
961 || inset == pars_[pit].getInset(pos));
962 // Make sure the cursor points to the position before
964 if (inset == pars_[pit].getInset(pos - 1))
966 inset = inset->editXY(cur, x, y);
967 if (cur.top().text() == this)
973 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
977 if (cur.pos() == cur.lastpos())
979 InsetBase * inset = cur.nextInset();
980 if (!isHighlyEditableInset(inset))
982 inset->edit(cur, front);
987 bool LyXText::cursorLeft(LCursor & cur)
989 if (!cur.boundary() && cur.pos() > 0 &&
990 cur.textRow().pos() == cur.pos() &&
991 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
992 !cur.paragraph().isNewline(cur.pos()-1)) {
993 return setCursor(cur, cur.pit(), cur.pos(), true, true);
995 if (cur.pos() != 0) {
996 bool boundary = cur.boundary();
997 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
998 if (!checkAndActivateInset(cur, false)) {
999 if (false && !boundary &&
1000 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1002 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1004 return updateNeeded;
1007 if (cur.pit() != 0) {
1008 // Steps into the paragraph above
1009 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1015 bool LyXText::cursorRight(LCursor & cur)
1017 if (cur.pos() != cur.lastpos()) {
1019 return setCursor(cur, cur.pit(), cur.pos(),
1022 bool updateNeeded = false;
1023 if (!checkAndActivateInset(cur, true)) {
1024 if (cur.textRow().endpos() == cur.pos() + 1 &&
1025 cur.textRow().endpos() != cur.lastpos() &&
1026 !cur.paragraph().isLineSeparator(cur.pos()) &&
1027 !cur.paragraph().isNewline(cur.pos())) {
1030 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1031 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1033 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1035 return updateNeeded;
1038 if (cur.pit() != cur.lastpit())
1039 return setCursor(cur, cur.pit() + 1, 0);
1044 bool LyXText::cursorUp(LCursor & cur)
1046 Paragraph const & par = cur.paragraph();
1048 int const x = cur.targetX();
1050 if (cur.pos() && cur.boundary())
1051 row = par.pos2row(cur.pos()-1);
1053 row = par.pos2row(cur.pos());
1055 if (!cur.selection()) {
1056 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1058 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1059 cur.clearSelection();
1061 // This happens when you move out of an inset.
1062 // And to give the DEPM the possibility of doing
1063 // something we must provide it with two different
1065 LCursor dummy = cur;
1069 return deleteEmptyParagraphMechanism(dummy, old);
1072 bool updateNeeded = false;
1075 updateNeeded |= setCursor(cur, cur.pit(),
1076 x2pos(cur.pit(), row - 1, x));
1077 } else if (cur.pit() > 0) {
1079 //cannot use 'par' now
1080 updateNeeded |= setCursor(cur, cur.pit(),
1081 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1086 return updateNeeded;
1090 bool LyXText::cursorDown(LCursor & cur)
1092 Paragraph const & par = cur.paragraph();
1094 int const x = cur.targetX();
1096 if (cur.pos() && cur.boundary())
1097 row = par.pos2row(cur.pos()-1);
1099 row = par.pos2row(cur.pos());
1101 if (!cur.selection()) {
1102 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1104 editXY(cur, x, y + par.rows()[row].descent() + 1);
1105 cur.clearSelection();
1107 // This happens when you move out of an inset.
1108 // And to give the DEPM the possibility of doing
1109 // something we must provide it with two different
1111 LCursor dummy = cur;
1115 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1117 // Make sure that cur gets back whatever happened to dummy(Lgb)
1124 bool updateNeeded = false;
1126 if (row + 1 < int(par.rows().size())) {
1127 updateNeeded |= setCursor(cur, cur.pit(),
1128 x2pos(cur.pit(), row + 1, x));
1129 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1131 updateNeeded |= setCursor(cur, cur.pit(),
1132 x2pos(cur.pit(), 0, x));
1137 return updateNeeded;
1141 bool LyXText::cursorUpParagraph(LCursor & cur)
1143 bool updated = false;
1145 updated = setCursor(cur, cur.pit(), 0);
1146 else if (cur.pit() != 0)
1147 updated = setCursor(cur, cur.pit() - 1, 0);
1152 bool LyXText::cursorDownParagraph(LCursor & cur)
1154 bool updated = false;
1155 if (cur.pit() != cur.lastpit())
1156 updated = setCursor(cur, cur.pit() + 1, 0);
1158 updated = setCursor(cur, cur.pit(), cur.lastpos());
1163 // fix the cursor `cur' after a characters has been deleted at `where'
1164 // position. Called by deleteEmptyParagraphMechanism
1165 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1167 // Do nothing if cursor is not in the paragraph where the
1168 // deletion occured,
1169 if (cur.pit() != where.pit())
1172 // If cursor position is after the deletion place update it
1173 if (cur.pos() > where.pos())
1176 // Check also if we don't want to set the cursor on a spot behind the
1177 // pagragraph because we erased the last character.
1178 if (cur.pos() > cur.lastpos())
1179 cur.pos() = cur.lastpos();
1183 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1185 // Would be wrong to delete anything if we have a selection.
1186 if (cur.selection())
1189 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1190 // old should point to us
1191 BOOST_ASSERT(old.text() == this);
1193 Paragraph & oldpar = old.paragraph();
1195 // We allow all kinds of "mumbo-jumbo" when freespacing.
1196 if (oldpar.isFreeSpacing())
1199 /* Ok I'll put some comments here about what is missing.
1200 There are still some small problems that can lead to
1201 double spaces stored in the document file or space at
1202 the beginning of paragraphs(). This happens if you have
1203 the cursor between to spaces and then save. Or if you
1204 cut and paste and the selection have a space at the
1205 beginning and then save right after the paste. (Lgb)
1208 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1209 // delete the LineSeparator.
1212 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1213 // delete the LineSeparator.
1216 bool const same_inset = &old.inset() == &cur.inset();
1217 bool const same_par = same_inset && old.pit() == cur.pit();
1218 bool const same_par_pos = same_par && old.pos() == cur.pos();
1220 // If the chars around the old cursor were spaces, delete one of them.
1221 if (!same_par_pos) {
1222 // Only if the cursor has really moved.
1224 && old.pos() < oldpar.size()
1225 && oldpar.isLineSeparator(old.pos())
1226 && oldpar.isLineSeparator(old.pos() - 1)
1227 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1228 // We need to set the text to Change::INSERTED to
1229 // get it erased properly
1230 oldpar.setChange(old.pos() -1, Change::INSERTED);
1231 oldpar.erase(old.pos() - 1);
1232 #ifdef WITH_WARNINGS
1233 #warning This will not work anymore when we have multiple views of the same buffer
1234 // In this case, we will have to correct also the cursors held by
1235 // other bufferviews. It will probably be easier to do that in a more
1236 // automated way in CursorSlice code. (JMarc 26/09/2001)
1238 // correct all cursor parts
1240 fixCursorAfterDelete(cur.top(), old.top());
1247 // only do our magic if we changed paragraph
1251 // don't delete anything if this is the ONLY paragraph!
1252 if (old.lastpit() == 0)
1255 // Do not delete empty paragraphs with keepempty set.
1256 if (oldpar.allowEmpty())
1259 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1261 recordUndo(old, Undo::ATOMIC, old.pit());
1262 ParagraphList & plist = old.text()->paragraphs();
1263 plist.erase(plist.begin() + old.pit());
1265 // see #warning above
1266 if (cur.depth() >= old.depth()) {
1267 CursorSlice & curslice = cur[old.depth() - 1];
1268 if (&curslice.inset() == &old.inset()
1269 && curslice.pit() > old.pit()) {
1271 // since a paragraph has been deleted, all the
1272 // insets after `old' have been copied and
1273 // their address has changed. Therefore we
1274 // need to `regenerate' cur. (JMarc)
1275 cur.updateInsets(&(cur.bottom().inset()));
1279 updateCounters(old.buffer());
1283 if (oldpar.stripLeadingSpaces())
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);