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 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
486 setCursor(cur, cur.pit(), row.pos());
490 void LyXText::cursorEnd(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 // if not on the last row of the par, put the cursor before
494 // the final space exept if I have a spanning inset or one string
495 // is so long that we force a break.
496 pos_type end = cur.textRow().endpos();
498 // empty text, end-1 is no valid position
500 bool boundary = false;
501 if (end != cur.lastpos()) {
502 if (!cur.paragraph().isLineSeparator(end-1)
503 && !cur.paragraph().isNewline(end-1))
508 setCursor(cur, cur.pit(), end, true, boundary);
512 void LyXText::cursorTop(LCursor & cur)
514 BOOST_ASSERT(this == cur.text());
515 setCursor(cur, 0, 0);
519 void LyXText::cursorBottom(LCursor & cur)
521 BOOST_ASSERT(this == cur.text());
522 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
526 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
528 BOOST_ASSERT(this == cur.text());
529 // If the mask is completely neutral, tell user
530 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
531 // Could only happen with user style
532 cur.message(_("No font change defined. "
533 "Use Character under the Layout menu to define font change."));
537 // Try implicit word selection
538 // If there is a change in the language the implicit word selection
540 CursorSlice resetCursor = cur.top();
541 bool implicitSelection =
542 font.language() == ignore_language
543 && font.number() == LyXFont::IGNORE
544 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
547 setFont(cur, font, toggleall);
549 // Implicit selections are cleared afterwards
550 // and cursor is set to the original position.
551 if (implicitSelection) {
552 cur.clearSelection();
553 cur.top() = resetCursor;
559 string LyXText::getStringToIndex(LCursor const & cur)
561 BOOST_ASSERT(this == cur.text());
564 if (cur.selection()) {
565 idxstring = cur.selectionAsString(false);
567 // Try implicit word selection. If there is a change
568 // in the language the implicit word selection is
570 LCursor tmpcur = cur;
571 selectWord(tmpcur, lyx::PREVIOUS_WORD);
573 if (!tmpcur.selection())
574 cur.message(_("Nothing to index!"));
575 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
576 cur.message(_("Cannot index more than one paragraph!"));
578 idxstring = tmpcur.selectionAsString(false);
585 void LyXText::setParagraph(LCursor & cur,
586 Spacing const & spacing, LyXAlignment align,
587 string const & labelwidthstring, bool noindent)
589 BOOST_ASSERT(cur.text());
590 // make sure that the depth behind the selection are restored, too
591 pit_type undopit = undoSpan(cur.selEnd().pit());
592 recUndo(cur.selBegin().pit(), undopit - 1);
594 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
596 Paragraph & par = pars_[pit];
597 ParagraphParameters & params = par.params();
598 params.spacing(spacing);
600 // does the layout allow the new alignment?
601 LyXLayout_ptr const & layout = par.layout();
603 if (align == LYX_ALIGN_LAYOUT)
604 align = layout->align;
605 if (align & layout->alignpossible) {
606 if (align == layout->align)
607 params.align(LYX_ALIGN_LAYOUT);
611 par.setLabelWidthString(labelwidthstring);
612 params.noindent(noindent);
617 // this really should just insert the inset and not move the cursor.
618 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
620 BOOST_ASSERT(this == cur.text());
622 cur.paragraph().insertInset(cur.pos(), inset);
626 // needed to insert the selection
627 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
629 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
630 current_font, str, autoBreakRows_);
634 // turn double CR to single CR, others are converted into one
635 // blank. Then insertStringAsLines is called
636 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
638 string linestr = str;
639 bool newline_inserted = false;
641 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
642 if (linestr[i] == '\n') {
643 if (newline_inserted) {
644 // we know that \r will be ignored by
645 // insertStringAsLines. Of course, it is a dirty
646 // trick, but it works...
647 linestr[i - 1] = '\r';
651 newline_inserted = true;
653 } else if (IsPrintable(linestr[i])) {
654 newline_inserted = false;
657 insertStringAsLines(cur, linestr);
661 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
662 bool setfont, bool boundary)
665 setCursorIntern(cur, par, pos, setfont, boundary);
666 return deleteEmptyParagraphMechanism(cur, old);
670 void LyXText::setCursor(CursorSlice & cur, pit_type par,
671 pos_type pos, bool boundary)
673 BOOST_ASSERT(par != int(paragraphs().size()));
677 // now some strict checking
678 Paragraph & para = getPar(par);
680 // None of these should happen, but we're scaredy-cats
682 lyxerr << "dont like -1" << endl;
686 if (pos > para.size()) {
687 lyxerr << "dont like 1, pos: " << pos
688 << " size: " << para.size()
689 << " par: " << par << endl;
695 void LyXText::setCursorIntern(LCursor & cur,
696 pit_type par, pos_type pos, bool setfont, bool boundary)
698 cur.boundary(boundary);
699 setCursor(cur.top(), par, pos, boundary);
706 void LyXText::setCurrentFont(LCursor & cur)
708 BOOST_ASSERT(this == cur.text());
709 pos_type pos = cur.pos();
710 Paragraph & par = cur.paragraph();
712 if (cur.boundary() && pos > 0)
716 if (pos == cur.lastpos())
718 else // potentional bug... BUG (Lgb)
719 if (par.isSeparator(pos)) {
720 if (pos > cur.textRow().pos() &&
721 bidi.level(pos) % 2 ==
722 bidi.level(pos - 1) % 2)
724 else if (pos + 1 < cur.lastpos())
729 BufferParams const & bufparams = cur.buffer().params();
730 current_font = par.getFontSettings(bufparams, pos);
731 real_current_font = getFont(par, pos);
733 if (cur.pos() == cur.lastpos()
734 && bidi.isBoundary(cur.buffer(), par, cur.pos())
735 && !cur.boundary()) {
736 Language const * lang = par.getParLanguage(bufparams);
737 current_font.setLanguage(lang);
738 current_font.setNumber(LyXFont::OFF);
739 real_current_font.setLanguage(lang);
740 real_current_font.setNumber(LyXFont::OFF);
745 // x is an absolute screen coord
746 // returns the column near the specified x-coordinate of the row
747 // x is set to the real beginning of this column
748 pos_type LyXText::getColumnNearX(pit_type const pit,
749 Row const & row, int & x, bool & boundary) const
751 int const xo = theCoords.get(this, pit).x_;
753 RowMetrics const r = computeRowMetrics(pit, row);
754 Paragraph const & par = pars_[pit];
756 pos_type vc = row.pos();
757 pos_type end = row.endpos();
759 LyXLayout_ptr const & layout = par.layout();
761 bool left_side = false;
763 pos_type body_pos = par.beginOfBody();
766 double last_tmpx = tmpx;
769 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
772 // check for empty row
778 while (vc < end && tmpx <= x) {
779 c = bidi.vis2log(vc);
781 if (body_pos > 0 && c == body_pos - 1) {
782 tmpx += r.label_hfill +
783 font_metrics::width(layout->labelsep, getLabelFont(par));
784 if (par.isLineSeparator(body_pos - 1))
785 tmpx -= singleWidth(par, body_pos - 1);
788 if (hfillExpansion(par, row, c)) {
789 tmpx += singleWidth(par, c);
793 tmpx += r.label_hfill;
794 } else if (par.isSeparator(c)) {
795 tmpx += singleWidth(par, c);
799 tmpx += singleWidth(par, c);
804 if ((tmpx + last_tmpx) / 2 > x) {
809 BOOST_ASSERT(vc <= end); // This shouldn't happen.
812 // This (rtl_support test) is not needed, but gives
813 // some speedup if rtl_support == false
814 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
816 // If lastrow is false, we don't need to compute
818 bool const rtl = lastrow ? isRTL(par) : false;
820 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
821 (!rtl && !left_side && vc == end && x > tmpx + 5)))
823 else if (vc == row.pos()) {
824 c = bidi.vis2log(vc);
825 if (bidi.level(c) % 2 == 1)
828 c = bidi.vis2log(vc - 1);
829 bool const rtl = (bidi.level(c) % 2 == 1);
830 if (left_side == rtl) {
832 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
836 // I believe this code is not needed anymore (Jug 20050717)
838 // The following code is necessary because the cursor position past
839 // the last char in a row is logically equivalent to that before
840 // the first char in the next row. That's why insets causing row
841 // divisions -- Newline and display-style insets -- must be treated
842 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
843 // Newline inset, air gap below:
844 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
845 if (bidi.level(end -1) % 2 == 0)
846 tmpx -= singleWidth(par, end - 1);
848 tmpx += singleWidth(par, end - 1);
852 // Air gap above display inset:
853 if (row.pos() < end && c >= end && end < par.size()
854 && par.isInset(end) && par.getInset(end)->display()) {
857 // Air gap below display inset:
858 if (row.pos() < end && c >= end && par.isInset(end - 1)
859 && par.getInset(end - 1)->display()) {
865 pos_type const col = c - row.pos();
867 if (!c || end == par.size())
870 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
875 return min(col, end - 1 - row.pos());
879 // y is screen coordinate
880 pit_type LyXText::getPitNearY(int y) const
882 BOOST_ASSERT(!paragraphs().empty());
883 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
884 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
886 << BOOST_CURRENT_FUNCTION
887 << ": y: " << y << " cache size: " << cc.size()
890 // look for highest numbered paragraph with y coordinate less than given y
893 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
894 CoordCache::InnerParPosCache::const_iterator et = cc.end();
895 for (; it != et; ++it) {
897 << BOOST_CURRENT_FUNCTION
898 << " examining: pit: " << it->first
899 << " y: " << it->second.y_
902 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
909 << BOOST_CURRENT_FUNCTION
910 << ": found best y: " << yy << " for pit: " << pit
917 Row const & LyXText::getRowNearY(int y, pit_type pit) const
919 Paragraph const & par = pars_[pit];
920 int yy = theCoords.get(this, pit).y_ - par.ascent();
921 BOOST_ASSERT(!par.rows().empty());
922 RowList::const_iterator rit = par.rows().begin();
923 RowList::const_iterator const rlast = boost::prior(par.rows().end());
924 for (; rit != rlast; yy += rit->height(), ++rit)
925 if (yy + rit->height() > y)
931 // x,y are absolute screen coordinates
932 // sets cursor recursively descending into nested editable insets
933 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
935 pit_type pit = getPitNearY(y);
936 BOOST_ASSERT(pit != -1);
937 Row const & row = getRowNearY(y, pit);
940 int xx = x; // is modified by getColumnNearX
941 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
947 // try to descend into nested insets
948 InsetBase * inset = checkInsetHit(x, y);
949 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
951 // Either we deconst editXY or better we move current_font
952 // and real_current_font to LCursor
957 // This should be just before or just behind the
958 // cursor position set above.
959 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
960 || inset == pars_[pit].getInset(pos));
961 // Make sure the cursor points to the position before
963 if (inset == pars_[pit].getInset(pos - 1))
965 inset = inset->editXY(cur, x, y);
966 if (cur.top().text() == this)
972 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
976 if (cur.pos() == cur.lastpos())
978 InsetBase * inset = cur.nextInset();
979 if (!isHighlyEditableInset(inset))
981 inset->edit(cur, front);
986 bool LyXText::cursorLeft(LCursor & cur)
988 if (!cur.boundary() && cur.pos() > 0 &&
989 cur.textRow().pos() == cur.pos() &&
990 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
991 !cur.paragraph().isNewline(cur.pos()-1))
993 return setCursor(cur, cur.pit(), cur.pos(), true, true);
995 if (cur.pos() != 0) {
996 bool boundary = cur.boundary();
997 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
998 if (!checkAndActivateInset(cur, false)) {
999 if (false && !boundary &&
1000 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1002 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1004 return updateNeeded;
1007 if (cur.pit() != 0) {
1008 // Steps into the paragraph above
1009 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1015 bool LyXText::cursorRight(LCursor & cur)
1017 if (cur.boundary()) {
1018 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1021 if (cur.pos() != cur.lastpos()) {
1022 bool updateNeeded = false;
1023 if (!checkAndActivateInset(cur, true)) {
1024 if (cur.textRow().endpos() == cur.pos() + 1 &&
1025 cur.textRow().endpos() != cur.lastpos() &&
1026 !cur.paragraph().isLineSeparator(cur.pos()) &&
1027 !cur.paragraph().isNewline(cur.pos()))
1031 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1032 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1034 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1036 return updateNeeded;
1039 if (cur.pit() != cur.lastpit())
1040 return setCursor(cur, cur.pit() + 1, 0);
1045 bool LyXText::cursorUp(LCursor & cur)
1047 Paragraph const & par = cur.paragraph();
1049 int const x = cur.targetX();
1051 if (cur.pos() && cur.boundary())
1052 row = par.pos2row(cur.pos()-1);
1054 row = par.pos2row(cur.pos());
1056 if (!cur.selection()) {
1057 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1059 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1061 // This happens when you move out of an inset.
1062 // And to give the DEPM the possibility of doing
1063 // something we must provide it with two different
1065 LCursor dummy = cur;
1069 return deleteEmptyParagraphMechanism(dummy, old);
1072 bool updateNeeded = false;
1075 updateNeeded |= setCursor(cur, cur.pit(),
1076 x2pos(cur.pit(), row - 1, x));
1077 } else if (cur.pit() > 0) {
1079 //cannot use 'par' now
1080 updateNeeded |= setCursor(cur, cur.pit(),
1081 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1086 return updateNeeded;
1090 bool LyXText::cursorDown(LCursor & cur)
1092 Paragraph const & par = cur.paragraph();
1094 int const x = cur.targetX();
1096 if (cur.pos() && cur.boundary())
1097 row = par.pos2row(cur.pos()-1);
1099 row = par.pos2row(cur.pos());
1101 if (!cur.selection()) {
1102 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1104 editXY(cur, x, y + par.rows()[row].descent() + 1);
1106 // This happens when you move out of an inset.
1107 // And to give the DEPM the possibility of doing
1108 // something we must provide it with two different
1110 LCursor dummy = cur;
1114 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1116 // Make sure that cur gets back whatever happened to dummy(Lgb)
1124 bool updateNeeded = false;
1126 if (row + 1 < int(par.rows().size())) {
1127 updateNeeded |= setCursor(cur, cur.pit(),
1128 x2pos(cur.pit(), row + 1, x));
1129 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1131 updateNeeded |= setCursor(cur, cur.pit(),
1132 x2pos(cur.pit(), 0, x));
1137 return updateNeeded;
1141 bool LyXText::cursorUpParagraph(LCursor & cur)
1143 bool updated = false;
1145 updated = setCursor(cur, cur.pit(), 0);
1146 else if (cur.pit() != 0)
1147 updated = setCursor(cur, cur.pit() - 1, 0);
1152 bool LyXText::cursorDownParagraph(LCursor & cur)
1154 bool updated = false;
1155 if (cur.pit() != cur.lastpit())
1156 updated = setCursor(cur, cur.pit() + 1, 0);
1158 updated = setCursor(cur, cur.pit(), cur.lastpos());
1163 // fix the cursor `cur' after a characters has been deleted at `where'
1164 // position. Called by deleteEmptyParagraphMechanism
1165 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1167 // Do nothing if cursor is not in the paragraph where the
1168 // deletion occured,
1169 if (cur.pit() != where.pit())
1172 // If cursor position is after the deletion place update it
1173 if (cur.pos() > where.pos())
1176 // Check also if we don't want to set the cursor on a spot behind the
1177 // pagragraph because we erased the last character.
1178 if (cur.pos() > cur.lastpos())
1179 cur.pos() = cur.lastpos();
1183 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1185 // Would be wrong to delete anything if we have a selection.
1186 if (cur.selection())
1189 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1190 Paragraph const & oldpar = pars_[old.pit()];
1192 // We allow all kinds of "mumbo-jumbo" when freespacing.
1193 if (oldpar.isFreeSpacing())
1196 /* Ok I'll put some comments here about what is missing.
1197 I have fixed BackSpace (and thus Delete) to not delete
1198 double-spaces automagically. I have also changed Cut,
1199 Copy and Paste to hopefully do some sensible things.
1200 There are still some small problems that can lead to
1201 double spaces stored in the document file or space at
1202 the beginning of paragraphs(). This happens if you have
1203 the cursor between to spaces and then save. Or if you
1204 cut and paste and the selection have a space at the
1205 beginning and then save right after the paste. I am
1206 sure none of these are very hard to fix, but I will
1207 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1208 that I can get some feedback. (Lgb)
1211 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1212 // delete the LineSeparator.
1215 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1216 // delete the LineSeparator.
1219 // If the chars around the old cursor were spaces, delete one of them.
1220 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1222 // Only if the cursor has really moved.
1224 && old.pos() < oldpar.size()
1225 && oldpar.isLineSeparator(old.pos())
1226 && oldpar.isLineSeparator(old.pos() - 1)
1227 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1228 // We need to set the text to Change::INSERTED to
1229 // get it erased properly
1230 pars_[old.pit()].setChange(old.pos() -1,
1232 pars_[old.pit()].erase(old.pos() - 1);
1233 #ifdef WITH_WARNINGS
1234 #warning This will not work anymore when we have multiple views of the same buffer
1235 // In this case, we will have to correct also the cursors held by
1236 // other bufferviews. It will probably be easier to do that in a more
1237 // automated way in CursorSlice code. (JMarc 26/09/2001)
1239 // correct all cursor parts
1240 fixCursorAfterDelete(cur.top(), old.top());
1241 #ifdef WITH_WARNINGS
1242 #warning DEPM, look here
1244 //fixCursorAfterDelete(cur.anchor(), old.top());
1249 // only do our magic if we changed paragraph
1250 if (old.pit() == cur.pit())
1253 // don't delete anything if this is the ONLY paragraph!
1254 if (pars_.size() == 1)
1257 // Do not delete empty paragraphs with keepempty set.
1258 if (oldpar.allowEmpty())
1261 // record if we have deleted a paragraph
1262 // we can't possibly have deleted a paragraph before this point
1263 bool deleted = false;
1265 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1266 // ok, we will delete something
1269 bool selection_position_was_oldcursor_position =
1270 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1272 // This is a bit of a overkill. We change the old and the cur par
1273 // at max, certainly not everything in between...
1274 recUndo(old.pit(), cur.pit());
1277 pars_.erase(pars_.begin() + old.pit());
1279 // Update cursor par offset if necessary.
1280 // Some 'iterator registration' would be nice that takes care of
1281 // such events. Maybe even signal/slot?
1282 if (cur.pit() > old.pit())
1284 #ifdef WITH_WARNINGS
1285 #warning DEPM, look here
1287 // if (cur.anchor().pit() > old.pit())
1288 // --cur.anchor().pit();
1290 if (selection_position_was_oldcursor_position) {
1291 // correct selection
1297 updateCounters(cur.buffer());
1301 if (pars_[old.pit()].stripLeadingSpaces())
1308 void LyXText::recUndo(pit_type first, pit_type last) const
1310 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1314 void LyXText::recUndo(pit_type par) const
1316 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1320 int defaultRowHeight()
1322 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);