X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ftext2.C;h=a570b387d498bc6d98110e482c25e1f2a57ad88a;hb=ee2966faba5563fb853f31f4d3d1785d380e1332;hp=f85342d4a16fc415e9295a93567a4d578cf0833a;hpb=7de76711b14a4c0bcdf5770885be8efc59cac264;p=lyx.git diff --git a/src/text2.C b/src/text2.C index f85342d4a1..a570b387d4 100644 --- a/src/text2.C +++ b/src/text2.C @@ -23,107 +23,156 @@ #include "buffer.h" #include "buffer_funcs.h" +#include "bufferlist.h" #include "bufferparams.h" #include "BufferView.h" +#include "bufferview_funcs.h" #include "Bullet.h" -#include "counters.h" +#include "coordcache.h" #include "cursor.h" #include "CutAndPaste.h" #include "debug.h" #include "dispatchresult.h" #include "errorlist.h" -#include "Floating.h" -#include "FloatList.h" #include "funcrequest.h" #include "gettext.h" #include "language.h" +#include "LColor.h" +#include "lyxfunc.h" #include "lyxrc.h" #include "lyxrow.h" -#include "lyxrow_funcs.h" #include "paragraph.h" +#include "TextMetrics.h" #include "paragraph_funcs.h" #include "ParagraphParameters.h" -#include "PosIterator.h" +#include "pariterator.h" +#include "lyxserver.h" +#include "lyxsocket.h" #include "undo.h" #include "vspace.h" -#include "frontends/font_metrics.h" -#include "frontends/LyXView.h" +#include "frontends/FontMetrics.h" -#include "insets/insetbibitem.h" -#include "insets/insetenv.h" -#include "insets/insetfloat.h" -#include "insets/insetwrap.h" +#include "insets/InsetEnv.h" + +#include "mathed/InsetMathHull.h" -#include "support/lstrings.h" #include "support/textutils.h" -#include "support/tostr.h" -#include "support/std_sstream.h" -#include +#include + +#include -using lyx::pos_type; -using lyx::paroffset_type; -using lyx::support::bformat; + +namespace lyx { using std::endl; using std::ostringstream; using std::string; +using std::max; +using std::min; -LyXText::LyXText(BufferView * bv, InsetText * inset, bool in_inset, - ParagraphList & paragraphs) - : height(0), width(0), textwidth_(bv ? bv->workWidth() : 100), - inset_owner(inset), bv_owner(bv), - in_inset_(in_inset), paragraphs_(¶graphs), xo_(0), yo_(0) +LyXText::LyXText() + : current_font(LyXFont::ALL_INHERIT), + background_color_(LColor::background), + autoBreakRows_(false) {} -void LyXText::init(BufferView * bview) +bool LyXText::isMainText(Buffer const & buffer) const { - bv_owner = bview; + return &buffer.text() == this; +} - ParagraphList::iterator const beg = ownerParagraphs().begin(); - ParagraphList::iterator const end = ownerParagraphs().end(); - for (ParagraphList::iterator pit = beg; pit != end; ++pit) - pit->rows.clear(); - width = 0; - height = 0; +//takes screen x,y coordinates +InsetBase * LyXText::checkInsetHit(BufferView & bv, int x, int y) +{ + pit_type pit = getPitNearY(bv, y); + BOOST_ASSERT(pit != -1); - current_font = getFont(beg, 0); + Paragraph const & par = pars_[pit]; - redoParagraphs(beg, end); - setCursorIntern(0, 0); - selection.cursor = cursor; + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": x: " << x + << " y: " << y + << " pit: " << pit + << endl; + InsetList::const_iterator iit = par.insetlist.begin(); + InsetList::const_iterator iend = par.insetlist.end(); + for (; iit != iend; ++iit) { + InsetBase * inset = iit->inset; +#if 1 + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": examining inset " << inset << endl; - updateCounters(); + if (bv.coordCache().getInsets().has(inset)) + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": xo: " << inset->xo(bv) << "..." + << inset->xo(bv) + inset->width() + << " yo: " << inset->yo(bv) - inset->ascent() + << "..." + << inset->yo(bv) + inset->descent() + << endl; + else + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": inset has no cached position" << endl; +#endif + if (inset->covers(bv, x, y)) { + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": Hit inset: " << inset << endl; + return inset; + } + } + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": No inset hit. " << endl; + return 0; } + // Gets the fully instantiated font at a given position in a paragraph // Basically the same routine as Paragraph::getFont() in paragraph.C. // The difference is that this one is used for displaying, and thus we // are allowed to make cosmetic improvements. For instance make footnotes // smaller. (Asger) -LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const +LyXFont LyXText::getFont(Buffer const & buffer, Paragraph const & par, + pos_type const pos) const { BOOST_ASSERT(pos >= 0); - LyXLayout_ptr const & layout = pit->layout(); + LyXLayout_ptr const & layout = par.layout(); +#ifdef WITH_WARNINGS #warning broken? - BufferParams const & params = bv()->buffer()->params(); - pos_type const body_pos = pit->beginOfBody(); +#endif + BufferParams const & params = buffer.params(); + pos_type const body_pos = par.beginOfBody(); // We specialize the 95% common case: - if (!pit->getDepth()) { - LyXFont f = pit->getFontSettings(params, pos); - if (pit->inInset()) - pit->inInset()->getDrawFont(f); - if (layout->labeltype == LABEL_MANUAL && pos < body_pos) - return f.realize(layout->reslabelfont); - else - return f.realize(layout->resfont); + if (!par.getDepth()) { + LyXFont f = par.getFontSettings(params, pos); + if (!isMainText(buffer)) + applyOuterFont(buffer, f); + LyXFont lf; + LyXFont rlf; + if (layout->labeltype == LABEL_MANUAL && pos < body_pos) { + lf = layout->labelfont; + rlf = layout->reslabelfont; + } else { + lf = layout->font; + rlf = layout->resfont; + } + // In case the default family has been customized + if (lf.family() == LyXFont::INHERIT_FAMILY) + rlf.setFamily(params.getFont().family()); + return f.realize(rlf); } // The uncommon case need not be optimized as much @@ -133,319 +182,274 @@ LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const else layoutfont = layout->font; - LyXFont font = pit->getFontSettings(params, pos); + LyXFont font = par.getFontSettings(params, pos); font.realize(layoutfont); - if (pit->inInset()) - pit->inInset()->getDrawFont(font); + if (!isMainText(buffer)) + applyOuterFont(buffer, font); + + // Find the pit value belonging to paragraph. This will not break + // even if pars_ would not be a vector anymore. + // Performance appears acceptable. + + pit_type pit = pars_.size(); + for (pit_type it = 0; it < pit; ++it) + if (&pars_[it] == &par) { + pit = it; + break; + } + // Realize against environment font information + // NOTE: the cast to pit_type should be removed when pit_type + // changes to a unsigned integer. + if (pit < pit_type(pars_.size())) + font.realize(outerFont(pit, pars_)); // Realize with the fonts of lesser depth. - //font.realize(outerFont(pit, ownerParagraphs())); - font.realize(defaultfont_); + font.realize(params.getFont()); return font; } +// There are currently two font mechanisms in LyX: +// 1. The font attributes in a lyxtext, and +// 2. The inset-specific font properties, defined in an inset's +// metrics() and draw() methods and handed down the inset chain through +// the pi/mi parameters, and stored locally in a lyxtext in font_. +// This is where the two are integrated in the final fully realized +// font. +void LyXText::applyOuterFont(Buffer const & buffer, LyXFont & font) const { + LyXFont lf(font_); + lf.reduce(buffer.params().getFont()); + lf.realize(font); + lf.setLanguage(font.language()); + font = lf; +} + -LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const +LyXFont LyXText::getLayoutFont(Buffer const & buffer, pit_type const pit) const { - LyXLayout_ptr const & layout = pit->layout(); + LyXLayout_ptr const & layout = pars_[pit].layout(); - if (!pit->getDepth()) - return layout->resfont; + if (!pars_[pit].getDepth()) { + LyXFont lf = layout->resfont; + // In case the default family has been customized + if (layout->font.family() == LyXFont::INHERIT_FAMILY) + lf.setFamily(buffer.params().getFont().family()); + return lf; + } LyXFont font = layout->font; // Realize with the fonts of lesser depth. - //font.realize(outerFont(pit, ownerParagraphs())); - font.realize(defaultfont_); + //font.realize(outerFont(pit, paragraphs())); + font.realize(buffer.params().getFont()); return font; } -LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const +LyXFont LyXText::getLabelFont(Buffer const & buffer, Paragraph const & par) const { - LyXLayout_ptr const & layout = pit->layout(); + LyXLayout_ptr const & layout = par.layout(); - if (!pit->getDepth()) - return layout->reslabelfont; + if (!par.getDepth()) { + LyXFont lf = layout->reslabelfont; + // In case the default family has been customized + if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY) + lf.setFamily(buffer.params().getFont().family()); + return lf; + } LyXFont font = layout->labelfont; // Realize with the fonts of lesser depth. - font.realize(outerFont(pit, ownerParagraphs())); - font.realize(defaultfont_); + font.realize(buffer.params().getFont()); return font; } -void LyXText::setCharFont( - ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt) +void LyXText::setCharFont(Buffer const & buffer, pit_type pit, + pos_type pos, LyXFont const & fnt) { LyXFont font = fnt; - LyXLayout_ptr const & layout = pit->layout(); + LyXLayout_ptr const & layout = pars_[pit].layout(); // Get concrete layout font to reduce against LyXFont layoutfont; - if (pos < pit->beginOfBody()) + if (pos < pars_[pit].beginOfBody()) layoutfont = layout->labelfont; else layoutfont = layout->font; // Realize against environment font information - if (pit->getDepth()) { - ParagraphList::iterator tp = pit; + if (pars_[pit].getDepth()) { + pit_type tp = pit; while (!layoutfont.resolved() && - tp != ownerParagraphs().end() && - tp->getDepth()) { - tp = outerHook(tp, ownerParagraphs()); - if (tp != ownerParagraphs().end()) - layoutfont.realize(tp->layout()->font); + tp != pit_type(paragraphs().size()) && + pars_[tp].getDepth()) { + tp = outerHook(tp, paragraphs()); + if (tp != pit_type(paragraphs().size())) + layoutfont.realize(pars_[tp].layout()->font); } } - layoutfont.realize(defaultfont_); + // Inside inset, apply the inset's font attributes if any + // (charstyle!) + if (!isMainText(buffer)) + layoutfont.realize(font_); + + layoutfont.realize(buffer.params().getFont()); // Now, reduce font against full layout font font.reduce(layoutfont); - pit->setFont(pos, font); -} - - -InsetOld * LyXText::getInset() const -{ - ParagraphList::iterator pit = cursorPar(); - pos_type const pos = cursor.pos(); - - if (pos < pit->size() && pit->isInset(pos)) { - return pit->getInset(pos); - } - return 0; -} - - -void LyXText::toggleInset() -{ - InsetOld * inset = getInset(); - // is there an editable inset at cursor position? - if (!isEditableInset(inset)) { - // No, try to see if we are inside a collapsable inset - if (inset_owner && inset_owner->owner() - && inset_owner->owner()->isOpen()) { - finishUndo(); - inset_owner->owner()->close(); - bv()->getLyXText()->cursorRight(true); - bv()->updateParagraphDialog(); - } - return; - } - //bv()->owner()->message(inset->editMessage()); - - // do we want to keep this?? (JMarc) - if (!isHighlyEditableInset(inset)) - recUndo(cursor.par()); - - if (inset->isOpen()) - inset->close(); - else - inset->open(); + pars_[pit].setFont(pos, font); } -// used in setLayout -// Asger is not sure we want to do this... -void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params, - Paragraph & par) +// return past-the-last paragraph influenced by a layout change on pit +pit_type LyXText::undoSpan(pit_type pit) { - LyXLayout_ptr const & layout = par.layout(); - pos_type const psize = par.size(); - - LyXFont layoutfont; - for (pos_type pos = 0; pos < psize; ++pos) { - if (pos < par.beginOfBody()) - layoutfont = layout->labelfont; - else - layoutfont = layout->font; - - LyXFont tmpfont = par.getFontSettings(params, pos); - tmpfont.reduce(layoutfont); - par.setFont(pos, tmpfont); + pit_type end = paragraphs().size(); + pit_type nextpit = pit + 1; + if (nextpit == end) + return nextpit; + //because of parindents + if (!pars_[pit].getDepth()) + return boost::next(nextpit); + //because of depth constrains + for (; nextpit != end; ++pit, ++nextpit) { + if (!pars_[pit].getDepth()) + break; } + return nextpit; } -ParagraphList::iterator -LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur, - LyXCursor & send_cur, - string const & layout) +void LyXText::setLayout(Buffer const & buffer, pit_type start, pit_type end, + string const & layout) { - ParagraphList::iterator endpit = boost::next(getPar(send_cur)); - ParagraphList::iterator undoendpit = endpit; - ParagraphList::iterator pars_end = ownerParagraphs().end(); - - if (endpit != pars_end && endpit->getDepth()) { - while (endpit != pars_end && endpit->getDepth()) { - ++endpit; - undoendpit = endpit; - } - } else if (endpit != pars_end) { - // because of parindents etc. - ++endpit; - } - - recUndo(sstart_cur.par(), parOffset(undoendpit) - 1); + BOOST_ASSERT(start != end); - // ok we have a selection. This is always between sstart_cur - // and sel_end cursor - cur = sstart_cur; - ParagraphList::iterator pit = getPar(sstart_cur); - ParagraphList::iterator epit = boost::next(getPar(send_cur)); + BufferParams const & bufparams = buffer.params(); + LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout]; - BufferParams const & bufparams = bv()->buffer()->params(); - LyXLayout_ptr const & lyxlayout = - bufparams.getLyXTextClass()[layout]; - - do { - pit->applyLayout(lyxlayout); - makeFontEntriesLayoutSpecific(bufparams, *pit); + for (pit_type pit = start; pit != end; ++pit) { + Paragraph & par = pars_[pit]; + par.applyLayout(lyxlayout); if (lyxlayout->margintype == MARGIN_MANUAL) - pit->setLabelWidthString(lyxlayout->labelstring()); - cur.par(std::distance(ownerParagraphs().begin(), pit)); - ++pit; - } while (pit != epit); - - return endpit; + par.setLabelWidthString(par.translateIfPossible( + lyxlayout->labelstring(), buffer.params())); + } } // set layout over selection and make a total rebreak of those paragraphs -void LyXText::setLayout(string const & layout) +void LyXText::setLayout(LCursor & cur, string const & layout) { - setSelection(); - + BOOST_ASSERT(this == cur.text()); // special handling of new environment insets - BufferParams const & params = bv()->buffer()->params(); + BufferView & bv = cur.bv(); + BufferParams const & params = bv.buffer()->params(); LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout]; if (lyxlayout->is_environment) { // move everything in a new environment inset - lyxerr << "setting layout " << layout << endl; - bv()->owner()->dispatch(FuncRequest(LFUN_HOME)); - bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL)); - bv()->owner()->dispatch(FuncRequest(LFUN_CUT)); - InsetOld * inset = new InsetEnvironment(params, layout); - if (bv()->insertInset(inset)) { - //inset->edit(bv()); - //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE)); - } else - delete inset; + LYXERR(Debug::DEBUG) << "setting layout " << layout << endl; + lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN)); + lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT)); + lyx::dispatch(FuncRequest(LFUN_CUT)); + InsetBase * inset = new InsetEnvironment(params, layout); + insertInset(cur, inset); + //inset->edit(cur, true); + //lyx::dispatch(FuncRequest(LFUN_PASTE)); return; } - ParagraphList::iterator endpit = setLayout(cursor, selection.start, - selection.end, layout); - redoParagraphs(getPar(selection.start), endpit); - updateCounters(); - redoCursor(); -} - - -namespace { - - -void getSelectionSpan(LyXText & text, - ParagraphList::iterator & beg, - ParagraphList::iterator & end) -{ - if (!text.selection.set()) { - beg = text.cursorPar(); - end = boost::next(beg); - } else { - beg = text.getPar(text.selection.start); - end = boost::next(text.getPar(text.selection.end)); - } + pit_type start = cur.selBegin().pit(); + pit_type end = cur.selEnd().pit() + 1; + pit_type undopit = undoSpan(end - 1); + recUndo(cur, start, undopit - 1); + setLayout(cur.buffer(), start, end, layout); + updateLabels(cur.buffer()); } -bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type, - Paragraph const & par, - int max_depth) +static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type, + Paragraph const & par, int max_depth) { if (par.layout()->labeltype == LABEL_BIBLIO) return false; int const depth = par.params().depth(); - if (type == bv_funcs::INC_DEPTH && depth < max_depth) + if (type == LyXText::INC_DEPTH && depth < max_depth) return true; - if (type == bv_funcs::DEC_DEPTH && depth > 0) + if (type == LyXText::DEC_DEPTH && depth > 0) return true; return false; } -} +bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const +{ + BOOST_ASSERT(this == cur.text()); + // this happens when selecting several cells in tabular (bug 2630) + if (cur.selBegin().idx() != cur.selEnd().idx()) + return false; + pit_type const beg = cur.selBegin().pit(); + pit_type const end = cur.selEnd().pit() + 1; + int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0); -bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type) -{ - ParagraphList::iterator beg, end; - getSelectionSpan(*this, beg, end); - int max_depth = 0; - if (beg != ownerParagraphs().begin()) - max_depth = boost::prior(beg)->getMaxDepthAfter(); - - for (ParagraphList::iterator pit = beg; pit != end; ++pit) { - if (::changeDepthAllowed(type, *pit, max_depth)) + for (pit_type pit = beg; pit != end; ++pit) { + if (lyx::changeDepthAllowed(type, pars_[pit], max_depth)) return true; - max_depth = pit->getMaxDepthAfter(); + max_depth = pars_[pit].getMaxDepthAfter(); } return false; } -void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type) +void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type) { - ParagraphList::iterator beg, end; - getSelectionSpan(*this, beg, end); - - recUndo(parOffset(beg), parOffset(end) - 1); - - int max_depth = 0; - if (beg != ownerParagraphs().begin()) - max_depth = boost::prior(beg)->getMaxDepthAfter(); - - for (ParagraphList::iterator pit = beg; pit != end; ++pit) { - if (::changeDepthAllowed(type, *pit, max_depth)) { - int const depth = pit->params().depth(); - if (type == bv_funcs::INC_DEPTH) - pit->params().depth(depth + 1); + BOOST_ASSERT(this == cur.text()); + pit_type const beg = cur.selBegin().pit(); + pit_type const end = cur.selEnd().pit() + 1; + recordUndoSelection(cur); + int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0); + + for (pit_type pit = beg; pit != end; ++pit) { + Paragraph & par = pars_[pit]; + if (lyx::changeDepthAllowed(type, par, max_depth)) { + int const depth = par.params().depth(); + if (type == INC_DEPTH) + par.params().depth(depth + 1); else - pit->params().depth(depth - 1); + par.params().depth(depth - 1); } - max_depth = pit->getMaxDepthAfter(); + max_depth = par.getMaxDepthAfter(); } // this handles the counter labels, and also fixes up // depth values for follow-on (child) paragraphs - updateCounters(); - redoCursor(); + updateLabels(cur.buffer()); } -// set font over selection and make a total rebreak of those paragraphs -void LyXText::setFont(LyXFont const & font, bool toggleall) +// set font over selection +void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall) { + BOOST_ASSERT(this == cur.text()); // if there is no selection just set the current_font - if (!selection.set()) { + if (!cur.selection()) { // Determine basis font LyXFont layoutfont; - if (cursor.pos() < cursorPar()->beginOfBody()) { - layoutfont = getLabelFont(cursorPar()); - } else { - layoutfont = getLayoutFont(cursorPar()); - } + pit_type pit = cur.pit(); + if (cur.pos() < pars_[pit].beginOfBody()) + layoutfont = getLabelFont(cur.buffer(), pars_[pit]); + else + layoutfont = getLayoutFont(cur.buffer(), pit); + // Update current font real_current_font.update(font, - bv()->buffer()->params().language, + cur.buffer().params().language, toggleall); // Reduce to implicit settings @@ -457,189 +461,152 @@ void LyXText::setFont(LyXFont const & font, bool toggleall) return; } - // ok we have a selection. - recUndo(selection.start.par(), selection.end.par()); - freezeUndo(); + // Ok, we have a selection. + recordUndoSelection(cur); - ParagraphList::iterator beg = getPar(selection.start.par()); - ParagraphList::iterator end = getPar(selection.end.par()); - - PosIterator pos(&ownerParagraphs(), beg, selection.start.pos()); - PosIterator posend(&ownerParagraphs(), end, selection.end.pos()); + DocIterator dit = cur.selectionBegin(); + DocIterator ditend = cur.selectionEnd(); - BufferParams const & params = bv()->buffer()->params(); + BufferParams const & params = cur.buffer().params(); - for (; pos != posend; ++pos) { - LyXFont f = getFont(pos.pit(), pos.pos()); - f.update(font, params.language, toggleall); - setCharFont(pos.pit(), pos.pos(), f); + // Don't use forwardChar here as ditend might have + // pos() == lastpos() and forwardChar would miss it. + // Can't use forwardPos either as this descends into + // nested insets. + for (; dit != ditend; dit.forwardPosNoDescend()) { + if (dit.pos() != dit.lastpos()) { + LyXFont f = getFont(cur.buffer(), dit.paragraph(), dit.pos()); + f.update(font, params.language, toggleall); + setCharFont(cur.buffer(), dit.pit(), dit.pos(), f); + } } - - unFreezeUndo(); - - redoParagraphs(beg, ++end); - redoCursor(); } -// important for the screen - - // the cursor set functions have a special mechanism. When they -// realize, that you left an empty paragraph, they will delete it. - -// need the selection cursor: -void LyXText::setSelection() -{ - TextCursor::setSelection(); -} - - -void LyXText::clearSelection() -{ - TextCursor::clearSelection(); +// realize you left an empty paragraph, they will delete it. - // reset this in the bv()! - if (bv() && bv()->text()) - bv()->text()->xsel_cache.set(false); -} - - -void LyXText::cursorHome() +bool LyXText::cursorHome(LCursor & cur) { - ParagraphList::iterator cpit = cursorPar(); - setCursor(cpit, cpit->getRow(cursor.pos())->pos()); + BOOST_ASSERT(this == cur.text()); + ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit()); + Row const & row = pm.getRow(cur.pos(),cur.boundary()); + return setCursor(cur, cur.pit(), row.pos()); } -void LyXText::cursorEnd() +bool LyXText::cursorEnd(LCursor & cur) { - ParagraphList::iterator cpit = cursorPar(); - pos_type end = cpit->getRow(cursor.pos())->endpos(); + BOOST_ASSERT(this == cur.text()); // if not on the last row of the par, put the cursor before - // the final space - setCursor(cpit, end == cpit->size() ? end : end - 1); + // the final space exept if I have a spanning inset or one string + // is so long that we force a break. + pos_type end = cur.textRow().endpos(); + if (end == 0) + // empty text, end-1 is no valid position + return false; + bool boundary = false; + if (end != cur.lastpos()) { + if (!cur.paragraph().isLineSeparator(end-1) + && !cur.paragraph().isNewline(end-1)) + boundary = true; + else + --end; + } + return setCursor(cur, cur.pit(), end, true, boundary); } -void LyXText::cursorTop() +bool LyXText::cursorTop(LCursor & cur) { - setCursor(ownerParagraphs().begin(), 0); + BOOST_ASSERT(this == cur.text()); + return setCursor(cur, 0, 0); } -void LyXText::cursorBottom() +bool LyXText::cursorBottom(LCursor & cur) { - ParagraphList::iterator lastpit = - boost::prior(ownerParagraphs().end()); - setCursor(lastpit, lastpit->size()); + BOOST_ASSERT(this == cur.text()); + return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size()); } -void LyXText::toggleFree(LyXFont const & font, bool toggleall) +void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall) { + BOOST_ASSERT(this == cur.text()); // If the mask is completely neutral, tell user if (font == LyXFont(LyXFont::ALL_IGNORE)) { // Could only happen with user style - bv()->owner()->message(_("No font change defined. " - "Use Character under the Layout menu to define font change.")); + cur.message(_("No font change defined. " + "Use Character under the Layout menu to define font change.")); return; } // Try implicit word selection // If there is a change in the language the implicit word selection // is disabled. - LyXCursor resetCursor = cursor; + CursorSlice resetCursor = cur.top(); bool implicitSelection = font.language() == ignore_language && font.number() == LyXFont::IGNORE - && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT); + && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT); // Set font - setFont(font, toggleall); + setFont(cur, font, toggleall); // Implicit selections are cleared afterwards - //and cursor is set to the original position. + // and cursor is set to the original position. if (implicitSelection) { - clearSelection(); - cursor = resetCursor; - setCursor(cursorPar(), cursor.pos()); - selection.cursor = cursor; + cur.clearSelection(); + cur.top() = resetCursor; + cur.resetAnchor(); } } -string LyXText::getStringToIndex() +docstring LyXText::getStringToIndex(LCursor const & cur) { - // Try implicit word selection - // If there is a change in the language the implicit word selection - // is disabled. - LyXCursor const reset_cursor = cursor; - bool const implicitSelection = - selectWordWhenUnderCursor(lyx::PREVIOUS_WORD); - - string idxstring; - if (!selection.set()) - bv()->owner()->message(_("Nothing to index!")); - else if (selection.start.par() != selection.end.par()) - bv()->owner()->message(_("Cannot index more than one paragraph!")); - else - idxstring = selectionAsString(*bv()->buffer(), false); + BOOST_ASSERT(this == cur.text()); - // Reset cursors to their original position. - cursor = reset_cursor; - setCursor(cursorPar(), cursor.pos()); - selection.cursor = cursor; + docstring idxstring; + if (cur.selection()) + idxstring = cur.selectionAsString(false); + else { + // Try implicit word selection. If there is a change + // in the language the implicit word selection is + // disabled. + LCursor tmpcur = cur; + selectWord(tmpcur, PREVIOUS_WORD); - // Clear the implicit selection. - if (implicitSelection) - clearSelection(); + if (!tmpcur.selection()) + cur.message(_("Nothing to index!")); + else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit()) + cur.message(_("Cannot index more than one paragraph!")); + else + idxstring = tmpcur.selectionAsString(false); + } return idxstring; } -// the DTP switches for paragraphs. LyX will store them in the first -// physical paragraph. When a paragraph is broken, the top settings rest, -// the bottom settings are given to the new one. So I can make sure, -// they do not duplicate themself and you cannot play dirty tricks with -// them! - -void LyXText::setParagraph( - Spacing const & spacing, - LyXAlignment align, - string const & labelwidthstring, - bool noindent) +void LyXText::setParagraph(LCursor & cur, + Spacing const & spacing, LyXAlignment align, + docstring const & labelwidthstring, bool noindent) { - setSelection(); + BOOST_ASSERT(cur.text()); // make sure that the depth behind the selection are restored, too - ParagraphList::iterator endpit = boost::next(getPar(selection.end)); - ParagraphList::iterator undoendpit = endpit; - ParagraphList::iterator pars_end = ownerParagraphs().end(); - - if (endpit != pars_end && endpit->getDepth()) { - while (endpit != pars_end && endpit->getDepth()) { - ++endpit; - undoendpit = endpit; - } - } else if (endpit != pars_end) { - // because of parindents etc. - ++endpit; - } - - recUndo(selection.start.par(), parOffset(undoendpit) - 1); - - int tmppit = selection.end.par(); - - while (tmppit != selection.start.par() - 1) { - setCursor(tmppit, 0); + pit_type undopit = undoSpan(cur.selEnd().pit()); + recUndo(cur, cur.selBegin().pit(), undopit - 1); - ParagraphList::iterator const pit = cursorPar(); - ParagraphParameters & params = pit->params(); + for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit(); + pit <= end; ++pit) { + Paragraph & par = pars_[pit]; + ParagraphParameters & params = par.params(); params.spacing(spacing); // does the layout allow the new alignment? - LyXLayout_ptr const & layout = pit->layout(); + LyXLayout_ptr const & layout = par.layout(); if (align == LYX_ALIGN_LAYOUT) align = layout->align; @@ -649,538 +616,43 @@ void LyXText::setParagraph( else params.align(align); } - pit->setLabelWidthString(labelwidthstring); + par.setLabelWidthString(labelwidthstring); params.noindent(noindent); - --tmppit; - } - - redoParagraphs(getPar(selection.start), endpit); - redoCursor(); -} - - -string expandLabel(LyXTextClass const & textclass, - LyXLayout_ptr const & layout, bool appendix) -{ - string fmt = appendix ? - layout->labelstring_appendix() : layout->labelstring(); - - // handle 'inherited level parts' in 'fmt', - // i.e. the stuff between '@' in '@Section@.\arabic{subsection}' - size_t const i = fmt.find('@', 0); - if (i != string::npos) { - size_t const j = fmt.find('@', i + 1); - if (j != string::npos) { - string parent(fmt, i + 1, j - i - 1); - string label = expandLabel(textclass, textclass[parent], appendix); - fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos); - } - } - - return textclass.counters().counterLabel(fmt); -} - - -namespace { - -void incrementItemDepth(ParagraphList::iterator pit, - ParagraphList::iterator first_pit) -{ - int const cur_labeltype = pit->layout()->labeltype; - - if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE) - return; - - int const cur_depth = pit->getDepth(); - - ParagraphList::iterator prev_pit = boost::prior(pit); - while (true) { - int const prev_depth = prev_pit->getDepth(); - int const prev_labeltype = prev_pit->layout()->labeltype; - if (prev_depth == 0 && cur_depth > 0) { - if (prev_labeltype == cur_labeltype) { - pit->itemdepth = prev_pit->itemdepth + 1; - } - break; - } else if (prev_depth < cur_depth) { - if (prev_labeltype == cur_labeltype) { - pit->itemdepth = prev_pit->itemdepth + 1; - break; - } - } else if (prev_depth == cur_depth) { - if (prev_labeltype == cur_labeltype) { - pit->itemdepth = prev_pit->itemdepth; - break; - } - } - if (prev_pit == first_pit) - break; - - --prev_pit; - } -} - - -void resetEnumCounterIfNeeded(ParagraphList::iterator pit, - ParagraphList::iterator firstpit, - Counters & counters) -{ - if (pit == firstpit) - return; - - int const cur_depth = pit->getDepth(); - ParagraphList::iterator prev_pit = boost::prior(pit); - while (true) { - int const prev_depth = prev_pit->getDepth(); - int const prev_labeltype = prev_pit->layout()->labeltype; - if (prev_depth <= cur_depth) { - if (prev_labeltype != LABEL_ENUMERATE) { - switch (pit->itemdepth) { - case 0: - counters.reset("enumi"); - case 1: - counters.reset("enumii"); - case 2: - counters.reset("enumiii"); - case 3: - counters.reset("enumiv"); - } - } - break; - } - - if (prev_pit == firstpit) - break; - - --prev_pit; - } -} - -} // anon namespace - - -// set the counter of a paragraph. This includes the labels -void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit) -{ - BufferParams const & bufparams = buf.params(); - LyXTextClass const & textclass = bufparams.getLyXTextClass(); - LyXLayout_ptr const & layout = pit->layout(); - ParagraphList::iterator first_pit = ownerParagraphs().begin(); - Counters & counters = textclass.counters(); - - // Always reset - pit->itemdepth = 0; - - if (pit == first_pit) { - pit->params().appendix(pit->params().startOfAppendix()); - } else { - pit->params().appendix(boost::prior(pit)->params().appendix()); - if (!pit->params().appendix() && - pit->params().startOfAppendix()) { - pit->params().appendix(true); - textclass.counters().reset(); - } - - // Maybe we have to increment the item depth. - incrementItemDepth(pit, first_pit); - } - - // erase what was there before - pit->params().labelString(string()); - - if (layout->margintype == MARGIN_MANUAL) { - if (pit->params().labelWidthString().empty()) - pit->setLabelWidthString(layout->labelstring()); - } else { - pit->setLabelWidthString(string()); - } - - // is it a layout that has an automatic label? - if (layout->labeltype == LABEL_COUNTER) { - BufferParams const & bufparams = buf.params(); - LyXTextClass const & textclass = bufparams.getLyXTextClass(); - counters.step(layout->counter); - string label = expandLabel(textclass, layout, pit->params().appendix()); - pit->params().labelString(label); - } else if (layout->labeltype == LABEL_ITEMIZE) { - // At some point of time we should do something more - // clever here, like: - // pit->params().labelString( - // bufparams.user_defined_bullet(pit->itemdepth).getText()); - // for now, use a simple hardcoded label - string itemlabel; - switch (pit->itemdepth) { - case 0: - itemlabel = "*"; - break; - case 1: - itemlabel = "-"; - break; - case 2: - itemlabel = "@"; - break; - case 3: - itemlabel = "ยท"; - break; - } - - pit->params().labelString(itemlabel); - } else if (layout->labeltype == LABEL_ENUMERATE) { - // Maybe we have to reset the enumeration counter. - resetEnumCounterIfNeeded(pit, first_pit, counters); - - // FIXME - // Yes I know this is a really, really! bad solution - // (Lgb) - string enumcounter = "enum"; - - switch (pit->itemdepth) { - case 2: - enumcounter += 'i'; - case 1: - enumcounter += 'i'; - case 0: - enumcounter += 'i'; - break; - case 3: - enumcounter += "iv"; - break; - default: - // not a valid enumdepth... - break; - } - - counters.step(enumcounter); - - pit->params().labelString(counters.enumLabel(enumcounter)); - } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302 - counters.step("bibitem"); - int number = counters.value("bibitem"); - if (pit->bibitem()) { - pit->bibitem()->setCounter(number); - pit->params().labelString(layout->labelstring()); - } - // In biblio should't be following counters but... - } else { - string s = buf.B_(layout->labelstring()); - - // the caption hack: - if (layout->labeltype == LABEL_SENSITIVE) { - ParagraphList::iterator end = ownerParagraphs().end(); - ParagraphList::iterator tmppit = pit; - InsetOld * in = 0; - bool isOK = false; - while (tmppit != end && tmppit->inInset() - // the single '=' is intended below - && (in = tmppit->inInset()->owner())) - { - if (in->lyxCode() == InsetOld::FLOAT_CODE || - in->lyxCode() == InsetOld::WRAP_CODE) { - isOK = true; - break; - } else { - Paragraph const * owner = &ownerPar(buf, in); - tmppit = first_pit; - for ( ; tmppit != end; ++tmppit) - if (&*tmppit == owner) - break; - } - } - - if (isOK) { - string type; - - if (in->lyxCode() == InsetOld::FLOAT_CODE) - type = static_cast(in)->params().type; - else if (in->lyxCode() == InsetOld::WRAP_CODE) - type = static_cast(in)->params().type; - else - BOOST_ASSERT(false); - - Floating const & fl = textclass.floats().getType(type); - - counters.step(fl.type()); - - // Doesn't work... yet. - s = bformat(_("%1$s #:"), buf.B_(fl.name())); - } else { - // par->SetLayout(0); - // s = layout->labelstring; - s = _("Senseless: "); - } - } - pit->params().labelString(s); - - } -} - - -// Updates all counters. -void LyXText::updateCounters() -{ - // start over - bv()->buffer()->params().getLyXTextClass().counters().reset(); - - bool update_pos = false; - - ParagraphList::iterator beg = ownerParagraphs().begin(); - ParagraphList::iterator end = ownerParagraphs().end(); - for (ParagraphList::iterator pit = beg; pit != end; ++pit) { - string const oldLabel = pit->params().labelString(); - size_t maxdepth = 0; - if (pit != beg) - maxdepth = boost::prior(pit)->getMaxDepthAfter(); - - if (pit->params().depth() > maxdepth) - pit->params().depth(maxdepth); - - // setCounter can potentially change the labelString. - setCounter(*bv()->buffer(), pit); - string const & newLabel = pit->params().labelString(); - if (oldLabel != newLabel) { - redoParagraphInternal(pit); - update_pos = true; - } - - } - if (update_pos) - updateParPositions(); -} - - -void LyXText::insertInset(InsetOld * inset) -{ - if (!cursorPar()->insetAllowed(inset->lyxCode())) - return; - - recUndo(cursor.par()); - freezeUndo(); - cursorPar()->insertInset(cursor.pos(), inset); - // Just to rebreak and refresh correctly. - // The character will not be inserted a second time - insertChar(Paragraph::META_INSET); - // If we enter a highly editable inset the cursor should be before - // the inset. After an undo LyX tries to call inset->edit(...) - // and fails if the cursor is behind the inset and getInset - // does not return the inset! - if (isHighlyEditableInset(inset)) - cursorLeft(true); - - unFreezeUndo(); -} - - -void LyXText::cutSelection(bool doclear, bool realcut) -{ - // Stuff what we got on the clipboard. Even if there is no selection. - - // There is a problem with having the stuffing here in that the - // larger the selection the slower LyX will get. This can be - // solved by running the line below only when the selection has - // finished. The solution used currently just works, to make it - // faster we need to be more clever and probably also have more - // calls to stuffClipboard. (Lgb) - bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true)); - - // This doesn't make sense, if there is no selection - if (!selection.set()) - return; - - // OK, we have a selection. This is always between selection.start - // and selection.end - - // make sure that the depth behind the selection are restored, too - ParagraphList::iterator endpit = boost::next(getPar(selection.end.par())); - ParagraphList::iterator undoendpit = endpit; - ParagraphList::iterator pars_end = ownerParagraphs().end(); - - if (endpit != pars_end && endpit->getDepth()) { - while (endpit != pars_end && endpit->getDepth()) { - ++endpit; - undoendpit = endpit; - } - } else if (endpit != pars_end) { - // because of parindents etc. - ++endpit; } - - recUndo(selection.start.par(), parOffset(undoendpit) - 1); - - endpit = getPar(selection.end.par()); - int endpos = selection.end.pos(); - - BufferParams const & bufparams = bv()->buffer()->params(); - boost::tie(endpit, endpos) = realcut ? - CutAndPaste::cutSelection(bufparams, - ownerParagraphs(), - getPar(selection.start.par()), endpit, - selection.start.pos(), endpos, - bufparams.textclass, - doclear) - : CutAndPaste::eraseSelection(bufparams, - ownerParagraphs(), - getPar(selection.start.par()), endpit, - selection.start.pos(), endpos, - doclear); - // sometimes necessary - if (doclear) - getPar(selection.start.par())->stripLeadingSpaces(); - - redoParagraphs(getPar(selection.start.par()), boost::next(endpit)); - // cutSelection can invalidate the cursor so we need to set - // it anew. (Lgb) - // we prefer the end for when tracking changes - cursor.pos(endpos); - cursor.par(parOffset(endpit)); - - // need a valid cursor. (Lgb) - clearSelection(); - - setCursor(cursorPar(), cursor.pos()); - selection.cursor = cursor; - updateCounters(); } -void LyXText::copySelection() +// this really should just insert the inset and not move the cursor. +void LyXText::insertInset(LCursor & cur, InsetBase * inset) { - // stuff the selection onto the X clipboard, from an explicit copy request - bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true)); - - // this doesnt make sense, if there is no selection - if (!selection.set()) - return; - - // ok we have a selection. This is always between selection.start - // and sel_end cursor - - // copy behind a space if there is one - while (getPar(selection.start)->size() > selection.start.pos() - && getPar(selection.start)->isLineSeparator(selection.start.pos()) - && (selection.start.par() != selection.end.par() - || selection.start.pos() < selection.end.pos())) - selection.start.pos(selection.start.pos() + 1); - - CutAndPaste::copySelection(getPar(selection.start.par()), - getPar(selection.end.par()), - selection.start.pos(), selection.end.pos(), - bv()->buffer()->params().textclass); -} - - -void LyXText::pasteSelection(size_t sel_index) -{ - // this does not make sense, if there is nothing to paste - if (!CutAndPaste::checkPastePossible()) - return; - - recUndo(cursor.par()); - - ParagraphList::iterator endpit; - PitPosPair ppp; - - ErrorList el; - - boost::tie(ppp, endpit) = - CutAndPaste::pasteSelection(*bv()->buffer(), - ownerParagraphs(), - cursorPar(), cursor.pos(), - bv()->buffer()->params().textclass, - sel_index, el); - bufferErrors(*bv()->buffer(), el); - bv()->showErrorList(_("Paste")); - - redoParagraphs(cursorPar(), endpit); - - setCursor(cursor.par(), cursor.pos()); - clearSelection(); - - selection.cursor = cursor; - setCursor(ppp.first, ppp.second); - setSelection(); - updateCounters(); -} - - -void LyXText::setSelectionRange(lyx::pos_type length) -{ - if (!length) - return; - - selection.cursor = cursor; - while (length--) - cursorRight(true); - setSelection(); -} - - -// simple replacing. The font of the first selected character is used -void LyXText::replaceSelectionWithString(string const & str) -{ - recUndo(cursor.par()); - freezeUndo(); - - if (!selection.set()) { // create a dummy selection - selection.end = cursor; - selection.start = cursor; - } - - // Get font setting before we cut - pos_type pos = selection.end.pos(); - LyXFont const font = getPar(selection.start) - ->getFontSettings(bv()->buffer()->params(), - selection.start.pos()); - - // Insert the new string - string::const_iterator cit = str.begin(); - string::const_iterator end = str.end(); - for (; cit != end; ++cit) { - getPar(selection.end)->insertChar(pos, (*cit), font); - ++pos; - } - - // Cut the selection - cutSelection(true, false); - - unFreezeUndo(); + BOOST_ASSERT(this == cur.text()); + BOOST_ASSERT(inset); + cur.paragraph().insertInset(cur.pos(), inset, + Change(cur.buffer().params().trackChanges ? + Change::INSERTED : Change::UNCHANGED)); } // needed to insert the selection -void LyXText::insertStringAsLines(string const & str) +void LyXText::insertStringAsLines(LCursor & cur, docstring const & str) { - ParagraphList::iterator pit = cursorPar(); - pos_type pos = cursor.pos(); - ParagraphList::iterator endpit = boost::next(cursorPar()); - - recUndo(cursor.par()); - - // only to be sure, should not be neccessary - clearSelection(); - - bv()->buffer()->insertStringAsLines(pit, pos, current_font, str); - - redoParagraphs(cursorPar(), endpit); - setCursor(cursorPar(), cursor.pos()); - selection.cursor = cursor; - setCursor(pit, pos); - setSelection(); + cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(), + current_font, str, autoBreakRows_); } -// turns double-CR to single CR, others where converted into one -// blank. Then InsertStringAsLines is called -void LyXText::insertStringAsParagraphs(string const & str) +// turn double CR to single CR, others are converted into one +// blank. Then insertStringAsLines is called +void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str) { - string linestr(str); + docstring linestr = str; bool newline_inserted = false; - string::size_type const siz = linestr.length(); - for (string::size_type i = 0; i < siz; ++i) { + for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) { if (linestr[i] == '\n') { if (newline_inserted) { // we know that \r will be ignored by - // InsertStringA. Of course, it is a dirty + // insertStringAsLines. Of course, it is a dirty // trick, but it works... linestr[i - 1] = '\r'; linestr[i] = '\n'; @@ -1188,202 +660,89 @@ void LyXText::insertStringAsParagraphs(string const & str) linestr[i] = ' '; newline_inserted = true; } - } else if (IsPrintable(linestr[i])) { + } else if (isPrintable(linestr[i])) { newline_inserted = false; } } - insertStringAsLines(linestr); + insertStringAsLines(cur, linestr); } -void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos) +bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos, + bool setfont, bool boundary) { - setCursor(parOffset(pit), pos); + LCursor old = cur; + setCursorIntern(cur, par, pos, setfont, boundary); + return cur.bv().checkDepm(cur, old); } -bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont, bool boundary) +void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos) { - LyXCursor old_cursor = cursor; - setCursorIntern(par, pos, setfont, boundary); - return deleteEmptyParagraphMechanism(old_cursor); -} + BOOST_ASSERT(par != int(paragraphs().size())); + cur.pit() = par; + cur.pos() = pos; - -void LyXText::redoCursor() -{ - setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary()); - - if (!selection.set()) - return; - - LyXCursor tmpcursor = cursor; - setCursor(selection.cursor.par(), selection.cursor.pos()); - selection.cursor = cursor; - setCursor(tmpcursor.par(), tmpcursor.pos()); - setSelection(); -} - - -void LyXText::setCursor(LyXCursor & cur, paroffset_type par, - pos_type pos, bool boundary) -{ - BOOST_ASSERT(par != int(ownerParagraphs().size())); - - cur.par(par); - cur.pos(pos); - cur.boundary(boundary); - - // no rows, no fun... - if (ownerParagraphs().begin()->rows.empty()) - return; - - // get the cursor y position in text - - ParagraphList::iterator pit = getPar(par); - Row const & row = *pit->getRow(pos); - - int y = pit->y + row.y_offset(); - - // y is now the beginning of the cursor row - y += row.baseline(); - // y is now the cursor baseline - cur.y(y); - - pos_type const end = row.endpos(); + // now some strict checking + Paragraph & para = getPar(par); // None of these should happen, but we're scaredy-cats if (pos < 0) { lyxerr << "dont like -1" << endl; - pos = 0; - cur.pos(0); - BOOST_ASSERT(false); - } else if (pos > pit->size()) { - lyxerr << "dont like 1, pos: " << pos - << " size: " << pit->size() - << " row.pos():" << row.pos() - << " paroffset: " << par << endl; - pos = 0; - cur.pos(0); - BOOST_ASSERT(false); - } else if (pos > end) { - lyxerr << "dont like 2 please report" << endl; - // This shouldn't happen. - pos = end; - cur.pos(pos); - BOOST_ASSERT(false); - } else if (pos < row.pos()) { - lyxerr << "dont like 3 please report pos:" << pos - << " size: " << pit->size() - << " row.pos():" << row.pos() - << " paroffset: " << par << endl; - pos = row.pos(); - cur.pos(pos); BOOST_ASSERT(false); } - // now get the cursors x position - cur.x(int(getCursorX(pit, row, pos, boundary))); -} - - -float LyXText::getCursorX(ParagraphList::iterator pit, Row const & row, - pos_type pos, bool boundary) const -{ - pos_type cursor_vpos = 0; - double x = row.x(); - double fill_separator = row.fill_separator(); - double fill_hfill = row.fill_hfill(); - double fill_label_hfill = row.fill_label_hfill(); - pos_type const row_pos = row.pos(); - pos_type const end = row.endpos(); - - if (end <= row_pos) - cursor_vpos = row_pos; - else if (pos >= end && !boundary) - cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params())) - ? row_pos : end; - else if (pos > row_pos && (pos >= end || boundary)) - // Place cursor after char at (logical) position pos - 1 - cursor_vpos = (bidi.level(pos - 1) % 2 == 0) - ? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1); - else - // Place cursor before char at (logical) position pos - cursor_vpos = (bidi.level(pos) % 2 == 0) - ? bidi.log2vis(pos) : bidi.log2vis(pos) + 1; - - pos_type body_pos = pit->beginOfBody(); - if (body_pos > 0 && - (body_pos > end || !pit->isLineSeparator(body_pos - 1))) - body_pos = 0; - - for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) { - pos_type pos = bidi.vis2log(vpos); - if (body_pos > 0 && pos == body_pos - 1) { - x += fill_label_hfill - + font_metrics::width(pit->layout()->labelsep, - getLabelFont(pit)); - if (pit->isLineSeparator(body_pos - 1)) - x -= singleWidth(pit, body_pos - 1); - } - if (hfillExpansion(*pit, row, pos)) { - x += singleWidth(pit, pos); - if (pos >= body_pos) - x += fill_hfill; - else - x += fill_label_hfill; - } else if (pit->isSeparator(pos)) { - x += singleWidth(pit, pos); - if (pos >= body_pos) - x += fill_separator; - } else - x += singleWidth(pit, pos); + if (pos > para.size()) { + lyxerr << "dont like 1, pos: " << pos + << " size: " << para.size() + << " par: " << par << endl; + BOOST_ASSERT(false); } - return x; } -void LyXText::setCursorIntern(paroffset_type par, - pos_type pos, bool setfont, bool boundary) +void LyXText::setCursorIntern(LCursor & cur, + pit_type par, pos_type pos, bool setfont, bool boundary) { - setCursor(cursor, par, pos, boundary); - bv()->x_target(cursor.x() + xo_); + BOOST_ASSERT(this == cur.text()); + cur.boundary(boundary); + setCursor(cur.top(), par, pos); if (setfont) - setCurrentFont(); + setCurrentFont(cur); } -void LyXText::setCurrentFont() +void LyXText::setCurrentFont(LCursor & cur) { - pos_type pos = cursor.pos(); - ParagraphList::iterator pit = cursorPar(); + BOOST_ASSERT(this == cur.text()); + pos_type pos = cur.pos(); + Paragraph & par = cur.paragraph(); - if (cursor.boundary() && pos > 0) + if (cur.boundary() && pos > 0) --pos; if (pos > 0) { - if (pos == pit->size()) + if (pos == cur.lastpos()) --pos; else // potentional bug... BUG (Lgb) - if (pit->isSeparator(pos)) { - if (pos > pit->getRow(pos)->pos() && + if (par.isSeparator(pos)) { + if (pos > cur.textRow().pos() && bidi.level(pos) % 2 == bidi.level(pos - 1) % 2) --pos; - else if (pos + 1 < pit->size()) + else if (pos + 1 < cur.lastpos()) ++pos; } } - BufferParams const & bufparams = bv()->buffer()->params(); - current_font = pit->getFontSettings(bufparams, pos); - real_current_font = getFont(pit, pos); + BufferParams const & bufparams = cur.buffer().params(); + current_font = par.getFontSettings(bufparams, pos); + real_current_font = getFont(cur.buffer(), par, pos); - if (cursor.pos() == pit->size() && - bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) && - !cursor.boundary()) { - Language const * lang = - pit->getParLanguage(bufparams); + if (cur.pos() == cur.lastpos() + && bidi.isBoundary(cur.buffer(), par, cur.pos()) + && !cur.boundary()) { + Language const * lang = par.getParLanguage(bufparams); current_font.setLanguage(lang); current_font.setNumber(LyXFont::OFF); real_current_font.setLanguage(lang); @@ -1391,502 +750,550 @@ void LyXText::setCurrentFont() } } - -// returns the column near the specified x-coordinate of the row -// x is set to the real beginning of this column -pos_type LyXText::getColumnNearX(ParagraphList::iterator pit, - Row const & row, int & x, bool & boundary) const -{ - double tmpx = row.x(); - double fill_separator = row.fill_separator(); - double fill_hfill = row.fill_hfill(); - double fill_label_hfill = row.fill_label_hfill(); - - pos_type vc = row.pos(); - pos_type end = row.endpos(); - pos_type c = 0; - LyXLayout_ptr const & layout = pit->layout(); - - bool left_side = false; - - pos_type body_pos = pit->beginOfBody(); - double last_tmpx = tmpx; - - if (body_pos > 0 && - (body_pos > end || - !pit->isLineSeparator(body_pos - 1))) - body_pos = 0; - - // check for empty row - if (vc == end) { - x = int(tmpx); - return 0; +// y is screen coordinate +pit_type LyXText::getPitNearY(BufferView & bv, int y) const +{ + BOOST_ASSERT(!paragraphs().empty()); + BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end()); + CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second; + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": y: " << y << " cache size: " << cc.size() + << endl; + + // look for highest numbered paragraph with y coordinate less than given y + pit_type pit = 0; + int yy = -1; + CoordCache::InnerParPosCache::const_iterator it = cc.begin(); + CoordCache::InnerParPosCache::const_iterator et = cc.end(); + CoordCache::InnerParPosCache::const_iterator last = et; last--; + + TextMetrics & tm = bv.textMetrics(this); + ParagraphMetrics const & pm = tm.parMetrics(it->first); + + // If we are off-screen (before the visible part) + if (y < 0 + // and even before the first paragraph in the cache. + && y < it->second.y_ - int(pm.ascent())) { + // and we are not at the first paragraph in the inset. + if (it->first == 0) + return 0; + // then this is the paragraph we are looking for. + pit = it->first - 1; + // rebreak it and update the CoordCache. + tm.redoParagraph(pit); + bv.coordCache().parPos()[this][pit] = + Point(0, it->second.y_ - pm.descent()); + return pit; } - while (vc < end && tmpx <= x) { - c = bidi.vis2log(vc); - last_tmpx = tmpx; - if (body_pos > 0 && c == body_pos - 1) { - tmpx += fill_label_hfill + - font_metrics::width(layout->labelsep, getLabelFont(pit)); - if (pit->isLineSeparator(body_pos - 1)) - tmpx -= singleWidth(pit, body_pos - 1); - } - - if (hfillExpansion(*pit, row, c)) { - tmpx += singleWidth(pit, c); - if (c >= body_pos) - tmpx += fill_hfill; - else - tmpx += fill_label_hfill; - } else if (pit->isSeparator(c)) { - tmpx += singleWidth(pit, c); - if (c >= body_pos) - tmpx += fill_separator; - } else { - tmpx += singleWidth(pit, c); - } - ++vc; + ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first); + + // If we are off-screen (after the visible part) + if (y > bv.workHeight() + // and even after the first paragraph in the cache. + && y >= last->second.y_ + int(pm_last.descent())) { + pit = last->first + 1; + // and we are not at the last paragraph in the inset. + if (pit == int(pars_.size())) + return last->first; + // then this is the paragraph we are looking for. + // rebreak it and update the CoordCache. + tm.redoParagraph(pit); + bv.coordCache().parPos()[this][pit] = + Point(0, last->second.y_ + pm_last.ascent()); + return pit; } - if ((tmpx + last_tmpx) / 2 > x) { - tmpx = last_tmpx; - left_side = true; - } + for (; it != et; ++it) { + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << " examining: pit: " << it->first + << " y: " << it->second.y_ + << endl; - BOOST_ASSERT(vc <= end); // This shouldn't happen. - - boundary = false; - // This (rtl_support test) is not needed, but gives - // some speedup if rtl_support == false - bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size(); - - // If lastrow is false, we don't need to compute - // the value of rtl. - bool const rtl = (lastrow) - ? pit->isRightToLeftPar(bv()->buffer()->params()) - : false; - if (lastrow && - ((rtl && left_side && vc == row.pos() && x < tmpx - 5) || - (!rtl && !left_side && vc == end && x > tmpx + 5))) - c = end; - else if (vc == row.pos()) { - c = bidi.vis2log(vc); - if (bidi.level(c) % 2 == 1) - ++c; - } else { - c = bidi.vis2log(vc - 1); - bool const rtl = (bidi.level(c) % 2 == 1); - if (left_side == rtl) { - ++c; - boundary = bidi.isBoundary(*bv()->buffer(), *pit, c); + ParagraphMetrics const & pm = bv.parMetrics(this, it->first); + + if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) { + pit = it->first; + yy = it->second.y_; } } - if (row.pos() < end && c >= end && pit->isNewline(end - 1)) { - if (bidi.level(end -1) % 2 == 0) - tmpx -= singleWidth(pit, end - 1); - else - tmpx += singleWidth(pit, end - 1); - c = end - 1; - } + LYXERR(Debug::DEBUG) + << BOOST_CURRENT_FUNCTION + << ": found best y: " << yy << " for pit: " << pit + << endl; - c -= row.pos(); - x = int(tmpx); - return c; + return pit; } -void LyXText::setCursorFromCoordinates(int x, int y) +Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const { - LyXCursor old_cursor = cursor; - setCursorFromCoordinates(cursor, x, y); - setCurrentFont(); - deleteEmptyParagraphMechanism(old_cursor); + ParagraphMetrics const & pm = bv.parMetrics(this, pit); + + int yy = bv.coordCache().get(this, pit).y_ - pm.ascent(); + BOOST_ASSERT(!pm.rows().empty()); + RowList::const_iterator rit = pm.rows().begin(); + RowList::const_iterator const rlast = boost::prior(pm.rows().end()); + for (; rit != rlast; yy += rit->height(), ++rit) + if (yy + rit->height() > y) + break; + return *rit; } -// x,y are coordinates relative to this LyXText -void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y) + +// x,y are absolute screen coordinates +// sets cursor recursively descending into nested editable insets +InsetBase * LyXText::editXY(LCursor & cur, int x, int y) { - // Get the row first. - ParagraphList::iterator pit; - Row const & row = *getRowNearY(y, pit); - y = pit->y + row.y_offset(); + if (lyxerr.debugging(Debug::WORKAREA)) { + lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl; + cur.bv().coordCache().dump(); + } + pit_type pit = getPitNearY(cur.bv(), y); + BOOST_ASSERT(pit != -1); + Row const & row = getRowNearY(cur.bv(), y, pit); bool bound = false; - pos_type const column = getColumnNearX(pit, row, x, bound); - cur.par(parOffset(pit)); - cur.pos(row.pos() + column); - cur.x(x); - cur.y(y + row.baseline()); + TextMetrics const & tm = cur.bv().textMetrics(this); + int xx = x; // is modified by getColumnNearX + pos_type const pos = row.pos() + + tm.getColumnNearX(pit, row, xx, bound); + cur.pit() = pit; + cur.pos() = pos; cur.boundary(bound); + cur.x_target() = x; + + // try to descend into nested insets + InsetBase * inset = checkInsetHit(cur.bv(), x, y); + //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl; + if (!inset) { + // Either we deconst editXY or better we move current_font + // and real_current_font to LCursor + setCurrentFont(cur); + return 0; + } + + InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0; + //InsetBase * insetBehind = pars_[pit].getInset(pos); + + // This should be just before or just behind the + // cursor position set above. + BOOST_ASSERT((pos != 0 && inset == insetBefore) + || inset == pars_[pit].getInset(pos)); + + // Make sure the cursor points to the position before + // this inset. + if (inset == insetBefore) + --cur.pos(); + + // Try to descend recursively inside the inset. + inset = inset->editXY(cur, x, y); + + if (cur.top().text() == this) + setCurrentFont(cur); + return inset; } -bool LyXText::checkAndActivateInset(bool front) +bool LyXText::checkAndActivateInset(LCursor & cur, bool front) { - if (cursor.pos() == cursorPar()->size()) + if (cur.selection()) + return false; + if (cur.pos() == cur.lastpos()) return false; - InsetOld * inset = cursorPar()->getInset(cursor.pos()); + InsetBase * inset = cur.nextInset(); if (!isHighlyEditableInset(inset)) return false; - inset->edit(bv(), front); + inset->edit(cur, front); return true; } -DispatchResult LyXText::moveRight() +bool LyXText::cursorLeft(LCursor & cur) { - if (cursorPar()->isRightToLeftPar(bv()->buffer()->params())) - return moveLeftIntern(false, true, false); - else - return moveRightIntern(true, true, false); -} + // Tell BufferView to test for FitCursor in any case! + cur.updateFlags(Update::FitCursor); + if (!cur.boundary() && cur.pos() > 0 && + cur.textRow().pos() == cur.pos() && + !cur.paragraph().isLineSeparator(cur.pos()-1) && + !cur.paragraph().isNewline(cur.pos()-1)) { + return setCursor(cur, cur.pit(), cur.pos(), true, true); + } + if (cur.pos() != 0) { + bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false); + if (!checkAndActivateInset(cur, false)) { + /** FIXME: What's this cause purpose??? + bool boundary = cur.boundary(); + if (false && !boundary && + bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1)) + updateNeeded |= + setCursor(cur, cur.pit(), cur.pos() + 1, true, true); + */ + } + return updateNeeded; + } -DispatchResult LyXText::moveLeft() -{ - if (cursorPar()->isRightToLeftPar(bv()->buffer()->params())) - return moveRightIntern(true, true, false); - else - return moveLeftIntern(false, true, false); + if (cur.pit() != 0) { + // Steps into the paragraph above + return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size()); + } + return false; } -DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting) +bool LyXText::cursorRight(LCursor & cur) { - ParagraphList::iterator c_par = cursorPar(); - if (boost::next(c_par) == ownerParagraphs().end() - && cursor.pos() >= c_par->size()) - return DispatchResult(false, FINISHED_RIGHT); - if (activate_inset && checkAndActivateInset(front)) - return DispatchResult(true, true); - cursorRight(true); - if (!selecting) - clearSelection(); - return DispatchResult(true); -} - + // Tell BufferView to test for FitCursor in any case! + cur.updateFlags(Update::FitCursor); -DispatchResult LyXText::moveLeftIntern(bool front, - bool activate_inset, bool selecting) -{ - if (cursor.par() == 0 && cursor.pos() <= 0) - return DispatchResult(false, FINISHED); - cursorLeft(true); - if (!selecting) - clearSelection(); - if (activate_inset && checkAndActivateInset(front)) - return DispatchResult(true, true); - return DispatchResult(true); -} + if (cur.pos() != cur.lastpos()) { + if (cur.boundary()) + return setCursor(cur, cur.pit(), cur.pos(), + true, false); + bool updateNeeded = false; + if (!checkAndActivateInset(cur, true)) { + if (cur.textRow().endpos() == cur.pos() + 1 && + cur.textRow().endpos() != cur.lastpos() && + !cur.paragraph().isLineSeparator(cur.pos()) && + !cur.paragraph().isNewline(cur.pos())) { + cur.boundary(true); + } + updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary()); + if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(), + cur.pos())) + updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true); + } + return updateNeeded; + } -DispatchResult LyXText::moveUp() -{ - if (cursorPar() == firstPar() && cursorRow() == firstRow()) - return DispatchResult(false, FINISHED_UP); - cursorUp(false); - clearSelection(); - return DispatchResult(true); + if (cur.pit() != cur.lastpit()) + return setCursor(cur, cur.pit() + 1, 0); + return false; } -DispatchResult LyXText::moveDown() +bool LyXText::cursorUp(LCursor & cur) { - if (cursorPar() == lastPar() && cursorRow() == lastRow()) - return DispatchResult(false, FINISHED_DOWN); - cursorDown(false); - clearSelection(); - return DispatchResult(true); -} + // Tell BufferView to test for FitCursor in any case! + cur.updateFlags(Update::FitCursor); + TextMetrics const & tm = cur.bv().textMetrics(this); + ParagraphMetrics const & pm = tm.parMetrics(cur.pit()); -bool LyXText::cursorLeft(bool internal) -{ - if (cursor.pos() > 0) { - bool boundary = cursor.boundary(); - setCursor(cursor.par(), cursor.pos() - 1, true, false); - if (!internal && !boundary && - bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1)) - setCursor(cursor.par(), cursor.pos() + 1, true, true); - return true; + int row; + if (cur.pos() && cur.boundary()) + row = pm.pos2row(cur.pos()-1); + else + row = pm.pos2row(cur.pos()); + + // remember current position only if we are not at the end of a row. + if (cur.pos() != pm.rows()[row].endpos()) + cur.setTargetX(); + int const x = cur.targetX(); + + if (!cur.selection()) { + int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_; + LCursor old = cur; + // Go to middle of previous row. 16 found to work OK; + // 12 = top/bottom margin of display math + int const margin = 3 * InsetMathHull::displayMargin() / 2; + editXY(cur, x, y - pm.rows()[row].ascent() - margin); + cur.clearSelection(); + + // This happens when you move out of an inset. + // And to give the DEPM the possibility of doing + // something we must provide it with two different + // cursors. (Lgb) + LCursor dummy = cur; + if (dummy == old) + ++dummy.pos(); + + cur.bv().checkDepm(dummy, old); + return false; } - if (cursor.par() != 0) { - // steps into the paragraph above - setCursor(cursor.par() - 1, boost::prior(cursorPar())->size()); - return true; + bool updateNeeded = false; + + if (row > 0) { + updateNeeded |= setCursor(cur, cur.pit(), + tm.x2pos(cur.pit(), row - 1, x)); + } else if (cur.pit() > 0) { + --cur.pit(); + //cannot use 'par' now + ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit()); + updateNeeded |= setCursor(cur, cur.pit(), + tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x)); } - return false; + cur.x_target() = x; + + return updateNeeded; } -bool LyXText::cursorRight(bool internal) +bool LyXText::cursorDown(LCursor & cur) { - if (!internal && cursor.boundary()) { - setCursor(cursor.par(), cursor.pos(), true, false); - return true; - } - - if (cursor.pos() != cursorPar()->size()) { - setCursor(cursor.par(), cursor.pos() + 1, true, false); - if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(), - cursor.pos())) - setCursor(cursor.par(), cursor.pos(), true, true); - return true; - } + // Tell BufferView to test for FitCursor in any case! + cur.updateFlags(Update::FitCursor); - if (cursor.par() + 1 != int(ownerParagraphs().size())) { - setCursor(cursor.par() + 1, 0); - return true; - } + TextMetrics const & tm = cur.bv().textMetrics(this); + ParagraphMetrics const & pm = tm.parMetrics(cur.pit()); - return false; -} + int row; + if (cur.pos() && cur.boundary()) + row = pm.pos2row(cur.pos()-1); + else + row = pm.pos2row(cur.pos()); + + // remember current position only if we are not at the end of a row. + if (cur.pos() != pm.rows()[row].endpos()) + cur.setTargetX(); + int const x = cur.targetX(); + + if (!cur.selection()) { + int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_; + LCursor old = cur; + // To middle of next row + int const margin = 3 * InsetMathHull::displayMargin() / 2; + editXY(cur, x, y + pm.rows()[row].descent() + margin); + cur.clearSelection(); + + // This happens when you move out of an inset. + // And to give the DEPM the possibility of doing + // something we must provide it with two different + // cursors. (Lgb) + LCursor dummy = cur; + if (dummy == old) + ++dummy.pos(); + + bool const changed = cur.bv().checkDepm(dummy, old); + // Make sure that cur gets back whatever happened to dummy(Lgb) + if (changed) + cur = dummy; -void LyXText::cursorUp(bool selecting) -{ - Row const & row = *cursorRow(); - int x = bv()->x_target() - xo_; - int y = cursor.y() - row.baseline() - 1; - setCursorFromCoordinates(x, y); - - if (!selecting) { - int y_abs = y + yo_ - bv()->top_y(); - InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs); - if (inset_hit && isHighlyEditableInset(inset_hit)) - inset_hit->edit(bv(), bv()->x_target(), y_abs); + return false; } -} + bool updateNeeded = false; -void LyXText::cursorDown(bool selecting) -{ - Row const & row = *cursorRow(); - int x = bv()->x_target() - xo_; - int y = cursor.y() - row.baseline() + row.height() + 1; - setCursorFromCoordinates(x, y); - - if (!selecting) { - int y_abs = y + yo_ - bv()->top_y(); - InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs); - if (inset_hit && isHighlyEditableInset(inset_hit)) - inset_hit->edit(bv(), bv()->x_target(), y_abs); + if (row + 1 < int(pm.rows().size())) { + updateNeeded |= setCursor(cur, cur.pit(), + tm.x2pos(cur.pit(), row + 1, x)); + } else if (cur.pit() + 1 < int(paragraphs().size())) { + ++cur.pit(); + updateNeeded |= setCursor(cur, cur.pit(), + tm.x2pos(cur.pit(), 0, x)); } + + cur.x_target() = x; + + return updateNeeded; } -void LyXText::cursorUpParagraph() +bool LyXText::cursorUpParagraph(LCursor & cur) { - ParagraphList::iterator cpit = cursorPar(); - if (cursor.pos() > 0) - setCursor(cpit, 0); - else if (cpit != ownerParagraphs().begin()) - setCursor(boost::prior(cpit), 0); + bool updated = false; + if (cur.pos() > 0) + updated = setCursor(cur, cur.pit(), 0); + else if (cur.pit() != 0) + updated = setCursor(cur, cur.pit() - 1, 0); + return updated; } -void LyXText::cursorDownParagraph() +bool LyXText::cursorDownParagraph(LCursor & cur) { - ParagraphList::iterator pit = cursorPar(); - ParagraphList::iterator next_pit = boost::next(pit); - - if (next_pit != ownerParagraphs().end()) - setCursor(next_pit, 0); + bool updated = false; + if (cur.pit() != cur.lastpit()) + updated = setCursor(cur, cur.pit() + 1, 0); else - setCursor(pit, pit->size()); + updated = setCursor(cur, cur.pit(), cur.lastpos()); + return updated; } // fix the cursor `cur' after a characters has been deleted at `where' // position. Called by deleteEmptyParagraphMechanism -void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where) +void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where) { - // if cursor is not in the paragraph where the delete occured, - // do nothing - if (cur.par() != where.par()) + // Do nothing if cursor is not in the paragraph where the + // deletion occured, + if (cur.pit() != where.pit()) return; - // if cursor position is after the place where the delete occured, - // update it + // If cursor position is after the deletion place update it if (cur.pos() > where.pos()) - cur.pos(cur.pos()-1); + --cur.pos(); - // check also if we don't want to set the cursor on a spot behind the + // Check also if we don't want to set the cursor on a spot behind the // pagragraph because we erased the last character. - if (cur.pos() > getPar(cur)->size()) - cur.pos(getPar(cur)->size()); - - // recompute row et al. for this cursor - setCursor(cur, cur.par(), cur.pos(), cur.boundary()); + if (cur.pos() > cur.lastpos()) + cur.pos() = cur.lastpos(); } -bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor) +bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, + LCursor & old, bool & need_anchor_change) { - // Would be wrong to delete anything if we have a selection. - if (selection.set()) - return false; + //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl; - // Don't do anything if the cursor is invalid - if (old_cursor.par() == -1) - return false; + Paragraph & oldpar = old.paragraph(); // We allow all kinds of "mumbo-jumbo" when freespacing. - ParagraphList::iterator const old_pit = getPar(old_cursor); - if (old_pit->isFreeSpacing()) + if (oldpar.isFreeSpacing()) return false; /* Ok I'll put some comments here about what is missing. - I have fixed BackSpace (and thus Delete) to not delete - double-spaces automagically. I have also changed Cut, - Copy and Paste to hopefully do some sensible things. There are still some small problems that can lead to double spaces stored in the document file or space at - the beginning of paragraphs. This happens if you have + the beginning of paragraphs(). This happens if you have the cursor between to spaces and then save. Or if you cut and paste and the selection have a space at the - beginning and then save right after the paste. I am - sure none of these are very hard to fix, but I will - put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so - that I can get some feedback. (Lgb) + beginning and then save right after the paste. (Lgb) */ - // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator + // If old.pos() == 0 and old.pos()(1) == LineSeparator // delete the LineSeparator. // MISSING - // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator + // If old.pos() == 1 and old.pos()(0) == LineSeparator // delete the LineSeparator. // MISSING - // If the pos around the old_cursor were spaces, delete one of them. - if (old_cursor.par() != cursor.par() - || old_cursor.pos() != cursor.pos()) { - - // Only if the cursor has really moved - if (old_cursor.pos() > 0 - && old_cursor.pos() < old_pit->size() - && old_pit->isLineSeparator(old_cursor.pos()) - && old_pit->isLineSeparator(old_cursor.pos() - 1)) { - bool erased = old_pit->erase(old_cursor.pos() - 1); - redoParagraph(old_pit); - - if (!erased) - return false; + bool const same_inset = &old.inset() == &cur.inset(); + bool const same_par = same_inset && old.pit() == cur.pit(); + bool const same_par_pos = same_par && old.pos() == cur.pos(); + + // If the chars around the old cursor were spaces, delete one of them. + if (!same_par_pos) { + // Only if the cursor has really moved. + if (old.pos() > 0 + && old.pos() < oldpar.size() + && oldpar.isLineSeparator(old.pos()) + && oldpar.isLineSeparator(old.pos() - 1) + && !oldpar.isDeleted(old.pos() - 1)) { + oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges); #ifdef WITH_WARNINGS #warning This will not work anymore when we have multiple views of the same buffer // In this case, we will have to correct also the cursors held by // other bufferviews. It will probably be easier to do that in a more -// automated way in LyXCursor code. (JMarc 26/09/2001) +// automated way in CursorSlice code. (JMarc 26/09/2001) #endif - // correct all cursors held by the LyXText - fixCursorAfterDelete(cursor, old_cursor); - fixCursorAfterDelete(selection.cursor, old_cursor); - fixCursorAfterDelete(selection.start, old_cursor); - fixCursorAfterDelete(selection.end, old_cursor); - return false; + // correct all cursor parts + if (same_par) { + fixCursorAfterDelete(cur.top(), old.top()); + need_anchor_change = true; + } + return true; } } - // don't delete anything if this is the ONLY paragraph! - if (ownerParagraphs().size() == 1) + // only do our magic if we changed paragraph + if (same_par) return false; - // Do not delete empty paragraphs with keepempty set. - if (old_pit->allowEmpty()) + // don't delete anything if this is the ONLY paragraph! + if (old.lastpit() == 0) return false; - // only do our magic if we changed paragraph - if (old_cursor.par() == cursor.par()) + // Do not delete empty paragraphs with keepempty set. + if (oldpar.allowEmpty()) return false; - // record if we have deleted a paragraph - // we can't possibly have deleted a paragraph before this point - bool deleted = false; - - if (old_pit->empty() - || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) { - // ok, we will delete something - LyXCursor tmpcursor; - - deleted = true; - - bool selection_position_was_oldcursor_position = - selection.cursor.par() == old_cursor.par() - && selection.cursor.pos() == old_cursor.pos(); - - tmpcursor = cursor; - cursor = old_cursor; // that undo can restore the right cursor position - - ParagraphList::iterator endpit = boost::next(old_pit); - while (endpit != ownerParagraphs().end() && endpit->getDepth()) - ++endpit; - - recUndo(parOffset(old_pit), parOffset(endpit) - 1); - cursor = tmpcursor; - - // cache cursor pit - ParagraphList::iterator tmppit = cursorPar(); - // delete old par - ownerParagraphs().erase(old_pit); - // update cursor par offset - cursor.par(parOffset(tmppit)); - redoParagraph(); - - // correct cursor y - setCursorIntern(cursor.par(), cursor.pos()); - - if (selection_position_was_oldcursor_position) { - // correct selection - selection.cursor = cursor; + if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) { + // Delete old par. + recordUndo(old, Undo::ATOMIC, + max(old.pit() - 1, pit_type(0)), + min(old.pit() + 1, old.lastpit())); + ParagraphList & plist = old.text()->paragraphs(); + plist.erase(boost::next(plist.begin(), old.pit())); + + // see #warning above + if (cur.depth() >= old.depth()) { + CursorSlice & curslice = cur[old.depth() - 1]; + if (&curslice.inset() == &old.inset() + && curslice.pit() > old.pit()) { + --curslice.pit(); + // since a paragraph has been deleted, all the + // insets after `old' have been copied and + // their address has changed. Therefore we + // need to `regenerate' cur. (JMarc) + cur.updateInsets(&(cur.bottom().inset())); + need_anchor_change = true; + } } + return true; } - if (deleted) + if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) { + need_anchor_change = true; + // We return true here because the Paragraph contents changed and + // we need a redraw before further action is processed. return true; - - if (old_pit->stripLeadingSpaces()) { - redoParagraph(old_pit); - // correct cursor y - setCursorIntern(cursor.par(), cursor.pos()); - selection.cursor = cursor; } + return false; } -ParagraphList & LyXText::ownerParagraphs() const +void LyXText::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges) { - return *paragraphs_; -} + BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size()); + for (pit_type pit = first; pit <= last; ++pit) { + Paragraph & par = pars_[pit]; -void LyXText::recUndo(paroffset_type first, paroffset_type last) const -{ - recordUndo(Undo::ATOMIC, this, first, last); -} + // We allow all kinds of "mumbo-jumbo" when freespacing. + if (par.isFreeSpacing()) + continue; + for (pos_type pos = 1; pos < par.size(); ++pos) { + if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1) + && !par.isDeleted(pos - 1)) { + if (par.eraseChar(pos - 1, trackChanges)) { + --pos; + } + } + } -void LyXText::recUndo(lyx::paroffset_type par) const -{ - recordUndo(Undo::ATOMIC, this, par, par); + // don't delete anything if this is the only remaining paragraph within the given range + // note: LyXText::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM + if (first == last) + continue; + + // don't delete empty paragraphs with keepempty set + if (par.allowEmpty()) + continue; + + if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) { + pars_.erase(boost::next(pars_.begin(), pit)); + --pit; + --last; + continue; + } + + par.stripLeadingSpaces(trackChanges); + } } -bool LyXText::isInInset() const +void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const { - // Sub-level has non-null bv owner and non-null inset owner. - return inset_owner != 0; + recordUndo(cur, Undo::ATOMIC, first, last); } -int defaultRowHeight() +void LyXText::recUndo(LCursor & cur, pit_type par) const { - LyXFont const font(LyXFont::ALL_SANE); - return int(font_metrics::maxHeight(font) * 1.2); + recordUndo(cur, Undo::ATOMIC, par, par); } + +} // namespace lyx