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;
70 LyXText::LyXText(BufferView * bv)
71 : maxwidth_(bv ? bv->workWidth() : 100),
72 current_font(LyXFont::ALL_INHERIT),
73 background_color_(LColor::background),
79 void LyXText::init(BufferView * bv)
83 maxwidth_ = bv->workWidth();
88 pit_type const end = paragraphs().size();
89 for (pit_type pit = 0; pit != end; ++pit)
90 pars_[pit].rows().clear();
92 current_font = getFont(pars_[0], 0);
93 updateLabels(*bv->buffer());
97 bool LyXText::isMainText() const
99 return &bv()->buffer()->text() == this;
103 //takes screen x,y coordinates
104 InsetBase * LyXText::checkInsetHit(int x, int y) const
106 pit_type pit = getPitNearY(y);
107 BOOST_ASSERT(pit != -1);
109 Paragraph const & par = pars_[pit];
112 << BOOST_CURRENT_FUNCTION
117 InsetList::const_iterator iit = par.insetlist.begin();
118 InsetList::const_iterator iend = par.insetlist.end();
119 for (; iit != iend; ++iit) {
120 InsetBase * inset = iit->inset;
123 << BOOST_CURRENT_FUNCTION
124 << ": examining inset " << inset << endl;
126 if (theCoords.getInsets().has(inset))
128 << BOOST_CURRENT_FUNCTION
129 << ": xo: " << inset->xo() << "..."
130 << inset->xo() + inset->width()
131 << " yo: " << inset->yo() - inset->ascent()
133 << inset->yo() + inset->descent()
137 << BOOST_CURRENT_FUNCTION
138 << ": inset has no cached position" << endl;
140 if (inset->covers(x, y)) {
142 << BOOST_CURRENT_FUNCTION
143 << ": Hit inset: " << inset << endl;
148 << BOOST_CURRENT_FUNCTION
149 << ": No inset hit. " << endl;
155 // Gets the fully instantiated font at a given position in a paragraph
156 // Basically the same routine as Paragraph::getFont() in paragraph.C.
157 // The difference is that this one is used for displaying, and thus we
158 // are allowed to make cosmetic improvements. For instance make footnotes
160 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
162 BOOST_ASSERT(pos >= 0);
164 LyXLayout_ptr const & layout = par.layout();
168 BufferParams const & params = bv()->buffer()->params();
169 pos_type const body_pos = par.beginOfBody();
171 // We specialize the 95% common case:
172 if (!par.getDepth()) {
173 LyXFont f = par.getFontSettings(params, pos);
178 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
179 lf = layout->labelfont;
180 rlf = layout->reslabelfont;
183 rlf = layout->resfont;
185 // In case the default family has been customized
186 if (lf.family() == LyXFont::INHERIT_FAMILY)
187 rlf.setFamily(params.getFont().family());
188 return f.realize(rlf);
191 // The uncommon case need not be optimized as much
194 layoutfont = layout->labelfont;
196 layoutfont = layout->font;
198 LyXFont font = par.getFontSettings(params, pos);
199 font.realize(layoutfont);
202 applyOuterFont(font);
204 // Find the pit value belonging to paragraph. This will not break
205 // even if pars_ would not be a vector anymore.
206 // Performance appears acceptable.
208 pit_type pit = pars_.size();
209 for (pit_type it = 0; it < pit; ++it)
210 if (&pars_[it] == &par) {
214 // Realize against environment font information
215 if (pit < pars_.size())
216 font.realize(outerFont(pit, pars_));
218 // Realize with the fonts of lesser depth.
219 font.realize(params.getFont());
224 // There are currently two font mechanisms in LyX:
225 // 1. The font attributes in a lyxtext, and
226 // 2. The inset-specific font properties, defined in an inset's
227 // metrics() and draw() methods and handed down the inset chain through
228 // the pi/mi parameters, and stored locally in a lyxtext in font_.
229 // This is where the two are integrated in the final fully realized
231 void LyXText::applyOuterFont(LyXFont & font) const {
233 lf.reduce(bv()->buffer()->params().getFont());
235 lf.setLanguage(font.language());
240 LyXFont LyXText::getLayoutFont(pit_type const pit) const
242 LyXLayout_ptr const & layout = pars_[pit].layout();
244 if (!pars_[pit].getDepth()) {
245 LyXFont lf = layout->resfont;
246 // In case the default family has been customized
247 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
248 lf.setFamily(bv()->buffer()->params().getFont().family());
252 LyXFont font = layout->font;
253 // Realize with the fonts of lesser depth.
254 //font.realize(outerFont(pit, paragraphs()));
255 font.realize(bv()->buffer()->params().getFont());
261 LyXFont LyXText::getLabelFont(Paragraph const & par) const
263 LyXLayout_ptr const & layout = par.layout();
265 if (!par.getDepth()) {
266 LyXFont lf = layout->reslabelfont;
267 // In case the default family has been customized
268 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
269 lf.setFamily(bv()->buffer()->params().getFont().family());
273 LyXFont font = layout->labelfont;
274 // Realize with the fonts of lesser depth.
275 font.realize(bv()->buffer()->params().getFont());
281 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
284 LyXLayout_ptr const & layout = pars_[pit].layout();
286 // Get concrete layout font to reduce against
289 if (pos < pars_[pit].beginOfBody())
290 layoutfont = layout->labelfont;
292 layoutfont = layout->font;
294 // Realize against environment font information
295 if (pars_[pit].getDepth()) {
297 while (!layoutfont.resolved() &&
298 tp != pit_type(paragraphs().size()) &&
299 pars_[tp].getDepth()) {
300 tp = outerHook(tp, paragraphs());
301 if (tp != pit_type(paragraphs().size()))
302 layoutfont.realize(pars_[tp].layout()->font);
306 // Inside inset, apply the inset's font attributes if any
309 layoutfont.realize(font_);
311 layoutfont.realize(bv()->buffer()->params().getFont());
313 // Now, reduce font against full layout font
314 font.reduce(layoutfont);
316 pars_[pit].setFont(pos, font);
320 // return past-the-last paragraph influenced by a layout change on pit
321 pit_type LyXText::undoSpan(pit_type pit)
323 pit_type end = paragraphs().size();
324 pit_type nextpit = pit + 1;
327 //because of parindents
328 if (!pars_[pit].getDepth())
329 return boost::next(nextpit);
330 //because of depth constrains
331 for (; nextpit != end; ++pit, ++nextpit) {
332 if (!pars_[pit].getDepth())
339 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
341 BOOST_ASSERT(start != end);
343 BufferParams const & bufparams = bv()->buffer()->params();
344 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
346 for (pit_type pit = start; pit != end; ++pit) {
347 pars_[pit].applyLayout(lyxlayout);
348 if (lyxlayout->margintype == MARGIN_MANUAL)
349 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
354 // set layout over selection and make a total rebreak of those paragraphs
355 void LyXText::setLayout(LCursor & cur, string const & layout)
357 BOOST_ASSERT(this == cur.text());
358 // special handling of new environment insets
359 BufferView & bv = cur.bv();
360 BufferParams const & params = bv.buffer()->params();
361 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
362 if (lyxlayout->is_environment) {
363 // move everything in a new environment inset
364 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
365 bv.owner()->dispatch(FuncRequest(LFUN_LINE_BEGIN));
366 bv.owner()->dispatch(FuncRequest(LFUN_LINE_END_SELECT));
367 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
368 InsetBase * inset = new InsetEnvironment(params, layout);
369 insertInset(cur, inset);
370 //inset->edit(cur, true);
371 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
375 pit_type start = cur.selBegin().pit();
376 pit_type end = cur.selEnd().pit() + 1;
377 pit_type undopit = undoSpan(end - 1);
378 recUndo(start, undopit - 1);
379 setLayout(start, end, layout);
380 updateLabels(cur.buffer());
387 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
388 Paragraph const & par, int max_depth)
390 if (par.layout()->labeltype == LABEL_BIBLIO)
392 int const depth = par.params().depth();
393 if (type == LyXText::INC_DEPTH && depth < max_depth)
395 if (type == LyXText::DEC_DEPTH && depth > 0)
404 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
406 BOOST_ASSERT(this == cur.text());
407 pit_type const beg = cur.selBegin().pit();
408 pit_type const end = cur.selEnd().pit() + 1;
409 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
411 for (pit_type pit = beg; pit != end; ++pit) {
412 if (::changeDepthAllowed(type, pars_[pit], max_depth))
414 max_depth = pars_[pit].getMaxDepthAfter();
420 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
422 BOOST_ASSERT(this == cur.text());
423 pit_type const beg = cur.selBegin().pit();
424 pit_type const end = cur.selEnd().pit() + 1;
425 recordUndoSelection(cur);
426 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
428 for (pit_type pit = beg; pit != end; ++pit) {
429 Paragraph & par = pars_[pit];
430 if (::changeDepthAllowed(type, par, max_depth)) {
431 int const depth = par.params().depth();
432 if (type == INC_DEPTH)
433 par.params().depth(depth + 1);
435 par.params().depth(depth - 1);
437 max_depth = par.getMaxDepthAfter();
439 // this handles the counter labels, and also fixes up
440 // depth values for follow-on (child) paragraphs
441 updateLabels(cur.buffer());
445 // set font over selection
446 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
448 BOOST_ASSERT(this == cur.text());
449 // if there is no selection just set the current_font
450 if (!cur.selection()) {
451 // Determine basis font
453 pit_type pit = cur.pit();
454 if (cur.pos() < pars_[pit].beginOfBody())
455 layoutfont = getLabelFont(pars_[pit]);
457 layoutfont = getLayoutFont(pit);
459 // Update current font
460 real_current_font.update(font,
461 cur.buffer().params().language,
464 // Reduce to implicit settings
465 current_font = real_current_font;
466 current_font.reduce(layoutfont);
467 // And resolve it completely
468 real_current_font.realize(layoutfont);
473 // Ok, we have a selection.
474 recordUndoSelection(cur);
476 DocIterator dit = cur.selectionBegin();
477 DocIterator ditend = cur.selectionEnd();
479 BufferParams const & params = cur.buffer().params();
481 // Don't use forwardChar here as ditend might have
482 // pos() == lastpos() and forwardChar would miss it.
483 // Can't use forwardPos either as this descends into
485 for (; dit != ditend; dit.forwardPosNoDescend()) {
486 if (dit.pos() != dit.lastpos()) {
487 LyXFont f = getFont(dit.paragraph(), dit.pos());
488 f.update(font, params.language, toggleall);
489 setCharFont(dit.pit(), dit.pos(), f);
495 // the cursor set functions have a special mechanism. When they
496 // realize you left an empty paragraph, they will delete it.
498 bool LyXText::cursorHome(LCursor & cur)
500 BOOST_ASSERT(this == cur.text());
501 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
503 return setCursor(cur, cur.pit(), row.pos());
507 bool LyXText::cursorEnd(LCursor & cur)
509 BOOST_ASSERT(this == cur.text());
510 // if not on the last row of the par, put the cursor before
511 // the final space exept if I have a spanning inset or one string
512 // is so long that we force a break.
513 pos_type end = cur.textRow().endpos();
515 // empty text, end-1 is no valid position
517 bool boundary = false;
518 if (end != cur.lastpos()) {
519 if (!cur.paragraph().isLineSeparator(end-1)
520 && !cur.paragraph().isNewline(end-1))
525 return setCursor(cur, cur.pit(), end, true, boundary);
529 bool LyXText::cursorTop(LCursor & cur)
531 BOOST_ASSERT(this == cur.text());
532 return setCursor(cur, 0, 0);
536 bool LyXText::cursorBottom(LCursor & cur)
538 BOOST_ASSERT(this == cur.text());
539 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
543 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
545 BOOST_ASSERT(this == cur.text());
546 // If the mask is completely neutral, tell user
547 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
548 // Could only happen with user style
549 cur.message(_("No font change defined. "
550 "Use Character under the Layout menu to define font change."));
554 // Try implicit word selection
555 // If there is a change in the language the implicit word selection
557 CursorSlice resetCursor = cur.top();
558 bool implicitSelection =
559 font.language() == ignore_language
560 && font.number() == LyXFont::IGNORE
561 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
564 setFont(cur, font, toggleall);
566 // Implicit selections are cleared afterwards
567 // and cursor is set to the original position.
568 if (implicitSelection) {
569 cur.clearSelection();
570 cur.top() = resetCursor;
576 string LyXText::getStringToIndex(LCursor const & cur)
578 BOOST_ASSERT(this == cur.text());
581 if (cur.selection()) {
582 idxstring = cur.selectionAsString(false);
584 // Try implicit word selection. If there is a change
585 // in the language the implicit word selection is
587 LCursor tmpcur = cur;
588 selectWord(tmpcur, lyx::PREVIOUS_WORD);
590 if (!tmpcur.selection())
591 cur.message(_("Nothing to index!"));
592 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
593 cur.message(_("Cannot index more than one paragraph!"));
595 idxstring = tmpcur.selectionAsString(false);
602 void LyXText::setParagraph(LCursor & cur,
603 Spacing const & spacing, LyXAlignment align,
604 string const & labelwidthstring, bool noindent)
606 BOOST_ASSERT(cur.text());
607 // make sure that the depth behind the selection are restored, too
608 pit_type undopit = undoSpan(cur.selEnd().pit());
609 recUndo(cur.selBegin().pit(), undopit - 1);
611 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
613 Paragraph & par = pars_[pit];
614 ParagraphParameters & params = par.params();
615 params.spacing(spacing);
617 // does the layout allow the new alignment?
618 LyXLayout_ptr const & layout = par.layout();
620 if (align == LYX_ALIGN_LAYOUT)
621 align = layout->align;
622 if (align & layout->alignpossible) {
623 if (align == layout->align)
624 params.align(LYX_ALIGN_LAYOUT);
628 par.setLabelWidthString(labelwidthstring);
629 params.noindent(noindent);
634 // this really should just insert the inset and not move the cursor.
635 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
637 BOOST_ASSERT(this == cur.text());
639 cur.paragraph().insertInset(cur.pos(), inset);
643 // needed to insert the selection
644 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
646 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
647 current_font, str, autoBreakRows_);
651 // turn double CR to single CR, others are converted into one
652 // blank. Then insertStringAsLines is called
653 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
655 string linestr = str;
656 bool newline_inserted = false;
658 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
659 if (linestr[i] == '\n') {
660 if (newline_inserted) {
661 // we know that \r will be ignored by
662 // insertStringAsLines. Of course, it is a dirty
663 // trick, but it works...
664 linestr[i - 1] = '\r';
668 newline_inserted = true;
670 } else if (isPrintable(linestr[i])) {
671 newline_inserted = false;
674 insertStringAsLines(cur, linestr);
678 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
679 bool setfont, bool boundary)
682 setCursorIntern(cur, par, pos, setfont, boundary);
683 return deleteEmptyParagraphMechanism(cur, old);
687 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
689 BOOST_ASSERT(par != int(paragraphs().size()));
693 // now some strict checking
694 Paragraph & para = getPar(par);
696 // None of these should happen, but we're scaredy-cats
698 lyxerr << "dont like -1" << endl;
702 if (pos > para.size()) {
703 lyxerr << "dont like 1, pos: " << pos
704 << " size: " << para.size()
705 << " par: " << par << endl;
711 void LyXText::setCursorIntern(LCursor & cur,
712 pit_type par, pos_type pos, bool setfont, bool boundary)
714 cur.boundary(boundary);
715 setCursor(cur.top(), par, pos);
722 void LyXText::setCurrentFont(LCursor & cur)
724 BOOST_ASSERT(this == cur.text());
725 pos_type pos = cur.pos();
726 Paragraph & par = cur.paragraph();
728 if (cur.boundary() && pos > 0)
732 if (pos == cur.lastpos())
734 else // potentional bug... BUG (Lgb)
735 if (par.isSeparator(pos)) {
736 if (pos > cur.textRow().pos() &&
737 bidi.level(pos) % 2 ==
738 bidi.level(pos - 1) % 2)
740 else if (pos + 1 < cur.lastpos())
745 BufferParams const & bufparams = cur.buffer().params();
746 current_font = par.getFontSettings(bufparams, pos);
747 real_current_font = getFont(par, pos);
749 if (cur.pos() == cur.lastpos()
750 && bidi.isBoundary(cur.buffer(), par, cur.pos())
751 && !cur.boundary()) {
752 Language const * lang = par.getParLanguage(bufparams);
753 current_font.setLanguage(lang);
754 current_font.setNumber(LyXFont::OFF);
755 real_current_font.setLanguage(lang);
756 real_current_font.setNumber(LyXFont::OFF);
761 // x is an absolute screen coord
762 // returns the column near the specified x-coordinate of the row
763 // x is set to the real beginning of this column
764 pos_type LyXText::getColumnNearX(pit_type const pit,
765 Row const & row, int & x, bool & boundary) const
767 int const xo = theCoords.get(this, pit).x_;
769 RowMetrics const r = computeRowMetrics(pit, row);
770 Paragraph const & par = pars_[pit];
772 pos_type vc = row.pos();
773 pos_type end = row.endpos();
775 LyXLayout_ptr const & layout = par.layout();
777 bool left_side = false;
779 pos_type body_pos = par.beginOfBody();
782 double last_tmpx = tmpx;
785 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
788 // check for empty row
794 while (vc < end && tmpx <= x) {
795 c = bidi.vis2log(vc);
797 if (body_pos > 0 && c == body_pos - 1) {
798 tmpx += r.label_hfill +
799 font_metrics::width(layout->labelsep, getLabelFont(par));
800 if (par.isLineSeparator(body_pos - 1))
801 tmpx -= singleWidth(par, body_pos - 1);
804 if (hfillExpansion(par, row, c)) {
805 tmpx += singleWidth(par, c);
809 tmpx += r.label_hfill;
810 } else if (par.isSeparator(c)) {
811 tmpx += singleWidth(par, c);
815 tmpx += singleWidth(par, c);
820 if ((tmpx + last_tmpx) / 2 > x) {
825 BOOST_ASSERT(vc <= end); // This shouldn't happen.
828 // This (rtl_support test) is not needed, but gives
829 // some speedup if rtl_support == false
830 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
832 // If lastrow is false, we don't need to compute
834 bool const rtl = lastrow ? isRTL(par) : false;
836 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
837 (!rtl && !left_side && vc == end && x > tmpx + 5)))
839 else if (vc == row.pos()) {
840 c = bidi.vis2log(vc);
841 if (bidi.level(c) % 2 == 1)
844 c = bidi.vis2log(vc - 1);
845 bool const rtl = (bidi.level(c) % 2 == 1);
846 if (left_side == rtl) {
848 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
852 // I believe this code is not needed anymore (Jug 20050717)
854 // The following code is necessary because the cursor position past
855 // the last char in a row is logically equivalent to that before
856 // the first char in the next row. That's why insets causing row
857 // divisions -- Newline and display-style insets -- must be treated
858 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
859 // Newline inset, air gap below:
860 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
861 if (bidi.level(end -1) % 2 == 0)
862 tmpx -= singleWidth(par, end - 1);
864 tmpx += singleWidth(par, end - 1);
868 // Air gap above display inset:
869 if (row.pos() < end && c >= end && end < par.size()
870 && par.isInset(end) && par.getInset(end)->display()) {
873 // Air gap below display inset:
874 if (row.pos() < end && c >= end && par.isInset(end - 1)
875 && par.getInset(end - 1)->display()) {
881 pos_type const col = c - row.pos();
883 if (!c || end == par.size())
886 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
891 return min(col, end - 1 - row.pos());
895 // y is screen coordinate
896 pit_type LyXText::getPitNearY(int y) const
898 BOOST_ASSERT(!paragraphs().empty());
899 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
900 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
902 << BOOST_CURRENT_FUNCTION
903 << ": y: " << y << " cache size: " << cc.size()
906 // look for highest numbered paragraph with y coordinate less than given y
909 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
910 CoordCache::InnerParPosCache::const_iterator et = cc.end();
911 for (; it != et; ++it) {
913 << BOOST_CURRENT_FUNCTION
914 << " examining: pit: " << it->first
915 << " y: " << it->second.y_
918 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
925 << BOOST_CURRENT_FUNCTION
926 << ": found best y: " << yy << " for pit: " << pit
933 Row const & LyXText::getRowNearY(int y, pit_type pit) const
935 Paragraph const & par = pars_[pit];
936 int yy = theCoords.get(this, pit).y_ - par.ascent();
937 BOOST_ASSERT(!par.rows().empty());
938 RowList::const_iterator rit = par.rows().begin();
939 RowList::const_iterator const rlast = boost::prior(par.rows().end());
940 for (; rit != rlast; yy += rit->height(), ++rit)
941 if (yy + rit->height() > y)
947 // x,y are absolute screen coordinates
948 // sets cursor recursively descending into nested editable insets
949 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
951 pit_type pit = getPitNearY(y);
952 BOOST_ASSERT(pit != -1);
953 Row const & row = getRowNearY(y, pit);
956 int xx = x; // is modified by getColumnNearX
957 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
963 // try to descend into nested insets
964 InsetBase * inset = checkInsetHit(x, y);
965 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
967 // Either we deconst editXY or better we move current_font
968 // and real_current_font to LCursor
973 // This should be just before or just behind the
974 // cursor position set above.
975 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
976 || inset == pars_[pit].getInset(pos));
977 // Make sure the cursor points to the position before
979 if (inset == pars_[pit].getInset(pos - 1))
981 inset = inset->editXY(cur, x, y);
982 if (cur.top().text() == this)
988 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
992 if (cur.pos() == cur.lastpos())
994 InsetBase * inset = cur.nextInset();
995 if (!isHighlyEditableInset(inset))
997 inset->edit(cur, front);
1002 bool LyXText::cursorLeft(LCursor & cur)
1004 if (!cur.boundary() && cur.pos() > 0 &&
1005 cur.textRow().pos() == cur.pos() &&
1006 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1007 !cur.paragraph().isNewline(cur.pos()-1)) {
1008 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1010 if (cur.pos() != 0) {
1011 bool boundary = cur.boundary();
1012 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1013 if (!checkAndActivateInset(cur, false)) {
1014 if (false && !boundary &&
1015 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1017 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1019 return updateNeeded;
1022 if (cur.pit() != 0) {
1023 // Steps into the paragraph above
1024 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1030 bool LyXText::cursorRight(LCursor & cur)
1032 if (cur.pos() != cur.lastpos()) {
1034 return setCursor(cur, cur.pit(), cur.pos(),
1037 bool updateNeeded = false;
1038 if (!checkAndActivateInset(cur, true)) {
1039 if (cur.textRow().endpos() == cur.pos() + 1 &&
1040 cur.textRow().endpos() != cur.lastpos() &&
1041 !cur.paragraph().isLineSeparator(cur.pos()) &&
1042 !cur.paragraph().isNewline(cur.pos())) {
1045 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1046 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1048 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1050 return updateNeeded;
1053 if (cur.pit() != cur.lastpit())
1054 return setCursor(cur, cur.pit() + 1, 0);
1059 bool LyXText::cursorUp(LCursor & cur)
1061 Paragraph const & par = cur.paragraph();
1063 int const x = cur.targetX();
1065 if (cur.pos() && cur.boundary())
1066 row = par.pos2row(cur.pos()-1);
1068 row = par.pos2row(cur.pos());
1070 if (!cur.selection()) {
1071 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1073 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1074 cur.clearSelection();
1076 // This happens when you move out of an inset.
1077 // And to give the DEPM the possibility of doing
1078 // something we must provide it with two different
1080 LCursor dummy = cur;
1084 return deleteEmptyParagraphMechanism(dummy, old);
1087 bool updateNeeded = false;
1090 updateNeeded |= setCursor(cur, cur.pit(),
1091 x2pos(cur.pit(), row - 1, x));
1092 } else if (cur.pit() > 0) {
1094 //cannot use 'par' now
1095 updateNeeded |= setCursor(cur, cur.pit(),
1096 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1101 return updateNeeded;
1105 bool LyXText::cursorDown(LCursor & cur)
1107 Paragraph const & par = cur.paragraph();
1109 int const x = cur.targetX();
1111 if (cur.pos() && cur.boundary())
1112 row = par.pos2row(cur.pos()-1);
1114 row = par.pos2row(cur.pos());
1116 if (!cur.selection()) {
1117 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1119 editXY(cur, x, y + par.rows()[row].descent() + 1);
1120 cur.clearSelection();
1122 // This happens when you move out of an inset.
1123 // And to give the DEPM the possibility of doing
1124 // something we must provide it with two different
1126 LCursor dummy = cur;
1130 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1132 // Make sure that cur gets back whatever happened to dummy(Lgb)
1139 bool updateNeeded = false;
1141 if (row + 1 < int(par.rows().size())) {
1142 updateNeeded |= setCursor(cur, cur.pit(),
1143 x2pos(cur.pit(), row + 1, x));
1144 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1146 updateNeeded |= setCursor(cur, cur.pit(),
1147 x2pos(cur.pit(), 0, x));
1152 return updateNeeded;
1156 bool LyXText::cursorUpParagraph(LCursor & cur)
1158 bool updated = false;
1160 updated = setCursor(cur, cur.pit(), 0);
1161 else if (cur.pit() != 0)
1162 updated = setCursor(cur, cur.pit() - 1, 0);
1167 bool LyXText::cursorDownParagraph(LCursor & cur)
1169 bool updated = false;
1170 if (cur.pit() != cur.lastpit())
1171 updated = setCursor(cur, cur.pit() + 1, 0);
1173 updated = setCursor(cur, cur.pit(), cur.lastpos());
1178 // fix the cursor `cur' after a characters has been deleted at `where'
1179 // position. Called by deleteEmptyParagraphMechanism
1180 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1182 // Do nothing if cursor is not in the paragraph where the
1183 // deletion occured,
1184 if (cur.pit() != where.pit())
1187 // If cursor position is after the deletion place update it
1188 if (cur.pos() > where.pos())
1191 // Check also if we don't want to set the cursor on a spot behind the
1192 // pagragraph because we erased the last character.
1193 if (cur.pos() > cur.lastpos())
1194 cur.pos() = cur.lastpos();
1198 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1200 // Would be wrong to delete anything if we have a selection.
1201 if (cur.selection())
1204 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1205 // old should point to us
1206 BOOST_ASSERT(old.text() == this);
1208 Paragraph & oldpar = old.paragraph();
1210 // We allow all kinds of "mumbo-jumbo" when freespacing.
1211 if (oldpar.isFreeSpacing())
1214 /* Ok I'll put some comments here about what is missing.
1215 There are still some small problems that can lead to
1216 double spaces stored in the document file or space at
1217 the beginning of paragraphs(). This happens if you have
1218 the cursor between to spaces and then save. Or if you
1219 cut and paste and the selection have a space at the
1220 beginning and then save right after the paste. (Lgb)
1223 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1224 // delete the LineSeparator.
1227 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1228 // delete the LineSeparator.
1231 bool const same_inset = &old.inset() == &cur.inset();
1232 bool const same_par = same_inset && old.pit() == cur.pit();
1233 bool const same_par_pos = same_par && old.pos() == cur.pos();
1235 // If the chars around the old cursor were spaces, delete one of them.
1236 if (!same_par_pos) {
1237 // Only if the cursor has really moved.
1239 && old.pos() < oldpar.size()
1240 && oldpar.isLineSeparator(old.pos())
1241 && oldpar.isLineSeparator(old.pos() - 1)
1242 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1243 // We need to set the text to Change::INSERTED to
1244 // get it erased properly
1245 oldpar.setChange(old.pos() -1, Change::INSERTED);
1246 oldpar.erase(old.pos() - 1);
1247 #ifdef WITH_WARNINGS
1248 #warning This will not work anymore when we have multiple views of the same buffer
1249 // In this case, we will have to correct also the cursors held by
1250 // other bufferviews. It will probably be easier to do that in a more
1251 // automated way in CursorSlice code. (JMarc 26/09/2001)
1253 // correct all cursor parts
1255 fixCursorAfterDelete(cur.top(), old.top());
1262 // only do our magic if we changed paragraph
1266 // don't delete anything if this is the ONLY paragraph!
1267 if (old.lastpit() == 0)
1270 // Do not delete empty paragraphs with keepempty set.
1271 if (oldpar.allowEmpty())
1274 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1276 recordUndo(old, Undo::ATOMIC,
1277 max(old.pit() - 1, pit_type(0)),
1278 min(old.pit() + 1, old.lastpit()));
1279 ParagraphList & plist = old.text()->paragraphs();
1280 plist.erase(boost::next(plist.begin(), old.pit()));
1282 // see #warning above
1283 if (cur.depth() >= old.depth()) {
1284 CursorSlice & curslice = cur[old.depth() - 1];
1285 if (&curslice.inset() == &old.inset()
1286 && curslice.pit() > old.pit()) {
1288 // since a paragraph has been deleted, all the
1289 // insets after `old' have been copied and
1290 // their address has changed. Therefore we
1291 // need to `regenerate' cur. (JMarc)
1292 cur.updateInsets(&(cur.bottom().inset()));
1296 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1297 //ParIterator par_it(old);
1298 //updateLabels(old.buffer(), par_it);
1299 // So for now we do the full update:
1300 updateLabels(old.buffer());
1304 if (oldpar.stripLeadingSpaces())
1311 void LyXText::recUndo(pit_type first, pit_type last) const
1313 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1317 void LyXText::recUndo(pit_type par) const
1319 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1323 int defaultRowHeight()
1325 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);