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;
66 LyXText::LyXText(BufferView * bv)
67 : maxwidth_(bv ? bv->workWidth() : 100),
68 background_color_(LColor::background),
74 void LyXText::init(BufferView * bv)
78 maxwidth_ = bv->workWidth();
83 pit_type const end = paragraphs().size();
84 for (pit_type pit = 0; pit != end; ++pit)
85 pars_[pit].rows().clear();
87 current_font = getFont(pars_[0], 0);
88 updateCounters(*bv->buffer());
92 bool LyXText::isMainText() const
94 return &bv()->buffer()->text() == this;
98 //takes screen x,y coordinates
99 InsetBase * LyXText::checkInsetHit(int x, int y) const
101 pit_type pit = getPitNearY(y);
102 BOOST_ASSERT(pit != -1);
104 Paragraph const & par = pars_[pit];
107 << BOOST_CURRENT_FUNCTION
112 InsetList::const_iterator iit = par.insetlist.begin();
113 InsetList::const_iterator iend = par.insetlist.end();
114 for (; iit != iend; ++iit) {
115 InsetBase * inset = iit->inset;
118 << BOOST_CURRENT_FUNCTION
119 << ": examining inset " << inset << endl;
121 if (theCoords.getInsets().has(inset))
123 << BOOST_CURRENT_FUNCTION
124 << ": xo: " << inset->xo() << "..."
125 << inset->xo() + inset->width()
126 << " yo: " << inset->yo() - inset->ascent()
128 << inset->yo() + inset->descent()
132 << BOOST_CURRENT_FUNCTION
133 << ": inset has no cached position" << endl;
135 if (inset->covers(x, y)) {
137 << BOOST_CURRENT_FUNCTION
138 << ": Hit inset: " << inset << endl;
143 << BOOST_CURRENT_FUNCTION
144 << ": No inset hit. " << endl;
150 // Gets the fully instantiated font at a given position in a paragraph
151 // Basically the same routine as Paragraph::getFont() in paragraph.C.
152 // The difference is that this one is used for displaying, and thus we
153 // are allowed to make cosmetic improvements. For instance make footnotes
155 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
157 BOOST_ASSERT(pos >= 0);
159 LyXLayout_ptr const & layout = par.layout();
163 BufferParams const & params = bv()->buffer()->params();
164 pos_type const body_pos = par.beginOfBody();
166 // We specialize the 95% common case:
167 if (!par.getDepth()) {
168 LyXFont f = par.getFontSettings(params, pos);
171 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
172 return f.realize(layout->reslabelfont);
174 return f.realize(layout->resfont);
177 // The uncommon case need not be optimized as much
180 layoutfont = layout->labelfont;
182 layoutfont = layout->font;
184 LyXFont font = par.getFontSettings(params, pos);
185 font.realize(layoutfont);
188 applyOuterFont(font);
190 // Realize with the fonts of lesser depth.
191 font.realize(defaultfont_);
196 // There are currently two font mechanisms in LyX:
197 // 1. The font attributes in a lyxtext, and
198 // 2. The inset-specific font properties, defined in an inset's
199 // metrics() and draw() methods and handed down the inset chain through
200 // the pi/mi parameters, and stored locally in a lyxtext in font_.
201 // This is where the two are integrated in the final fully realized
203 void LyXText::applyOuterFont(LyXFont & font) const {
205 lf.reduce(defaultfont_);
207 lf.setLanguage(font.language());
212 LyXFont LyXText::getLayoutFont(pit_type const pit) const
214 LyXLayout_ptr const & layout = pars_[pit].layout();
216 if (!pars_[pit].getDepth())
217 return layout->resfont;
219 LyXFont font = layout->font;
220 // Realize with the fonts of lesser depth.
221 //font.realize(outerFont(pit, paragraphs()));
222 font.realize(defaultfont_);
228 LyXFont LyXText::getLabelFont(Paragraph const & par) const
230 LyXLayout_ptr const & layout = par.layout();
233 return layout->reslabelfont;
235 LyXFont font = layout->labelfont;
236 // Realize with the fonts of lesser depth.
237 font.realize(defaultfont_);
243 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
246 LyXLayout_ptr const & layout = pars_[pit].layout();
248 // Get concrete layout font to reduce against
251 if (pos < pars_[pit].beginOfBody())
252 layoutfont = layout->labelfont;
254 layoutfont = layout->font;
256 // Realize against environment font information
257 if (pars_[pit].getDepth()) {
259 while (!layoutfont.resolved() &&
260 tp != pit_type(paragraphs().size()) &&
261 pars_[tp].getDepth()) {
262 tp = outerHook(tp, paragraphs());
263 if (tp != pit_type(paragraphs().size()))
264 layoutfont.realize(pars_[tp].layout()->font);
268 layoutfont.realize(defaultfont_);
270 // Now, reduce font against full layout font
271 font.reduce(layoutfont);
273 pars_[pit].setFont(pos, font);
278 // Asger is not sure we want to do this...
279 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
282 LyXLayout_ptr const & layout = par.layout();
283 pos_type const psize = par.size();
286 for (pos_type pos = 0; pos < psize; ++pos) {
287 if (pos < par.beginOfBody())
288 layoutfont = layout->labelfont;
290 layoutfont = layout->font;
292 LyXFont tmpfont = par.getFontSettings(params, pos);
293 tmpfont.reduce(layoutfont);
294 par.setFont(pos, tmpfont);
299 // return past-the-last paragraph influenced by a layout change on pit
300 pit_type LyXText::undoSpan(pit_type pit)
302 pit_type end = paragraphs().size();
303 pit_type nextpit = pit + 1;
306 //because of parindents
307 if (!pars_[pit].getDepth())
308 return boost::next(nextpit);
309 //because of depth constrains
310 for (; nextpit != end; ++pit, ++nextpit) {
311 if (!pars_[pit].getDepth())
318 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
320 BOOST_ASSERT(start != end);
321 pit_type undopit = undoSpan(end - 1);
322 recUndo(start, undopit - 1);
324 BufferParams const & bufparams = bv()->buffer()->params();
325 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
327 for (pit_type pit = start; pit != end; ++pit) {
328 pars_[pit].applyLayout(lyxlayout);
329 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
330 if (lyxlayout->margintype == MARGIN_MANUAL)
331 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
338 // set layout over selection and make a total rebreak of those paragraphs
339 void LyXText::setLayout(LCursor & cur, string const & layout)
341 BOOST_ASSERT(this == cur.text());
342 // special handling of new environment insets
343 BufferView & bv = cur.bv();
344 BufferParams const & params = bv.buffer()->params();
345 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
346 if (lyxlayout->is_environment) {
347 // move everything in a new environment inset
348 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
349 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
350 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
351 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
352 InsetBase * inset = new InsetEnvironment(params, layout);
353 insertInset(cur, inset);
354 //inset->edit(cur, true);
355 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
359 pit_type start = cur.selBegin().pit();
360 pit_type end = cur.selEnd().pit() + 1;
361 setLayout(start, end, layout);
362 updateCounters(cur.buffer());
369 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
370 Paragraph const & par, int max_depth)
372 if (par.layout()->labeltype == LABEL_BIBLIO)
374 int const depth = par.params().depth();
375 if (type == LyXText::INC_DEPTH && depth < max_depth)
377 if (type == LyXText::DEC_DEPTH && depth > 0)
386 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
388 BOOST_ASSERT(this == cur.text());
389 pit_type const beg = cur.selBegin().pit();
390 pit_type const end = cur.selEnd().pit() + 1;
391 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
393 for (pit_type pit = beg; pit != end; ++pit) {
394 if (::changeDepthAllowed(type, pars_[pit], max_depth))
396 max_depth = pars_[pit].getMaxDepthAfter();
402 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
404 BOOST_ASSERT(this == cur.text());
405 pit_type const beg = cur.selBegin().pit();
406 pit_type const end = cur.selEnd().pit() + 1;
407 recordUndoSelection(cur);
408 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
410 for (pit_type pit = beg; pit != end; ++pit) {
411 Paragraph & par = pars_[pit];
412 if (::changeDepthAllowed(type, par, max_depth)) {
413 int const depth = par.params().depth();
414 if (type == INC_DEPTH)
415 par.params().depth(depth + 1);
417 par.params().depth(depth - 1);
419 max_depth = par.getMaxDepthAfter();
421 // this handles the counter labels, and also fixes up
422 // depth values for follow-on (child) paragraphs
423 updateCounters(cur.buffer());
427 // set font over selection
428 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
430 BOOST_ASSERT(this == cur.text());
431 // if there is no selection just set the current_font
432 if (!cur.selection()) {
433 // Determine basis font
435 pit_type pit = cur.pit();
436 if (cur.pos() < pars_[pit].beginOfBody())
437 layoutfont = getLabelFont(pars_[pit]);
439 layoutfont = getLayoutFont(pit);
441 // Update current font
442 real_current_font.update(font,
443 cur.buffer().params().language,
446 // Reduce to implicit settings
447 current_font = real_current_font;
448 current_font.reduce(layoutfont);
449 // And resolve it completely
450 real_current_font.realize(layoutfont);
455 // Ok, we have a selection.
456 recordUndoSelection(cur);
458 DocIterator dit = cur.selectionBegin();
459 DocIterator ditend = cur.selectionEnd();
461 BufferParams const & params = cur.buffer().params();
463 // Don't use forwardChar here as ditend might have
464 // pos() == lastpos() and forwardChar would miss it.
465 // Can't use forwardPos either as this descends into
467 for (; dit != ditend; dit.forwardPosNoDescend()) {
468 if (dit.pos() != dit.lastpos()) {
469 LyXFont f = getFont(dit.paragraph(), dit.pos());
470 f.update(font, params.language, toggleall);
471 setCharFont(dit.pit(), dit.pos(), f);
477 // the cursor set functions have a special mechanism. When they
478 // realize you left an empty paragraph, they will delete it.
480 void LyXText::cursorHome(LCursor & cur)
482 BOOST_ASSERT(this == cur.text());
483 setCursor(cur, cur.pit(), cur.textRow().pos());
487 void LyXText::cursorEnd(LCursor & cur)
489 BOOST_ASSERT(this == cur.text());
490 // if not on the last row of the par, put the cursor before
492 // FIXME: does this final space exist?
493 pos_type const end = cur.textRow().endpos();
494 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
498 void LyXText::cursorTop(LCursor & cur)
500 BOOST_ASSERT(this == cur.text());
501 setCursor(cur, 0, 0);
505 void LyXText::cursorBottom(LCursor & cur)
507 BOOST_ASSERT(this == cur.text());
508 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
512 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
514 BOOST_ASSERT(this == cur.text());
515 // If the mask is completely neutral, tell user
516 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
517 // Could only happen with user style
518 cur.message(_("No font change defined. "
519 "Use Character under the Layout menu to define font change."));
523 // Try implicit word selection
524 // If there is a change in the language the implicit word selection
526 CursorSlice resetCursor = cur.top();
527 bool implicitSelection =
528 font.language() == ignore_language
529 && font.number() == LyXFont::IGNORE
530 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
533 setFont(cur, font, toggleall);
535 // Implicit selections are cleared afterwards
536 // and cursor is set to the original position.
537 if (implicitSelection) {
538 cur.clearSelection();
539 cur.top() = resetCursor;
545 string LyXText::getStringToIndex(LCursor const & cur)
547 BOOST_ASSERT(this == cur.text());
550 if (cur.selection()) {
551 idxstring = cur.selectionAsString(false);
553 // Try implicit word selection. If there is a change
554 // in the language the implicit word selection is
556 LCursor tmpcur = cur;
557 selectWord(tmpcur, lyx::PREVIOUS_WORD);
559 if (!tmpcur.selection())
560 cur.message(_("Nothing to index!"));
561 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
562 cur.message(_("Cannot index more than one paragraph!"));
564 idxstring = tmpcur.selectionAsString(false);
571 void LyXText::setParagraph(LCursor & cur,
572 Spacing const & spacing, LyXAlignment align,
573 string const & labelwidthstring, bool noindent)
575 BOOST_ASSERT(cur.text());
576 // make sure that the depth behind the selection are restored, too
577 pit_type undopit = undoSpan(cur.selEnd().pit());
578 recUndo(cur.selBegin().pit(), undopit - 1);
580 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
582 Paragraph & par = pars_[pit];
583 ParagraphParameters & params = par.params();
584 params.spacing(spacing);
586 // does the layout allow the new alignment?
587 LyXLayout_ptr const & layout = par.layout();
589 if (align == LYX_ALIGN_LAYOUT)
590 align = layout->align;
591 if (align & layout->alignpossible) {
592 if (align == layout->align)
593 params.align(LYX_ALIGN_LAYOUT);
597 par.setLabelWidthString(labelwidthstring);
598 params.noindent(noindent);
603 // this really should just insert the inset and not move the cursor.
604 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
606 BOOST_ASSERT(this == cur.text());
608 cur.paragraph().insertInset(cur.pos(), inset);
612 // needed to insert the selection
613 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
615 pit_type pit = cur.pit();
616 pos_type pos = cur.pos();
619 // only to be sure, should not be neccessary
620 cur.clearSelection();
621 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
625 setCursor(cur, cur.pit(), pos);
630 // turn double CR to single CR, others are converted into one
631 // blank. Then insertStringAsLines is called
632 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
634 string linestr = str;
635 bool newline_inserted = false;
637 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
638 if (linestr[i] == '\n') {
639 if (newline_inserted) {
640 // we know that \r will be ignored by
641 // insertStringAsLines. Of course, it is a dirty
642 // trick, but it works...
643 linestr[i - 1] = '\r';
647 newline_inserted = true;
649 } else if (IsPrintable(linestr[i])) {
650 newline_inserted = false;
653 insertStringAsLines(cur, linestr);
657 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
658 bool setfont, bool boundary)
661 setCursorIntern(cur, par, pos, setfont, boundary);
662 return deleteEmptyParagraphMechanism(cur, old);
666 void LyXText::setCursor(CursorSlice & cur, pit_type par,
667 pos_type pos, bool boundary)
669 BOOST_ASSERT(par != int(paragraphs().size()));
672 cur.boundary() = boundary;
674 // now some strict checking
675 Paragraph & para = getPar(par);
677 // None of these should happen, but we're scaredy-cats
679 lyxerr << "dont like -1" << endl;
683 if (pos > para.size()) {
684 lyxerr << "dont like 1, pos: " << pos
685 << " size: " << para.size()
686 << " par: " << par << endl;
692 void LyXText::setCursorIntern(LCursor & cur,
693 pit_type par, pos_type pos, bool setfont, bool boundary)
695 setCursor(cur.top(), par, pos, boundary);
702 void LyXText::setCurrentFont(LCursor & cur)
704 BOOST_ASSERT(this == cur.text());
705 pos_type pos = cur.pos();
706 Paragraph & par = cur.paragraph();
708 if (cur.boundary() && pos > 0)
712 if (pos == cur.lastpos())
714 else // potentional bug... BUG (Lgb)
715 if (par.isSeparator(pos)) {
716 if (pos > cur.textRow().pos() &&
717 bidi.level(pos) % 2 ==
718 bidi.level(pos - 1) % 2)
720 else if (pos + 1 < cur.lastpos())
725 BufferParams const & bufparams = cur.buffer().params();
726 current_font = par.getFontSettings(bufparams, pos);
727 real_current_font = getFont(par, pos);
729 if (cur.pos() == cur.lastpos()
730 && bidi.isBoundary(cur.buffer(), par, cur.pos())
731 && !cur.boundary()) {
732 Language const * lang = par.getParLanguage(bufparams);
733 current_font.setLanguage(lang);
734 current_font.setNumber(LyXFont::OFF);
735 real_current_font.setLanguage(lang);
736 real_current_font.setNumber(LyXFont::OFF);
741 // x is an absolute screen coord
742 // returns the column near the specified x-coordinate of the row
743 // x is set to the real beginning of this column
744 pos_type LyXText::getColumnNearX(pit_type const pit,
745 Row const & row, int & x, bool & boundary) const
747 int const xo = theCoords.get(this, pit).x_;
749 RowMetrics const r = computeRowMetrics(pit, row);
750 Paragraph const & par = pars_[pit];
752 pos_type vc = row.pos();
753 pos_type end = row.endpos();
755 LyXLayout_ptr const & layout = par.layout();
757 bool left_side = false;
759 pos_type body_pos = par.beginOfBody();
762 double last_tmpx = tmpx;
765 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
768 // check for empty row
774 while (vc < end && tmpx <= x) {
775 c = bidi.vis2log(vc);
777 if (body_pos > 0 && c == body_pos - 1) {
778 tmpx += r.label_hfill +
779 font_metrics::width(layout->labelsep, getLabelFont(par));
780 if (par.isLineSeparator(body_pos - 1))
781 tmpx -= singleWidth(par, body_pos - 1);
784 if (hfillExpansion(par, row, c)) {
785 tmpx += singleWidth(par, c);
789 tmpx += r.label_hfill;
790 } else if (par.isSeparator(c)) {
791 tmpx += singleWidth(par, c);
795 tmpx += singleWidth(par, c);
800 if ((tmpx + last_tmpx) / 2 > x) {
805 BOOST_ASSERT(vc <= end); // This shouldn't happen.
808 // This (rtl_support test) is not needed, but gives
809 // some speedup if rtl_support == false
810 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
812 // If lastrow is false, we don't need to compute
814 bool const rtl = lastrow ? isRTL(par) : false;
816 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
817 (!rtl && !left_side && vc == end && x > tmpx + 5)))
819 else if (vc == row.pos()) {
820 c = bidi.vis2log(vc);
821 if (bidi.level(c) % 2 == 1)
824 c = bidi.vis2log(vc - 1);
825 bool const rtl = (bidi.level(c) % 2 == 1);
826 if (left_side == rtl) {
828 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
832 // The following code is necessary because the cursor position past
833 // the last char in a row is logically equivalent to that before
834 // the first char in the next row. That's why insets causing row
835 // divisions -- Newline and display-style insets -- must be treated
836 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
837 // Newline inset, air gap below:
838 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
839 if (bidi.level(end -1) % 2 == 0)
840 tmpx -= singleWidth(par, end - 1);
842 tmpx += singleWidth(par, end - 1);
845 // Air gap above display inset:
846 if (row.pos() < end && c >= end && end < par.size()
847 && par.isInset(end) && par.getInset(end)->display()) {
850 // Air gap below display inset:
851 if (row.pos() < end && c >= end && par.isInset(end - 1)
852 && par.getInset(end - 1)->display()) {
857 return c - row.pos();
861 // y is screen coordinate
862 pit_type LyXText::getPitNearY(int y) const
864 BOOST_ASSERT(!paragraphs().empty());
865 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
866 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
868 << BOOST_CURRENT_FUNCTION
869 << ": y: " << y << " cache size: " << cc.size()
872 // look for highest numbered paragraph with y coordinate less than given y
875 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
876 CoordCache::InnerParPosCache::const_iterator et = cc.end();
877 for (; it != et; ++it) {
879 << BOOST_CURRENT_FUNCTION
880 << " examining: pit: " << it->first
881 << " y: " << it->second.y_
884 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
891 << BOOST_CURRENT_FUNCTION
892 << ": found best y: " << yy << " for pit: " << pit
899 Row const & LyXText::getRowNearY(int y, pit_type pit) const
901 Paragraph const & par = pars_[pit];
902 int yy = theCoords.get(this, pit).y_ - par.ascent();
903 BOOST_ASSERT(!par.rows().empty());
904 RowList::const_iterator rit = par.rows().begin();
905 RowList::const_iterator const rlast = boost::prior(par.rows().end());
906 for (; rit != rlast; yy += rit->height(), ++rit)
907 if (yy + rit->height() > y)
913 // x,y are absolute screen coordinates
914 // sets cursor recursively descending into nested editable insets
915 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
917 pit_type pit = getPitNearY(y);
918 BOOST_ASSERT(pit != -1);
919 Row const & row = getRowNearY(y, pit);
922 int xx = x; // is modified by getColumnNearX
923 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
926 cur.boundary() = bound;
929 // try to descend into nested insets
930 InsetBase * inset = checkInsetHit(x, y);
931 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
933 // Either we deconst editXY or better we move current_font
934 // and real_current_font to LCursor
939 // This should be just before or just behind the
940 // cursor position set above.
941 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
942 || inset == pars_[pit].getInset(pos));
943 // Make sure the cursor points to the position before
945 if (inset == pars_[pit].getInset(pos - 1))
947 inset = inset->editXY(cur, x, y);
948 if (cur.top().text() == this)
954 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
958 if (cur.pos() == cur.lastpos())
960 InsetBase * inset = cur.nextInset();
961 if (!isHighlyEditableInset(inset))
963 inset->edit(cur, front);
968 bool LyXText::cursorLeft(LCursor & cur)
970 if (cur.pos() != 0) {
971 bool boundary = cur.boundary();
972 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
973 if (!checkAndActivateInset(cur, false)) {
974 if (false && !boundary &&
975 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
977 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
982 if (cur.pit() != 0) {
983 // Steps into the paragraph above
984 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
990 bool LyXText::cursorRight(LCursor & cur)
992 if (false && cur.boundary()) {
993 return setCursor(cur, cur.pit(), cur.pos(), true, false);
996 if (cur.pos() != cur.lastpos()) {
997 bool updateNeeded = false;
998 if (!checkAndActivateInset(cur, true)) {
999 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1000 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1002 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1004 return updateNeeded;
1007 if (cur.pit() != cur.lastpit())
1008 return setCursor(cur, cur.pit() + 1, 0);
1013 bool LyXText::cursorUp(LCursor & cur)
1015 Paragraph const & par = cur.paragraph();
1016 int const row = par.pos2row(cur.pos());
1017 int const x = cur.targetX();
1019 if (!cur.selection()) {
1020 int const y = bv_funcs::getPos(cur).y_;
1022 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1024 // This happens when you move out of an inset.
1025 // And to give the DEPM the possibility of doing
1026 // something we must provide it with two different
1028 LCursor dummy = cur;
1032 return deleteEmptyParagraphMechanism(dummy, old);
1035 bool updateNeeded = false;
1038 updateNeeded |= setCursor(cur, cur.pit(),
1039 x2pos(cur.pit(), row - 1, x));
1040 } else if (cur.pit() > 0) {
1042 //cannot use 'par' now
1043 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1048 return updateNeeded;
1052 bool LyXText::cursorDown(LCursor & cur)
1054 Paragraph const & par = cur.paragraph();
1055 int const row = par.pos2row(cur.pos());
1056 int const x = cur.targetX();
1058 if (!cur.selection()) {
1059 int const y = bv_funcs::getPos(cur).y_;
1061 editXY(cur, x, y + par.rows()[row].descent() + 1);
1063 // This happens when you move out of an inset.
1064 // And to give the DEPM the possibility of doing
1065 // something we must provide it with two different
1067 LCursor dummy = cur;
1071 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1073 // Make sure that cur gets back whatever happened to dummy(Lgb)
1081 bool updateNeeded = false;
1083 if (row + 1 < int(par.rows().size())) {
1084 updateNeeded |= setCursor(cur, cur.pit(),
1085 x2pos(cur.pit(), row + 1, x));
1086 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1088 updateNeeded |= setCursor(cur, cur.pit(),
1089 x2pos(cur.pit(), 0, x));
1094 return updateNeeded;
1098 bool LyXText::cursorUpParagraph(LCursor & cur)
1100 bool updated = false;
1102 updated = setCursor(cur, cur.pit(), 0);
1103 else if (cur.pit() != 0)
1104 updated = setCursor(cur, cur.pit() - 1, 0);
1109 bool LyXText::cursorDownParagraph(LCursor & cur)
1111 bool updated = false;
1112 if (cur.pit() != cur.lastpit())
1113 updated = setCursor(cur, cur.pit() + 1, 0);
1115 updated = setCursor(cur, cur.pit(), cur.lastpos());
1120 // fix the cursor `cur' after a characters has been deleted at `where'
1121 // position. Called by deleteEmptyParagraphMechanism
1122 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1124 // Do nothing if cursor is not in the paragraph where the
1125 // deletion occured,
1126 if (cur.pit() != where.pit())
1129 // If cursor position is after the deletion place update it
1130 if (cur.pos() > where.pos())
1133 // Check also if we don't want to set the cursor on a spot behind the
1134 // pagragraph because we erased the last character.
1135 if (cur.pos() > cur.lastpos())
1136 cur.pos() = cur.lastpos();
1140 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1142 // Would be wrong to delete anything if we have a selection.
1143 if (cur.selection())
1146 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1147 Paragraph const & oldpar = pars_[old.pit()];
1149 // We allow all kinds of "mumbo-jumbo" when freespacing.
1150 if (oldpar.isFreeSpacing())
1153 /* Ok I'll put some comments here about what is missing.
1154 I have fixed BackSpace (and thus Delete) to not delete
1155 double-spaces automagically. I have also changed Cut,
1156 Copy and Paste to hopefully do some sensible things.
1157 There are still some small problems that can lead to
1158 double spaces stored in the document file or space at
1159 the beginning of paragraphs(). This happens if you have
1160 the cursor between to spaces and then save. Or if you
1161 cut and paste and the selection have a space at the
1162 beginning and then save right after the paste. I am
1163 sure none of these are very hard to fix, but I will
1164 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1165 that I can get some feedback. (Lgb)
1168 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1169 // delete the LineSeparator.
1172 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1173 // delete the LineSeparator.
1176 // If the chars around the old cursor were spaces, delete one of them.
1177 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1179 // Only if the cursor has really moved.
1181 && old.pos() < oldpar.size()
1182 && oldpar.isLineSeparator(old.pos())
1183 && oldpar.isLineSeparator(old.pos() - 1)) {
1184 // We need to set the text to Change::INSERTED to
1185 // get it erased properly
1186 pars_[old.pit()].setChange(old.pos() -1,
1188 pars_[old.pit()].erase(old.pos() - 1);
1189 #ifdef WITH_WARNINGS
1190 #warning This will not work anymore when we have multiple views of the same buffer
1191 // In this case, we will have to correct also the cursors held by
1192 // other bufferviews. It will probably be easier to do that in a more
1193 // automated way in CursorSlice code. (JMarc 26/09/2001)
1195 // correct all cursor parts
1196 fixCursorAfterDelete(cur.top(), old.top());
1197 #ifdef WITH_WARNINGS
1198 #warning DEPM, look here
1200 //fixCursorAfterDelete(cur.anchor(), old.top());
1205 // only do our magic if we changed paragraph
1206 if (old.pit() == cur.pit())
1209 // don't delete anything if this is the ONLY paragraph!
1210 if (pars_.size() == 1)
1213 // Do not delete empty paragraphs with keepempty set.
1214 if (oldpar.allowEmpty())
1217 // record if we have deleted a paragraph
1218 // we can't possibly have deleted a paragraph before this point
1219 bool deleted = false;
1221 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1222 // ok, we will delete something
1225 bool selection_position_was_oldcursor_position =
1226 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1228 // This is a bit of a overkill. We change the old and the cur par
1229 // at max, certainly not everything in between...
1230 recUndo(old.pit(), cur.pit());
1233 pars_.erase(pars_.begin() + old.pit());
1235 // Update cursor par offset if necessary.
1236 // Some 'iterator registration' would be nice that takes care of
1237 // such events. Maybe even signal/slot?
1238 if (cur.pit() > old.pit())
1240 #ifdef WITH_WARNINGS
1241 #warning DEPM, look here
1243 // if (cur.anchor().pit() > old.pit())
1244 // --cur.anchor().pit();
1246 if (selection_position_was_oldcursor_position) {
1247 // correct selection
1255 if (pars_[old.pit()].stripLeadingSpaces())
1262 ParagraphList & LyXText::paragraphs() const
1264 return const_cast<ParagraphList &>(pars_);
1268 void LyXText::recUndo(pit_type first, pit_type last) const
1270 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1274 void LyXText::recUndo(pit_type par) const
1276 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1280 int defaultRowHeight()
1282 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);