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 "bufferlist.h"
27 #include "bufferparams.h"
28 #include "BufferView.h"
30 #include "coordcache.h"
32 #include "CutAndPaste.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
36 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "pariterator.h"
48 #include "lyxserver.h"
49 #include "lyxsocket.h"
53 #include "frontends/FontMetrics.h"
55 #include "insets/insetenv.h"
57 #include "mathed/InsetMathHull.h"
59 #include "support/textutils.h"
61 #include <boost/current_function.hpp>
65 using lyx::CoordCache;
71 using std::ostringstream;
77 LyXText::LyXText(BufferView * bv)
78 : maxwidth_(bv ? bv->workWidth() : 100),
79 current_font(LyXFont::ALL_INHERIT),
80 background_color_(LColor::background),
86 void LyXText::init(BufferView * bv)
90 maxwidth_ = bv->workWidth();
95 pit_type const end = paragraphs().size();
96 for (pit_type pit = 0; pit != end; ++pit)
97 pars_[pit].rows().clear();
99 updateLabels(*bv->buffer());
103 bool LyXText::isMainText() const
105 return &bv()->buffer()->text() == this;
109 //takes screen x,y coordinates
110 InsetBase * LyXText::checkInsetHit(int x, int y) const
112 pit_type pit = getPitNearY(y);
113 BOOST_ASSERT(pit != -1);
115 Paragraph const & par = pars_[pit];
118 << BOOST_CURRENT_FUNCTION
123 InsetList::const_iterator iit = par.insetlist.begin();
124 InsetList::const_iterator iend = par.insetlist.end();
125 for (; iit != iend; ++iit) {
126 InsetBase * inset = iit->inset;
129 << BOOST_CURRENT_FUNCTION
130 << ": examining inset " << inset << endl;
132 if (bv()->coordCache().getInsets().has(inset))
134 << BOOST_CURRENT_FUNCTION
135 << ": xo: " << inset->xo(*bv()) << "..."
136 << inset->xo(*bv()) + inset->width()
137 << " yo: " << inset->yo(*bv()) - inset->ascent()
139 << inset->yo(*bv()) + inset->descent()
143 << BOOST_CURRENT_FUNCTION
144 << ": inset has no cached position" << endl;
146 if (inset->covers(*bv(), x, y)) {
148 << BOOST_CURRENT_FUNCTION
149 << ": Hit inset: " << inset << endl;
154 << BOOST_CURRENT_FUNCTION
155 << ": No inset hit. " << endl;
161 // Gets the fully instantiated font at a given position in a paragraph
162 // Basically the same routine as Paragraph::getFont() in paragraph.C.
163 // The difference is that this one is used for displaying, and thus we
164 // are allowed to make cosmetic improvements. For instance make footnotes
166 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
168 BOOST_ASSERT(pos >= 0);
170 LyXLayout_ptr const & layout = par.layout();
174 BufferParams const & params = bv()->buffer()->params();
175 pos_type const body_pos = par.beginOfBody();
177 // We specialize the 95% common case:
178 if (!par.getDepth()) {
179 LyXFont f = par.getFontSettings(params, pos);
184 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
185 lf = layout->labelfont;
186 rlf = layout->reslabelfont;
189 rlf = layout->resfont;
191 // In case the default family has been customized
192 if (lf.family() == LyXFont::INHERIT_FAMILY)
193 rlf.setFamily(params.getFont().family());
194 return f.realize(rlf);
197 // The uncommon case need not be optimized as much
200 layoutfont = layout->labelfont;
202 layoutfont = layout->font;
204 LyXFont font = par.getFontSettings(params, pos);
205 font.realize(layoutfont);
208 applyOuterFont(font);
210 // Find the pit value belonging to paragraph. This will not break
211 // even if pars_ would not be a vector anymore.
212 // Performance appears acceptable.
214 pit_type pit = pars_.size();
215 for (pit_type it = 0; it < pit; ++it)
216 if (&pars_[it] == &par) {
220 // Realize against environment font information
221 // NOTE: the cast to pit_type should be removed when pit_type
222 // changes to a unsigned integer.
223 if (pit < pit_type(pars_.size()))
224 font.realize(outerFont(pit, pars_));
226 // Realize with the fonts of lesser depth.
227 font.realize(params.getFont());
232 // There are currently two font mechanisms in LyX:
233 // 1. The font attributes in a lyxtext, and
234 // 2. The inset-specific font properties, defined in an inset's
235 // metrics() and draw() methods and handed down the inset chain through
236 // the pi/mi parameters, and stored locally in a lyxtext in font_.
237 // This is where the two are integrated in the final fully realized
239 void LyXText::applyOuterFont(LyXFont & font) const {
241 lf.reduce(bv()->buffer()->params().getFont());
243 lf.setLanguage(font.language());
248 LyXFont LyXText::getLayoutFont(pit_type const pit) const
250 LyXLayout_ptr const & layout = pars_[pit].layout();
252 if (!pars_[pit].getDepth()) {
253 LyXFont lf = layout->resfont;
254 // In case the default family has been customized
255 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
256 lf.setFamily(bv()->buffer()->params().getFont().family());
260 LyXFont font = layout->font;
261 // Realize with the fonts of lesser depth.
262 //font.realize(outerFont(pit, paragraphs()));
263 font.realize(bv()->buffer()->params().getFont());
269 LyXFont LyXText::getLabelFont(Paragraph const & par) const
271 LyXLayout_ptr const & layout = par.layout();
273 if (!par.getDepth()) {
274 LyXFont lf = layout->reslabelfont;
275 // In case the default family has been customized
276 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
277 lf.setFamily(bv()->buffer()->params().getFont().family());
281 LyXFont font = layout->labelfont;
282 // Realize with the fonts of lesser depth.
283 font.realize(bv()->buffer()->params().getFont());
289 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
292 LyXLayout_ptr const & layout = pars_[pit].layout();
294 // Get concrete layout font to reduce against
297 if (pos < pars_[pit].beginOfBody())
298 layoutfont = layout->labelfont;
300 layoutfont = layout->font;
302 // Realize against environment font information
303 if (pars_[pit].getDepth()) {
305 while (!layoutfont.resolved() &&
306 tp != pit_type(paragraphs().size()) &&
307 pars_[tp].getDepth()) {
308 tp = outerHook(tp, paragraphs());
309 if (tp != pit_type(paragraphs().size()))
310 layoutfont.realize(pars_[tp].layout()->font);
314 // Inside inset, apply the inset's font attributes if any
317 layoutfont.realize(font_);
319 layoutfont.realize(bv()->buffer()->params().getFont());
321 // Now, reduce font against full layout font
322 font.reduce(layoutfont);
324 pars_[pit].setFont(pos, font);
328 // return past-the-last paragraph influenced by a layout change on pit
329 pit_type LyXText::undoSpan(pit_type pit)
331 pit_type end = paragraphs().size();
332 pit_type nextpit = pit + 1;
335 //because of parindents
336 if (!pars_[pit].getDepth())
337 return boost::next(nextpit);
338 //because of depth constrains
339 for (; nextpit != end; ++pit, ++nextpit) {
340 if (!pars_[pit].getDepth())
347 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
349 BOOST_ASSERT(start != end);
351 BufferParams const & bufparams = bv()->buffer()->params();
352 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
354 for (pit_type pit = start; pit != end; ++pit) {
355 pars_[pit].applyLayout(lyxlayout);
356 if (lyxlayout->margintype == MARGIN_MANUAL)
357 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
362 // set layout over selection and make a total rebreak of those paragraphs
363 void LyXText::setLayout(LCursor & cur, string const & layout)
365 BOOST_ASSERT(this == cur.text());
366 // special handling of new environment insets
367 BufferView & bv = cur.bv();
368 BufferParams const & params = bv.buffer()->params();
369 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
370 if (lyxlayout->is_environment) {
371 // move everything in a new environment inset
372 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
373 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
374 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
375 lyx::dispatch(FuncRequest(LFUN_CUT));
376 InsetBase * inset = new InsetEnvironment(params, layout);
377 insertInset(cur, inset);
378 //inset->edit(cur, true);
379 //lyx::dispatch(FuncRequest(LFUN_PASTE));
383 pit_type start = cur.selBegin().pit();
384 pit_type end = cur.selEnd().pit() + 1;
385 pit_type undopit = undoSpan(end - 1);
386 recUndo(start, undopit - 1);
387 setLayout(start, end, layout);
388 updateLabels(cur.buffer());
395 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
396 Paragraph const & par, int max_depth)
398 if (par.layout()->labeltype == LABEL_BIBLIO)
400 int const depth = par.params().depth();
401 if (type == LyXText::INC_DEPTH && depth < max_depth)
403 if (type == LyXText::DEC_DEPTH && depth > 0)
412 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
414 BOOST_ASSERT(this == cur.text());
415 // this happens when selecting several cells in tabular (bug 2630)
416 if (cur.selBegin().idx() != cur.selEnd().idx())
419 pit_type const beg = cur.selBegin().pit();
420 pit_type const end = cur.selEnd().pit() + 1;
421 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
423 for (pit_type pit = beg; pit != end; ++pit) {
424 if (::changeDepthAllowed(type, pars_[pit], max_depth))
426 max_depth = pars_[pit].getMaxDepthAfter();
432 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
434 BOOST_ASSERT(this == cur.text());
435 pit_type const beg = cur.selBegin().pit();
436 pit_type const end = cur.selEnd().pit() + 1;
437 recordUndoSelection(cur);
438 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
440 for (pit_type pit = beg; pit != end; ++pit) {
441 Paragraph & par = pars_[pit];
442 if (::changeDepthAllowed(type, par, max_depth)) {
443 int const depth = par.params().depth();
444 if (type == INC_DEPTH)
445 par.params().depth(depth + 1);
447 par.params().depth(depth - 1);
449 max_depth = par.getMaxDepthAfter();
451 // this handles the counter labels, and also fixes up
452 // depth values for follow-on (child) paragraphs
453 updateLabels(cur.buffer());
457 // set font over selection
458 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
460 BOOST_ASSERT(this == cur.text());
461 // if there is no selection just set the current_font
462 if (!cur.selection()) {
463 // Determine basis font
465 pit_type pit = cur.pit();
466 if (cur.pos() < pars_[pit].beginOfBody())
467 layoutfont = getLabelFont(pars_[pit]);
469 layoutfont = getLayoutFont(pit);
471 // Update current font
472 real_current_font.update(font,
473 cur.buffer().params().language,
476 // Reduce to implicit settings
477 current_font = real_current_font;
478 current_font.reduce(layoutfont);
479 // And resolve it completely
480 real_current_font.realize(layoutfont);
485 // Ok, we have a selection.
486 recordUndoSelection(cur);
488 DocIterator dit = cur.selectionBegin();
489 DocIterator ditend = cur.selectionEnd();
491 BufferParams const & params = cur.buffer().params();
493 // Don't use forwardChar here as ditend might have
494 // pos() == lastpos() and forwardChar would miss it.
495 // Can't use forwardPos either as this descends into
497 for (; dit != ditend; dit.forwardPosNoDescend()) {
498 if (dit.pos() != dit.lastpos()) {
499 LyXFont f = getFont(dit.paragraph(), dit.pos());
500 f.update(font, params.language, toggleall);
501 setCharFont(dit.pit(), dit.pos(), f);
507 // the cursor set functions have a special mechanism. When they
508 // realize you left an empty paragraph, they will delete it.
510 bool LyXText::cursorHome(LCursor & cur)
512 BOOST_ASSERT(this == cur.text());
513 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
515 return setCursor(cur, cur.pit(), row.pos());
519 bool LyXText::cursorEnd(LCursor & cur)
521 BOOST_ASSERT(this == cur.text());
522 // if not on the last row of the par, put the cursor before
523 // the final space exept if I have a spanning inset or one string
524 // is so long that we force a break.
525 pos_type end = cur.textRow().endpos();
527 // empty text, end-1 is no valid position
529 bool boundary = false;
530 if (end != cur.lastpos()) {
531 if (!cur.paragraph().isLineSeparator(end-1)
532 && !cur.paragraph().isNewline(end-1))
537 return setCursor(cur, cur.pit(), end, true, boundary);
541 bool LyXText::cursorTop(LCursor & cur)
543 BOOST_ASSERT(this == cur.text());
544 return setCursor(cur, 0, 0);
548 bool LyXText::cursorBottom(LCursor & cur)
550 BOOST_ASSERT(this == cur.text());
551 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
555 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
557 BOOST_ASSERT(this == cur.text());
558 // If the mask is completely neutral, tell user
559 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
560 // Could only happen with user style
561 cur.message(_("No font change defined. "
562 "Use Character under the Layout menu to define font change."));
566 // Try implicit word selection
567 // If there is a change in the language the implicit word selection
569 CursorSlice resetCursor = cur.top();
570 bool implicitSelection =
571 font.language() == ignore_language
572 && font.number() == LyXFont::IGNORE
573 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
576 setFont(cur, font, toggleall);
578 // Implicit selections are cleared afterwards
579 // and cursor is set to the original position.
580 if (implicitSelection) {
581 cur.clearSelection();
582 cur.top() = resetCursor;
588 string LyXText::getStringToIndex(LCursor const & cur)
590 BOOST_ASSERT(this == cur.text());
594 idxstring = cur.selectionAsString(false);
596 // Try implicit word selection. If there is a change
597 // in the language the implicit word selection is
599 LCursor tmpcur = cur;
600 selectWord(tmpcur, lyx::PREVIOUS_WORD);
602 if (!tmpcur.selection())
603 cur.message(_("Nothing to index!"));
604 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
605 cur.message(_("Cannot index more than one paragraph!"));
607 idxstring = tmpcur.selectionAsString(false);
610 return lyx::to_utf8(idxstring);
614 void LyXText::setParagraph(LCursor & cur,
615 Spacing const & spacing, LyXAlignment align,
616 string const & labelwidthstring, bool noindent)
618 BOOST_ASSERT(cur.text());
619 // make sure that the depth behind the selection are restored, too
620 pit_type undopit = undoSpan(cur.selEnd().pit());
621 recUndo(cur.selBegin().pit(), undopit - 1);
623 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
625 Paragraph & par = pars_[pit];
626 ParagraphParameters & params = par.params();
627 params.spacing(spacing);
629 // does the layout allow the new alignment?
630 LyXLayout_ptr const & layout = par.layout();
632 if (align == LYX_ALIGN_LAYOUT)
633 align = layout->align;
634 if (align & layout->alignpossible) {
635 if (align == layout->align)
636 params.align(LYX_ALIGN_LAYOUT);
641 par.setLabelWidthString(lyx::from_ascii(labelwidthstring));
642 params.noindent(noindent);
647 // this really should just insert the inset and not move the cursor.
648 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
650 BOOST_ASSERT(this == cur.text());
652 cur.paragraph().insertInset(cur.pos(), inset,
653 Change(cur.buffer().params().trackChanges ?
654 Change::INSERTED : Change::UNCHANGED));
658 // needed to insert the selection
659 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
661 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
662 current_font, str, autoBreakRows_);
666 // turn double CR to single CR, others are converted into one
667 // blank. Then insertStringAsLines is called
668 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
670 docstring linestr = str;
671 bool newline_inserted = false;
673 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
674 if (linestr[i] == '\n') {
675 if (newline_inserted) {
676 // we know that \r will be ignored by
677 // insertStringAsLines. Of course, it is a dirty
678 // trick, but it works...
679 linestr[i - 1] = '\r';
683 newline_inserted = true;
685 } else if (isPrintable(linestr[i])) {
686 newline_inserted = false;
689 insertStringAsLines(cur, linestr);
693 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
694 bool setfont, bool boundary)
697 setCursorIntern(cur, par, pos, setfont, boundary);
698 return deleteEmptyParagraphMechanism(cur, old);
702 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
704 BOOST_ASSERT(par != int(paragraphs().size()));
708 // now some strict checking
709 Paragraph & para = getPar(par);
711 // None of these should happen, but we're scaredy-cats
713 lyxerr << "dont like -1" << endl;
717 if (pos > para.size()) {
718 lyxerr << "dont like 1, pos: " << pos
719 << " size: " << para.size()
720 << " par: " << par << endl;
726 void LyXText::setCursorIntern(LCursor & cur,
727 pit_type par, pos_type pos, bool setfont, bool boundary)
729 BOOST_ASSERT(this == cur.text());
730 cur.boundary(boundary);
731 setCursor(cur.top(), par, pos);
738 void LyXText::setCurrentFont(LCursor & cur)
740 BOOST_ASSERT(this == cur.text());
741 pos_type pos = cur.pos();
742 Paragraph & par = cur.paragraph();
744 if (cur.boundary() && pos > 0)
748 if (pos == cur.lastpos())
750 else // potentional bug... BUG (Lgb)
751 if (par.isSeparator(pos)) {
752 if (pos > cur.textRow().pos() &&
753 bidi.level(pos) % 2 ==
754 bidi.level(pos - 1) % 2)
756 else if (pos + 1 < cur.lastpos())
761 BufferParams const & bufparams = cur.buffer().params();
762 current_font = par.getFontSettings(bufparams, pos);
763 real_current_font = getFont(par, pos);
765 if (cur.pos() == cur.lastpos()
766 && bidi.isBoundary(cur.buffer(), par, cur.pos())
767 && !cur.boundary()) {
768 Language const * lang = par.getParLanguage(bufparams);
769 current_font.setLanguage(lang);
770 current_font.setNumber(LyXFont::OFF);
771 real_current_font.setLanguage(lang);
772 real_current_font.setNumber(LyXFont::OFF);
777 // x is an absolute screen coord
778 // returns the column near the specified x-coordinate of the row
779 // x is set to the real beginning of this column
780 pos_type LyXText::getColumnNearX(pit_type const pit,
781 Row const & row, int & x, bool & boundary) const
783 int const xo = bv()->coordCache().get(this, pit).x_;
785 RowMetrics const r = computeRowMetrics(pit, row);
786 Paragraph const & par = pars_[pit];
788 pos_type vc = row.pos();
789 pos_type end = row.endpos();
791 LyXLayout_ptr const & layout = par.layout();
793 bool left_side = false;
795 pos_type body_pos = par.beginOfBody();
798 double last_tmpx = tmpx;
801 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
804 // check for empty row
810 lyx::frontend::FontMetrics const & fm
811 = theFontMetrics(getLabelFont(par));
813 while (vc < end && tmpx <= x) {
814 c = bidi.vis2log(vc);
816 if (body_pos > 0 && c == body_pos - 1) {
818 docstring const lsep = lyx::from_utf8(layout->labelsep);
819 tmpx += r.label_hfill + fm.width(lsep);
820 if (par.isLineSeparator(body_pos - 1))
821 tmpx -= singleWidth(par, body_pos - 1);
824 if (hfillExpansion(par, row, c)) {
825 tmpx += singleWidth(par, c);
829 tmpx += r.label_hfill;
830 } else if (par.isSeparator(c)) {
831 tmpx += singleWidth(par, c);
835 tmpx += singleWidth(par, c);
840 if ((tmpx + last_tmpx) / 2 > x) {
845 BOOST_ASSERT(vc <= end); // This shouldn't happen.
848 // This (rtl_support test) is not needed, but gives
849 // some speedup if rtl_support == false
850 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
852 // If lastrow is false, we don't need to compute
854 bool const rtl = lastrow ? isRTL(par) : false;
856 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
857 (!rtl && !left_side && vc == end && x > tmpx + 5)))
859 else if (vc == row.pos()) {
860 c = bidi.vis2log(vc);
861 if (bidi.level(c) % 2 == 1)
864 c = bidi.vis2log(vc - 1);
865 bool const rtl = (bidi.level(c) % 2 == 1);
866 if (left_side == rtl) {
868 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
872 // I believe this code is not needed anymore (Jug 20050717)
874 // The following code is necessary because the cursor position past
875 // the last char in a row is logically equivalent to that before
876 // the first char in the next row. That's why insets causing row
877 // divisions -- Newline and display-style insets -- must be treated
878 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
879 // Newline inset, air gap below:
880 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
881 if (bidi.level(end -1) % 2 == 0)
882 tmpx -= singleWidth(par, end - 1);
884 tmpx += singleWidth(par, end - 1);
888 // Air gap above display inset:
889 if (row.pos() < end && c >= end && end < par.size()
890 && par.isInset(end) && par.getInset(end)->display()) {
893 // Air gap below display inset:
894 if (row.pos() < end && c >= end && par.isInset(end - 1)
895 && par.getInset(end - 1)->display()) {
901 pos_type const col = c - row.pos();
903 if (!c || end == par.size())
906 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
911 return min(col, end - 1 - row.pos());
915 // y is screen coordinate
916 pit_type LyXText::getPitNearY(int y) const
918 BOOST_ASSERT(!paragraphs().empty());
919 BOOST_ASSERT(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
920 CoordCache::InnerParPosCache const & cc = bv()->coordCache().getParPos().find(this)->second;
922 << BOOST_CURRENT_FUNCTION
923 << ": y: " << y << " cache size: " << cc.size()
926 // look for highest numbered paragraph with y coordinate less than given y
929 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
930 CoordCache::InnerParPosCache::const_iterator et = cc.end();
931 for (; it != et; ++it) {
933 << BOOST_CURRENT_FUNCTION
934 << " examining: pit: " << it->first
935 << " y: " << it->second.y_
938 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
945 << BOOST_CURRENT_FUNCTION
946 << ": found best y: " << yy << " for pit: " << pit
953 Row const & LyXText::getRowNearY(int y, pit_type pit) const
955 Paragraph const & par = pars_[pit];
956 int yy = bv()->coordCache().get(this, pit).y_ - par.ascent();
957 BOOST_ASSERT(!par.rows().empty());
958 RowList::const_iterator rit = par.rows().begin();
959 RowList::const_iterator const rlast = boost::prior(par.rows().end());
960 for (; rit != rlast; yy += rit->height(), ++rit)
961 if (yy + rit->height() > y)
967 // x,y are absolute screen coordinates
968 // sets cursor recursively descending into nested editable insets
969 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
971 pit_type pit = getPitNearY(y);
972 BOOST_ASSERT(pit != -1);
973 Row const & row = getRowNearY(y, pit);
976 int xx = x; // is modified by getColumnNearX
977 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
983 // try to descend into nested insets
984 InsetBase * inset = checkInsetHit(x, y);
985 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
987 // Either we deconst editXY or better we move current_font
988 // and real_current_font to LCursor
993 // This should be just before or just behind the
994 // cursor position set above.
995 InsetBase * inset2 = pars_[pit].getInset(pos - 1);
996 InsetBase * inset3 = pars_[pit].getInset(pos);
998 BOOST_ASSERT((pos != 0 && inset == inset2)
1000 // Make sure the cursor points to the position before
1002 if (inset == pars_[pit].getInset(pos - 1))
1004 inset = inset->editXY(cur, x, y);
1005 if (cur.top().text() == this)
1006 setCurrentFont(cur);
1011 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1013 if (cur.selection())
1015 if (cur.pos() == cur.lastpos())
1017 InsetBase * inset = cur.nextInset();
1018 if (!isHighlyEditableInset(inset))
1020 inset->edit(cur, front);
1025 bool LyXText::cursorLeft(LCursor & cur)
1027 if (!cur.boundary() && cur.pos() > 0 &&
1028 cur.textRow().pos() == cur.pos() &&
1029 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1030 !cur.paragraph().isNewline(cur.pos()-1)) {
1031 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1033 if (cur.pos() != 0) {
1034 bool boundary = cur.boundary();
1035 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1036 if (!checkAndActivateInset(cur, false)) {
1037 if (false && !boundary &&
1038 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1040 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1042 return updateNeeded;
1045 if (cur.pit() != 0) {
1046 // Steps into the paragraph above
1047 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1053 bool LyXText::cursorRight(LCursor & cur)
1055 if (cur.pos() != cur.lastpos()) {
1057 return setCursor(cur, cur.pit(), cur.pos(),
1060 bool updateNeeded = false;
1061 if (!checkAndActivateInset(cur, true)) {
1062 if (cur.textRow().endpos() == cur.pos() + 1 &&
1063 cur.textRow().endpos() != cur.lastpos() &&
1064 !cur.paragraph().isLineSeparator(cur.pos()) &&
1065 !cur.paragraph().isNewline(cur.pos())) {
1068 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1069 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1071 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1073 return updateNeeded;
1076 if (cur.pit() != cur.lastpit())
1077 return setCursor(cur, cur.pit() + 1, 0);
1082 bool LyXText::cursorUp(LCursor & cur)
1084 Paragraph const & par = cur.paragraph();
1086 int const x = cur.targetX();
1088 if (cur.pos() && cur.boundary())
1089 row = par.pos2row(cur.pos()-1);
1091 row = par.pos2row(cur.pos());
1093 if (!cur.selection()) {
1094 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1096 // Go to middle of previous row. 16 found to work OK;
1097 // 12 = top/bottom margin of display math
1098 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1099 editXY(cur, x, y - par.rows()[row].ascent() - margin);
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 return deleteEmptyParagraphMechanism(dummy, old);
1113 bool updateNeeded = false;
1116 updateNeeded |= setCursor(cur, cur.pit(),
1117 x2pos(cur.pit(), row - 1, x));
1118 } else if (cur.pit() > 0) {
1120 //cannot use 'par' now
1121 updateNeeded |= setCursor(cur, cur.pit(),
1122 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1127 return updateNeeded;
1131 bool LyXText::cursorDown(LCursor & cur)
1133 Paragraph const & par = cur.paragraph();
1135 int const x = cur.targetX();
1137 if (cur.pos() && cur.boundary())
1138 row = par.pos2row(cur.pos()-1);
1140 row = par.pos2row(cur.pos());
1142 if (!cur.selection()) {
1143 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1145 // To middle of next row
1146 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1147 editXY(cur, x, y + par.rows()[row].descent() + margin);
1148 cur.clearSelection();
1150 // This happens when you move out of an inset.
1151 // And to give the DEPM the possibility of doing
1152 // something we must provide it with two different
1154 LCursor dummy = cur;
1158 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1160 // Make sure that cur gets back whatever happened to dummy(Lgb)
1167 bool updateNeeded = false;
1169 if (row + 1 < int(par.rows().size())) {
1170 updateNeeded |= setCursor(cur, cur.pit(),
1171 x2pos(cur.pit(), row + 1, x));
1172 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1174 updateNeeded |= setCursor(cur, cur.pit(),
1175 x2pos(cur.pit(), 0, x));
1180 return updateNeeded;
1184 bool LyXText::cursorUpParagraph(LCursor & cur)
1186 bool updated = false;
1188 updated = setCursor(cur, cur.pit(), 0);
1189 else if (cur.pit() != 0)
1190 updated = setCursor(cur, cur.pit() - 1, 0);
1195 bool LyXText::cursorDownParagraph(LCursor & cur)
1197 bool updated = false;
1198 if (cur.pit() != cur.lastpit())
1199 updated = setCursor(cur, cur.pit() + 1, 0);
1201 updated = setCursor(cur, cur.pit(), cur.lastpos());
1206 // fix the cursor `cur' after a characters has been deleted at `where'
1207 // position. Called by deleteEmptyParagraphMechanism
1208 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1210 // Do nothing if cursor is not in the paragraph where the
1211 // deletion occured,
1212 if (cur.pit() != where.pit())
1215 // If cursor position is after the deletion place update it
1216 if (cur.pos() > where.pos())
1219 // Check also if we don't want to set the cursor on a spot behind the
1220 // pagragraph because we erased the last character.
1221 if (cur.pos() > cur.lastpos())
1222 cur.pos() = cur.lastpos();
1226 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1228 // Would be wrong to delete anything if we have a selection.
1229 if (cur.selection())
1232 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1233 // old should point to us
1234 BOOST_ASSERT(old.text() == this);
1236 Paragraph & oldpar = old.paragraph();
1238 // We allow all kinds of "mumbo-jumbo" when freespacing.
1239 if (oldpar.isFreeSpacing())
1242 /* Ok I'll put some comments here about what is missing.
1243 There are still some small problems that can lead to
1244 double spaces stored in the document file or space at
1245 the beginning of paragraphs(). This happens if you have
1246 the cursor between to spaces and then save. Or if you
1247 cut and paste and the selection have a space at the
1248 beginning and then save right after the paste. (Lgb)
1251 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1252 // delete the LineSeparator.
1255 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1256 // delete the LineSeparator.
1259 bool const same_inset = &old.inset() == &cur.inset();
1260 bool const same_par = same_inset && old.pit() == cur.pit();
1261 bool const same_par_pos = same_par && old.pos() == cur.pos();
1263 // If the chars around the old cursor were spaces, delete one of them.
1264 if (!same_par_pos) {
1265 // Only if the cursor has really moved.
1267 && old.pos() < oldpar.size()
1268 && oldpar.isLineSeparator(old.pos())
1269 && oldpar.isLineSeparator(old.pos() - 1)
1270 && oldpar.lookupChange(old.pos() - 1).type != Change::DELETED) {
1271 oldpar.erase(old.pos() - 1, false); // do not track changes in DEPM
1272 #ifdef WITH_WARNINGS
1273 #warning This will not work anymore when we have multiple views of the same buffer
1274 // In this case, we will have to correct also the cursors held by
1275 // other bufferviews. It will probably be easier to do that in a more
1276 // automated way in CursorSlice code. (JMarc 26/09/2001)
1278 // correct all cursor parts
1280 fixCursorAfterDelete(cur.top(), old.top());
1287 // only do our magic if we changed paragraph
1291 // don't delete anything if this is the ONLY paragraph!
1292 if (old.lastpit() == 0)
1295 // Do not delete empty paragraphs with keepempty set.
1296 if (oldpar.allowEmpty())
1299 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1301 recordUndo(old, Undo::ATOMIC,
1302 max(old.pit() - 1, pit_type(0)),
1303 min(old.pit() + 1, old.lastpit()));
1304 ParagraphList & plist = old.text()->paragraphs();
1305 plist.erase(boost::next(plist.begin(), old.pit()));
1307 // see #warning above
1308 if (cur.depth() >= old.depth()) {
1309 CursorSlice & curslice = cur[old.depth() - 1];
1310 if (&curslice.inset() == &old.inset()
1311 && curslice.pit() > old.pit()) {
1313 // since a paragraph has been deleted, all the
1314 // insets after `old' have been copied and
1315 // their address has changed. Therefore we
1316 // need to `regenerate' cur. (JMarc)
1317 cur.updateInsets(&(cur.bottom().inset()));
1321 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1322 //ParIterator par_it(old);
1323 //updateLabels(old.buffer(), par_it);
1324 // So for now we do the full update:
1325 updateLabels(old.buffer());
1329 if (oldpar.stripLeadingSpaces())
1336 void LyXText::recUndo(pit_type first, pit_type last) const
1338 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1342 void LyXText::recUndo(pit_type par) const
1344 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1348 int defaultRowHeight()
1350 return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() * 1.2);