3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
29 #include "coordcache.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
52 #include "insets/insetenv.h"
54 #include "support/textutils.h"
62 using std::ostringstream;
67 LyXText::LyXText(BufferView * bv)
68 : maxwidth_(bv ? bv->workWidth() : 100),
69 background_color_(LColor::background),
75 void LyXText::init(BufferView * bv)
79 maxwidth_ = bv->workWidth();
84 pit_type const end = paragraphs().size();
85 for (pit_type pit = 0; pit != end; ++pit)
86 pars_[pit].rows().clear();
88 current_font = getFont(pars_[0], 0);
89 updateCounters(*bv->buffer());
93 bool LyXText::isMainText() const
95 return &bv()->buffer()->text() == this;
99 //takes screen x,y coordinates
100 InsetBase * LyXText::checkInsetHit(int x, int y) const
102 pit_type pit = getPitNearY(y);
103 BOOST_ASSERT(pit != -1);
105 Paragraph const & par = pars_[pit];
108 << BOOST_CURRENT_FUNCTION
113 InsetList::const_iterator iit = par.insetlist.begin();
114 InsetList::const_iterator iend = par.insetlist.end();
115 for (; iit != iend; ++iit) {
116 InsetBase * inset = iit->inset;
119 << BOOST_CURRENT_FUNCTION
120 << ": examining inset " << inset << endl;
122 if (theCoords.getInsets().has(inset))
124 << BOOST_CURRENT_FUNCTION
125 << ": xo: " << inset->xo() << "..."
126 << inset->xo() + inset->width()
127 << " yo: " << inset->yo() - inset->ascent()
129 << inset->yo() + inset->descent()
133 << BOOST_CURRENT_FUNCTION
134 << ": inset has no cached position" << endl;
136 if (inset->covers(x, y)) {
138 << BOOST_CURRENT_FUNCTION
139 << ": Hit inset: " << inset << endl;
144 << BOOST_CURRENT_FUNCTION
145 << ": No inset hit. " << endl;
151 // Gets the fully instantiated font at a given position in a paragraph
152 // Basically the same routine as Paragraph::getFont() in paragraph.C.
153 // The difference is that this one is used for displaying, and thus we
154 // are allowed to make cosmetic improvements. For instance make footnotes
156 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
158 BOOST_ASSERT(pos >= 0);
160 LyXLayout_ptr const & layout = par.layout();
164 BufferParams const & params = bv()->buffer()->params();
165 pos_type const body_pos = par.beginOfBody();
167 // We specialize the 95% common case:
168 if (!par.getDepth()) {
169 LyXFont f = par.getFontSettings(params, pos);
172 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
173 return f.realize(layout->reslabelfont);
175 return f.realize(layout->resfont);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 LyXFont font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
189 applyOuterFont(font);
191 // Realize with the fonts of lesser depth.
192 font.realize(defaultfont_);
197 // There are currently two font mechanisms in LyX:
198 // 1. The font attributes in a lyxtext, and
199 // 2. The inset-specific font properties, defined in an inset's
200 // metrics() and draw() methods and handed down the inset chain through
201 // the pi/mi parameters, and stored locally in a lyxtext in font_.
202 // This is where the two are integrated in the final fully realized
204 void LyXText::applyOuterFont(LyXFont & font) const {
206 lf.reduce(defaultfont_);
208 lf.setLanguage(font.language());
213 LyXFont LyXText::getLayoutFont(pit_type const pit) const
215 LyXLayout_ptr const & layout = pars_[pit].layout();
217 if (!pars_[pit].getDepth())
218 return layout->resfont;
220 LyXFont font = layout->font;
221 // Realize with the fonts of lesser depth.
222 //font.realize(outerFont(pit, paragraphs()));
223 font.realize(defaultfont_);
229 LyXFont LyXText::getLabelFont(Paragraph const & par) const
231 LyXLayout_ptr const & layout = par.layout();
234 return layout->reslabelfont;
236 LyXFont font = layout->labelfont;
237 // Realize with the fonts of lesser depth.
238 font.realize(defaultfont_);
244 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
247 LyXLayout_ptr const & layout = pars_[pit].layout();
249 // Get concrete layout font to reduce against
252 if (pos < pars_[pit].beginOfBody())
253 layoutfont = layout->labelfont;
255 layoutfont = layout->font;
257 // Realize against environment font information
258 if (pars_[pit].getDepth()) {
260 while (!layoutfont.resolved() &&
261 tp != pit_type(paragraphs().size()) &&
262 pars_[tp].getDepth()) {
263 tp = outerHook(tp, paragraphs());
264 if (tp != pit_type(paragraphs().size()))
265 layoutfont.realize(pars_[tp].layout()->font);
269 layoutfont.realize(defaultfont_);
271 // Now, reduce font against full layout font
272 font.reduce(layoutfont);
274 pars_[pit].setFont(pos, font);
279 // Asger is not sure we want to do this...
280 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
283 LyXLayout_ptr const & layout = par.layout();
284 pos_type const psize = par.size();
287 for (pos_type pos = 0; pos < psize; ++pos) {
288 if (pos < par.beginOfBody())
289 layoutfont = layout->labelfont;
291 layoutfont = layout->font;
293 LyXFont tmpfont = par.getFontSettings(params, pos);
294 tmpfont.reduce(layoutfont);
295 par.setFont(pos, tmpfont);
300 // return past-the-last paragraph influenced by a layout change on pit
301 pit_type LyXText::undoSpan(pit_type pit)
303 pit_type end = paragraphs().size();
304 pit_type nextpit = pit + 1;
307 //because of parindents
308 if (!pars_[pit].getDepth())
309 return boost::next(nextpit);
310 //because of depth constrains
311 for (; nextpit != end; ++pit, ++nextpit) {
312 if (!pars_[pit].getDepth())
319 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
321 BOOST_ASSERT(start != end);
322 pit_type undopit = undoSpan(end - 1);
323 recUndo(start, undopit - 1);
325 BufferParams const & bufparams = bv()->buffer()->params();
326 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
328 for (pit_type pit = start; pit != end; ++pit) {
329 pars_[pit].applyLayout(lyxlayout);
330 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
331 if (lyxlayout->margintype == MARGIN_MANUAL)
332 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
339 // set layout over selection and make a total rebreak of those paragraphs
340 void LyXText::setLayout(LCursor & cur, string const & layout)
342 BOOST_ASSERT(this == cur.text());
343 // special handling of new environment insets
344 BufferView & bv = cur.bv();
345 BufferParams const & params = bv.buffer()->params();
346 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
347 if (lyxlayout->is_environment) {
348 // move everything in a new environment inset
349 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
350 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
351 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
352 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
353 InsetBase * inset = new InsetEnvironment(params, layout);
354 insertInset(cur, inset);
355 //inset->edit(cur, true);
356 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
360 pit_type start = cur.selBegin().pit();
361 pit_type end = cur.selEnd().pit() + 1;
362 setLayout(start, end, layout);
363 updateCounters(cur.buffer());
370 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
371 Paragraph const & par, int max_depth)
373 if (par.layout()->labeltype == LABEL_BIBLIO)
375 int const depth = par.params().depth();
376 if (type == LyXText::INC_DEPTH && depth < max_depth)
378 if (type == LyXText::DEC_DEPTH && depth > 0)
387 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
389 BOOST_ASSERT(this == cur.text());
390 pit_type const beg = cur.selBegin().pit();
391 pit_type const end = cur.selEnd().pit() + 1;
392 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
394 for (pit_type pit = beg; pit != end; ++pit) {
395 if (::changeDepthAllowed(type, pars_[pit], max_depth))
397 max_depth = pars_[pit].getMaxDepthAfter();
403 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
405 BOOST_ASSERT(this == cur.text());
406 pit_type const beg = cur.selBegin().pit();
407 pit_type const end = cur.selEnd().pit() + 1;
408 recordUndoSelection(cur);
409 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
411 for (pit_type pit = beg; pit != end; ++pit) {
412 Paragraph & par = pars_[pit];
413 if (::changeDepthAllowed(type, par, max_depth)) {
414 int const depth = par.params().depth();
415 if (type == INC_DEPTH)
416 par.params().depth(depth + 1);
418 par.params().depth(depth - 1);
420 max_depth = par.getMaxDepthAfter();
422 // this handles the counter labels, and also fixes up
423 // depth values for follow-on (child) paragraphs
424 updateCounters(cur.buffer());
428 // set font over selection
429 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
431 BOOST_ASSERT(this == cur.text());
432 // if there is no selection just set the current_font
433 if (!cur.selection()) {
434 // Determine basis font
436 pit_type pit = cur.pit();
437 if (cur.pos() < pars_[pit].beginOfBody())
438 layoutfont = getLabelFont(pars_[pit]);
440 layoutfont = getLayoutFont(pit);
442 // Update current font
443 real_current_font.update(font,
444 cur.buffer().params().language,
447 // Reduce to implicit settings
448 current_font = real_current_font;
449 current_font.reduce(layoutfont);
450 // And resolve it completely
451 real_current_font.realize(layoutfont);
456 // Ok, we have a selection.
457 recordUndoSelection(cur);
459 DocIterator dit = cur.selectionBegin();
460 DocIterator ditend = cur.selectionEnd();
462 BufferParams const & params = cur.buffer().params();
464 // Don't use forwardChar here as ditend might have
465 // pos() == lastpos() and forwardChar would miss it.
466 // Can't use forwardPos either as this descends into
468 for (; dit != ditend; dit.forwardPosNoDescend()) {
469 if (dit.pos() != dit.lastpos()) {
470 LyXFont f = getFont(dit.paragraph(), dit.pos());
471 f.update(font, params.language, toggleall);
472 setCharFont(dit.pit(), dit.pos(), f);
478 // the cursor set functions have a special mechanism. When they
479 // realize you left an empty paragraph, they will delete it.
481 void LyXText::cursorHome(LCursor & cur)
483 BOOST_ASSERT(this == cur.text());
484 Paragraph const & par = cur.paragraph();
485 if (cur.boundary() && cur.pos())
486 setCursor(cur, cur.pit(), par.getRow(cur.pos()-1).pos());
488 setCursor(cur, cur.pit(), cur.textRow().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();
499 bool boundary = false;
500 if (!cur.paragraph().isLineSeparator(end-1) &&
501 !cur.paragraph().isNewline(end-1))
504 } else if (end != cur.lastpos())
506 setCursor(cur, cur.pit(), end, true, boundary);
510 void LyXText::cursorTop(LCursor & cur)
512 BOOST_ASSERT(this == cur.text());
513 setCursor(cur, 0, 0);
517 void LyXText::cursorBottom(LCursor & cur)
519 BOOST_ASSERT(this == cur.text());
520 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
524 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
526 BOOST_ASSERT(this == cur.text());
527 // If the mask is completely neutral, tell user
528 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
529 // Could only happen with user style
530 cur.message(_("No font change defined. "
531 "Use Character under the Layout menu to define font change."));
535 // Try implicit word selection
536 // If there is a change in the language the implicit word selection
538 CursorSlice resetCursor = cur.top();
539 bool implicitSelection =
540 font.language() == ignore_language
541 && font.number() == LyXFont::IGNORE
542 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
545 setFont(cur, font, toggleall);
547 // Implicit selections are cleared afterwards
548 // and cursor is set to the original position.
549 if (implicitSelection) {
550 cur.clearSelection();
551 cur.top() = resetCursor;
557 string LyXText::getStringToIndex(LCursor const & cur)
559 BOOST_ASSERT(this == cur.text());
562 if (cur.selection()) {
563 idxstring = cur.selectionAsString(false);
565 // Try implicit word selection. If there is a change
566 // in the language the implicit word selection is
568 LCursor tmpcur = cur;
569 selectWord(tmpcur, lyx::PREVIOUS_WORD);
571 if (!tmpcur.selection())
572 cur.message(_("Nothing to index!"));
573 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
574 cur.message(_("Cannot index more than one paragraph!"));
576 idxstring = tmpcur.selectionAsString(false);
583 void LyXText::setParagraph(LCursor & cur,
584 Spacing const & spacing, LyXAlignment align,
585 string const & labelwidthstring, bool noindent)
587 BOOST_ASSERT(cur.text());
588 // make sure that the depth behind the selection are restored, too
589 pit_type undopit = undoSpan(cur.selEnd().pit());
590 recUndo(cur.selBegin().pit(), undopit - 1);
592 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
594 Paragraph & par = pars_[pit];
595 ParagraphParameters & params = par.params();
596 params.spacing(spacing);
598 // does the layout allow the new alignment?
599 LyXLayout_ptr const & layout = par.layout();
601 if (align == LYX_ALIGN_LAYOUT)
602 align = layout->align;
603 if (align & layout->alignpossible) {
604 if (align == layout->align)
605 params.align(LYX_ALIGN_LAYOUT);
609 par.setLabelWidthString(labelwidthstring);
610 params.noindent(noindent);
615 // this really should just insert the inset and not move the cursor.
616 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
618 BOOST_ASSERT(this == cur.text());
620 cur.paragraph().insertInset(cur.pos(), inset);
624 // needed to insert the selection
625 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
627 pit_type pit = cur.pit();
628 pos_type pos = cur.pos();
631 // only to be sure, should not be neccessary
632 cur.clearSelection();
633 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
637 setCursor(cur, cur.pit(), pos);
642 // turn double CR to single CR, others are converted into one
643 // blank. Then insertStringAsLines is called
644 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
646 string linestr = str;
647 bool newline_inserted = false;
649 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
650 if (linestr[i] == '\n') {
651 if (newline_inserted) {
652 // we know that \r will be ignored by
653 // insertStringAsLines. Of course, it is a dirty
654 // trick, but it works...
655 linestr[i - 1] = '\r';
659 newline_inserted = true;
661 } else if (IsPrintable(linestr[i])) {
662 newline_inserted = false;
665 insertStringAsLines(cur, linestr);
669 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
670 bool setfont, bool boundary)
673 setCursorIntern(cur, par, pos, setfont, boundary);
674 return deleteEmptyParagraphMechanism(cur, old);
678 void LyXText::setCursor(CursorSlice & cur, pit_type par,
679 pos_type pos, bool boundary)
681 BOOST_ASSERT(par != int(paragraphs().size()));
685 // now some strict checking
686 Paragraph & para = getPar(par);
688 // None of these should happen, but we're scaredy-cats
690 lyxerr << "dont like -1" << endl;
694 if (pos > para.size()) {
695 lyxerr << "dont like 1, pos: " << pos
696 << " size: " << para.size()
697 << " par: " << par << endl;
703 void LyXText::setCursorIntern(LCursor & cur,
704 pit_type par, pos_type pos, bool setfont, bool boundary)
706 cur.boundary(boundary);
707 setCursor(cur.top(), par, pos, boundary);
714 void LyXText::setCurrentFont(LCursor & cur)
716 BOOST_ASSERT(this == cur.text());
717 pos_type pos = cur.pos();
718 Paragraph & par = cur.paragraph();
720 if (cur.boundary() && pos > 0)
724 if (pos == cur.lastpos())
726 else // potentional bug... BUG (Lgb)
727 if (par.isSeparator(pos)) {
728 if (pos > cur.textRow().pos() &&
729 bidi.level(pos) % 2 ==
730 bidi.level(pos - 1) % 2)
732 else if (pos + 1 < cur.lastpos())
737 BufferParams const & bufparams = cur.buffer().params();
738 current_font = par.getFontSettings(bufparams, pos);
739 real_current_font = getFont(par, pos);
741 if (cur.pos() == cur.lastpos()
742 && bidi.isBoundary(cur.buffer(), par, cur.pos())
743 && !cur.boundary()) {
744 Language const * lang = par.getParLanguage(bufparams);
745 current_font.setLanguage(lang);
746 current_font.setNumber(LyXFont::OFF);
747 real_current_font.setLanguage(lang);
748 real_current_font.setNumber(LyXFont::OFF);
753 // x is an absolute screen coord
754 // returns the column near the specified x-coordinate of the row
755 // x is set to the real beginning of this column
756 pos_type LyXText::getColumnNearX(pit_type const pit,
757 Row const & row, int & x, bool & boundary) const
759 int const xo = theCoords.get(this, pit).x_;
761 RowMetrics const r = computeRowMetrics(pit, row);
762 Paragraph const & par = pars_[pit];
764 pos_type vc = row.pos();
765 pos_type end = row.endpos();
767 LyXLayout_ptr const & layout = par.layout();
769 bool left_side = false;
771 pos_type body_pos = par.beginOfBody();
774 double last_tmpx = tmpx;
777 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
780 // check for empty row
786 while (vc < end && tmpx <= x) {
787 c = bidi.vis2log(vc);
789 if (body_pos > 0 && c == body_pos - 1) {
790 tmpx += r.label_hfill +
791 font_metrics::width(layout->labelsep, getLabelFont(par));
792 if (par.isLineSeparator(body_pos - 1))
793 tmpx -= singleWidth(par, body_pos - 1);
796 if (hfillExpansion(par, row, c)) {
797 tmpx += singleWidth(par, c);
801 tmpx += r.label_hfill;
802 } else if (par.isSeparator(c)) {
803 tmpx += singleWidth(par, c);
807 tmpx += singleWidth(par, c);
812 if ((tmpx + last_tmpx) / 2 > x) {
817 BOOST_ASSERT(vc <= end); // This shouldn't happen.
820 // This (rtl_support test) is not needed, but gives
821 // some speedup if rtl_support == false
822 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
824 // If lastrow is false, we don't need to compute
826 bool const rtl = lastrow ? isRTL(par) : false;
828 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
829 (!rtl && !left_side && vc == end && x > tmpx + 5)))
831 else if (vc == row.pos()) {
832 c = bidi.vis2log(vc);
833 if (bidi.level(c) % 2 == 1)
836 c = bidi.vis2log(vc - 1);
837 bool const rtl = (bidi.level(c) % 2 == 1);
838 if (left_side == rtl) {
840 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
844 // I believe this code is not needed anymore (Jug 20050717)
846 // The following code is necessary because the cursor position past
847 // the last char in a row is logically equivalent to that before
848 // the first char in the next row. That's why insets causing row
849 // divisions -- Newline and display-style insets -- must be treated
850 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
851 // Newline inset, air gap below:
852 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
853 if (bidi.level(end -1) % 2 == 0)
854 tmpx -= singleWidth(par, end - 1);
856 tmpx += singleWidth(par, end - 1);
860 // Air gap above display inset:
861 if (row.pos() < end && c >= end && end < par.size()
862 && par.isInset(end) && par.getInset(end)->display()) {
865 // Air gap below display inset:
866 if (row.pos() < end && c >= end && par.isInset(end - 1)
867 && par.getInset(end - 1)->display()) {
873 int const col = c - row.pos();
875 if (!c || end == par.size())
878 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
883 return min(col, end - 1 - row.pos());
887 // y is screen coordinate
888 pit_type LyXText::getPitNearY(int y) const
890 BOOST_ASSERT(!paragraphs().empty());
891 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
892 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
894 << BOOST_CURRENT_FUNCTION
895 << ": y: " << y << " cache size: " << cc.size()
898 // look for highest numbered paragraph with y coordinate less than given y
901 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
902 CoordCache::InnerParPosCache::const_iterator et = cc.end();
903 for (; it != et; ++it) {
905 << BOOST_CURRENT_FUNCTION
906 << " examining: pit: " << it->first
907 << " y: " << it->second.y_
910 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
917 << BOOST_CURRENT_FUNCTION
918 << ": found best y: " << yy << " for pit: " << pit
925 Row const & LyXText::getRowNearY(int y, pit_type pit) const
927 Paragraph const & par = pars_[pit];
928 int yy = theCoords.get(this, pit).y_ - par.ascent();
929 BOOST_ASSERT(!par.rows().empty());
930 RowList::const_iterator rit = par.rows().begin();
931 RowList::const_iterator const rlast = boost::prior(par.rows().end());
932 for (; rit != rlast; yy += rit->height(), ++rit)
933 if (yy + rit->height() > y)
939 // x,y are absolute screen coordinates
940 // sets cursor recursively descending into nested editable insets
941 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
943 pit_type pit = getPitNearY(y);
944 BOOST_ASSERT(pit != -1);
945 Row const & row = getRowNearY(y, pit);
948 int xx = x; // is modified by getColumnNearX
949 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
955 // try to descend into nested insets
956 InsetBase * inset = checkInsetHit(x, y);
957 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
959 // Either we deconst editXY or better we move current_font
960 // and real_current_font to LCursor
965 // This should be just before or just behind the
966 // cursor position set above.
967 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
968 || inset == pars_[pit].getInset(pos));
969 // Make sure the cursor points to the position before
971 if (inset == pars_[pit].getInset(pos - 1))
973 inset = inset->editXY(cur, x, y);
974 if (cur.top().text() == this)
980 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
984 if (cur.pos() == cur.lastpos())
986 InsetBase * inset = cur.nextInset();
987 if (!isHighlyEditableInset(inset))
989 inset->edit(cur, front);
994 bool LyXText::cursorLeft(LCursor & cur)
996 if (!cur.boundary() &&
997 cur.textRow().pos() == cur.pos() &&
998 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
999 !cur.paragraph().isNewline(cur.pos()-1))
1001 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1003 if (cur.pos() != 0) {
1004 bool boundary = cur.boundary();
1005 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1006 if (!checkAndActivateInset(cur, false)) {
1007 if (false && !boundary &&
1008 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1010 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1012 return updateNeeded;
1015 if (cur.pit() != 0) {
1016 // Steps into the paragraph above
1017 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1023 bool LyXText::cursorRight(LCursor & cur)
1025 if (cur.boundary()) {
1026 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1029 if (cur.pos() != cur.lastpos()) {
1030 bool updateNeeded = false;
1031 if (!checkAndActivateInset(cur, true)) {
1032 if (cur.textRow().endpos() == (cur.pos() + 1) &&
1033 !cur.paragraph().isLineSeparator(cur.pos()) &&
1034 !cur.paragraph().isNewline(cur.pos()))
1038 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1039 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1041 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1043 return updateNeeded;
1046 if (cur.pit() != cur.lastpit())
1047 return setCursor(cur, cur.pit() + 1, 0);
1052 bool LyXText::cursorUp(LCursor & cur)
1054 Paragraph const & par = cur.paragraph();
1056 int const x = cur.targetX();
1058 if (cur.pos() && cur.boundary())
1059 row = par.pos2row(cur.pos()-1);
1061 row = par.pos2row(cur.pos());
1063 if (!cur.selection()) {
1064 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1066 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1068 // This happens when you move out of an inset.
1069 // And to give the DEPM the possibility of doing
1070 // something we must provide it with two different
1072 LCursor dummy = cur;
1076 return deleteEmptyParagraphMechanism(dummy, old);
1079 bool updateNeeded = false;
1082 updateNeeded |= setCursor(cur, cur.pit(),
1083 x2pos(cur.pit(), row - 1, x));
1084 } else if (cur.pit() > 0) {
1086 //cannot use 'par' now
1087 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1092 return updateNeeded;
1096 bool LyXText::cursorDown(LCursor & cur)
1098 Paragraph const & par = cur.paragraph();
1100 int const x = cur.targetX();
1102 if (cur.pos() && cur.boundary())
1103 row = par.pos2row(cur.pos()-1);
1105 row = par.pos2row(cur.pos());
1107 if (!cur.selection()) {
1108 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1110 editXY(cur, x, y + par.rows()[row].descent() + 1);
1112 // This happens when you move out of an inset.
1113 // And to give the DEPM the possibility of doing
1114 // something we must provide it with two different
1116 LCursor dummy = cur;
1120 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1122 // Make sure that cur gets back whatever happened to dummy(Lgb)
1130 bool updateNeeded = false;
1132 if (row + 1 < int(par.rows().size())) {
1133 updateNeeded |= setCursor(cur, cur.pit(),
1134 x2pos(cur.pit(), row + 1, x));
1135 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1137 updateNeeded |= setCursor(cur, cur.pit(),
1138 x2pos(cur.pit(), 0, x));
1143 return updateNeeded;
1147 bool LyXText::cursorUpParagraph(LCursor & cur)
1149 bool updated = false;
1151 updated = setCursor(cur, cur.pit(), 0);
1152 else if (cur.pit() != 0)
1153 updated = setCursor(cur, cur.pit() - 1, 0);
1158 bool LyXText::cursorDownParagraph(LCursor & cur)
1160 bool updated = false;
1161 if (cur.pit() != cur.lastpit())
1162 updated = setCursor(cur, cur.pit() + 1, 0);
1164 updated = setCursor(cur, cur.pit(), cur.lastpos());
1169 // fix the cursor `cur' after a characters has been deleted at `where'
1170 // position. Called by deleteEmptyParagraphMechanism
1171 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1173 // Do nothing if cursor is not in the paragraph where the
1174 // deletion occured,
1175 if (cur.pit() != where.pit())
1178 // If cursor position is after the deletion place update it
1179 if (cur.pos() > where.pos())
1182 // Check also if we don't want to set the cursor on a spot behind the
1183 // pagragraph because we erased the last character.
1184 if (cur.pos() > cur.lastpos())
1185 cur.pos() = cur.lastpos();
1189 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1191 // Would be wrong to delete anything if we have a selection.
1192 if (cur.selection())
1195 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1196 Paragraph const & oldpar = pars_[old.pit()];
1198 // We allow all kinds of "mumbo-jumbo" when freespacing.
1199 if (oldpar.isFreeSpacing())
1202 /* Ok I'll put some comments here about what is missing.
1203 I have fixed BackSpace (and thus Delete) to not delete
1204 double-spaces automagically. I have also changed Cut,
1205 Copy and Paste to hopefully do some sensible things.
1206 There are still some small problems that can lead to
1207 double spaces stored in the document file or space at
1208 the beginning of paragraphs(). This happens if you have
1209 the cursor between to spaces and then save. Or if you
1210 cut and paste and the selection have a space at the
1211 beginning and then save right after the paste. I am
1212 sure none of these are very hard to fix, but I will
1213 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1214 that I can get some feedback. (Lgb)
1217 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1218 // delete the LineSeparator.
1221 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1222 // delete the LineSeparator.
1225 // If the chars around the old cursor were spaces, delete one of them.
1226 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1228 // Only if the cursor has really moved.
1230 && old.pos() < oldpar.size()
1231 && oldpar.isLineSeparator(old.pos())
1232 && oldpar.isLineSeparator(old.pos() - 1)) {
1233 // We need to set the text to Change::INSERTED to
1234 // get it erased properly
1235 pars_[old.pit()].setChange(old.pos() -1,
1237 pars_[old.pit()].erase(old.pos() - 1);
1238 #ifdef WITH_WARNINGS
1239 #warning This will not work anymore when we have multiple views of the same buffer
1240 // In this case, we will have to correct also the cursors held by
1241 // other bufferviews. It will probably be easier to do that in a more
1242 // automated way in CursorSlice code. (JMarc 26/09/2001)
1244 // correct all cursor parts
1245 fixCursorAfterDelete(cur.top(), old.top());
1246 #ifdef WITH_WARNINGS
1247 #warning DEPM, look here
1249 //fixCursorAfterDelete(cur.anchor(), old.top());
1254 // only do our magic if we changed paragraph
1255 if (old.pit() == cur.pit())
1258 // don't delete anything if this is the ONLY paragraph!
1259 if (pars_.size() == 1)
1262 // Do not delete empty paragraphs with keepempty set.
1263 if (oldpar.allowEmpty())
1266 // record if we have deleted a paragraph
1267 // we can't possibly have deleted a paragraph before this point
1268 bool deleted = false;
1270 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1271 // ok, we will delete something
1274 bool selection_position_was_oldcursor_position =
1275 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1277 // This is a bit of a overkill. We change the old and the cur par
1278 // at max, certainly not everything in between...
1279 recUndo(old.pit(), cur.pit());
1282 pars_.erase(pars_.begin() + old.pit());
1284 // Update cursor par offset if necessary.
1285 // Some 'iterator registration' would be nice that takes care of
1286 // such events. Maybe even signal/slot?
1287 if (cur.pit() > old.pit())
1289 #ifdef WITH_WARNINGS
1290 #warning DEPM, look here
1292 // if (cur.anchor().pit() > old.pit())
1293 // --cur.anchor().pit();
1295 if (selection_position_was_oldcursor_position) {
1296 // correct selection
1304 if (pars_[old.pit()].stripLeadingSpaces())
1311 ParagraphList & LyXText::paragraphs() const
1313 return const_cast<ParagraphList &>(pars_);
1317 void LyXText::recUndo(pit_type first, pit_type last) const
1319 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1323 void LyXText::recUndo(pit_type par) const
1325 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1329 int defaultRowHeight()
1331 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);