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"
56 #include <boost/current_function.hpp>
64 using std::ostringstream;
69 LyXText::LyXText(BufferView * bv)
70 : maxwidth_(bv ? bv->workWidth() : 100),
71 background_color_(LColor::background),
77 void LyXText::init(BufferView * bv)
81 maxwidth_ = bv->workWidth();
86 pit_type const end = paragraphs().size();
87 for (pit_type pit = 0; pit != end; ++pit)
88 pars_[pit].rows().clear();
90 current_font = getFont(pars_[0], 0);
91 updateCounters(*bv->buffer());
95 bool LyXText::isMainText() const
97 return &bv()->buffer()->text() == this;
101 //takes screen x,y coordinates
102 InsetBase * LyXText::checkInsetHit(int x, int y) const
104 pit_type pit = getPitNearY(y);
105 BOOST_ASSERT(pit != -1);
107 Paragraph const & par = pars_[pit];
110 << BOOST_CURRENT_FUNCTION
115 InsetList::const_iterator iit = par.insetlist.begin();
116 InsetList::const_iterator iend = par.insetlist.end();
117 for (; iit != iend; ++iit) {
118 InsetBase * inset = iit->inset;
121 << BOOST_CURRENT_FUNCTION
122 << ": examining inset " << inset << endl;
124 if (theCoords.getInsets().has(inset))
126 << BOOST_CURRENT_FUNCTION
127 << ": xo: " << inset->xo() << "..."
128 << inset->xo() + inset->width()
129 << " yo: " << inset->yo() - inset->ascent()
131 << inset->yo() + inset->descent()
135 << BOOST_CURRENT_FUNCTION
136 << ": inset has no cached position" << endl;
138 if (inset->covers(x, y)) {
140 << BOOST_CURRENT_FUNCTION
141 << ": Hit inset: " << inset << endl;
146 << BOOST_CURRENT_FUNCTION
147 << ": No inset hit. " << endl;
153 // Gets the fully instantiated font at a given position in a paragraph
154 // Basically the same routine as Paragraph::getFont() in paragraph.C.
155 // The difference is that this one is used for displaying, and thus we
156 // are allowed to make cosmetic improvements. For instance make footnotes
158 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
160 BOOST_ASSERT(pos >= 0);
162 LyXLayout_ptr const & layout = par.layout();
166 BufferParams const & params = bv()->buffer()->params();
167 pos_type const body_pos = par.beginOfBody();
169 // We specialize the 95% common case:
170 if (!par.getDepth()) {
171 LyXFont f = par.getFontSettings(params, pos);
174 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
175 return f.realize(layout->reslabelfont);
177 return f.realize(layout->resfont);
180 // The uncommon case need not be optimized as much
183 layoutfont = layout->labelfont;
185 layoutfont = layout->font;
187 LyXFont font = par.getFontSettings(params, pos);
188 font.realize(layoutfont);
191 applyOuterFont(font);
193 // Realize with the fonts of lesser depth.
194 font.realize(defaultfont_);
199 // There are currently two font mechanisms in LyX:
200 // 1. The font attributes in a lyxtext, and
201 // 2. The inset-specific font properties, defined in an inset's
202 // metrics() and draw() methods and handed down the inset chain through
203 // the pi/mi parameters, and stored locally in a lyxtext in font_.
204 // This is where the two are integrated in the final fully realized
206 void LyXText::applyOuterFont(LyXFont & font) const {
208 lf.reduce(defaultfont_);
210 lf.setLanguage(font.language());
215 LyXFont LyXText::getLayoutFont(pit_type const pit) const
217 LyXLayout_ptr const & layout = pars_[pit].layout();
219 if (!pars_[pit].getDepth())
220 return layout->resfont;
222 LyXFont font = layout->font;
223 // Realize with the fonts of lesser depth.
224 //font.realize(outerFont(pit, paragraphs()));
225 font.realize(defaultfont_);
231 LyXFont LyXText::getLabelFont(Paragraph const & par) const
233 LyXLayout_ptr const & layout = par.layout();
236 return layout->reslabelfont;
238 LyXFont font = layout->labelfont;
239 // Realize with the fonts of lesser depth.
240 font.realize(defaultfont_);
246 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
249 LyXLayout_ptr const & layout = pars_[pit].layout();
251 // Get concrete layout font to reduce against
254 if (pos < pars_[pit].beginOfBody())
255 layoutfont = layout->labelfont;
257 layoutfont = layout->font;
259 // Realize against environment font information
260 if (pars_[pit].getDepth()) {
262 while (!layoutfont.resolved() &&
263 tp != pit_type(paragraphs().size()) &&
264 pars_[tp].getDepth()) {
265 tp = outerHook(tp, paragraphs());
266 if (tp != pit_type(paragraphs().size()))
267 layoutfont.realize(pars_[tp].layout()->font);
271 layoutfont.realize(defaultfont_);
273 // Now, reduce font against full layout font
274 font.reduce(layoutfont);
276 pars_[pit].setFont(pos, font);
281 // Asger is not sure we want to do this...
282 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
285 LyXLayout_ptr const & layout = par.layout();
286 pos_type const psize = par.size();
289 for (pos_type pos = 0; pos < psize; ++pos) {
290 if (pos < par.beginOfBody())
291 layoutfont = layout->labelfont;
293 layoutfont = layout->font;
295 LyXFont tmpfont = par.getFontSettings(params, pos);
296 tmpfont.reduce(layoutfont);
297 par.setFont(pos, tmpfont);
302 // return past-the-last paragraph influenced by a layout change on pit
303 pit_type LyXText::undoSpan(pit_type pit)
305 pit_type end = paragraphs().size();
306 pit_type nextpit = pit + 1;
309 //because of parindents
310 if (!pars_[pit].getDepth())
311 return boost::next(nextpit);
312 //because of depth constrains
313 for (; nextpit != end; ++pit, ++nextpit) {
314 if (!pars_[pit].getDepth())
321 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
323 BOOST_ASSERT(start != end);
325 BufferParams const & bufparams = bv()->buffer()->params();
326 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
328 for (pit_type pit = start; pit != end; ++pit) {
329 pars_[pit].applyLayout(lyxlayout);
330 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
331 if (lyxlayout->margintype == MARGIN_MANUAL)
332 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
337 // set layout over selection and make a total rebreak of those paragraphs
338 void LyXText::setLayout(LCursor & cur, string const & layout)
340 BOOST_ASSERT(this == cur.text());
341 // special handling of new environment insets
342 BufferView & bv = cur.bv();
343 BufferParams const & params = bv.buffer()->params();
344 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
345 if (lyxlayout->is_environment) {
346 // move everything in a new environment inset
347 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
348 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
349 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
350 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
351 InsetBase * inset = new InsetEnvironment(params, layout);
352 insertInset(cur, inset);
353 //inset->edit(cur, true);
354 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
358 pit_type start = cur.selBegin().pit();
359 pit_type end = cur.selEnd().pit() + 1;
360 pit_type undopit = undoSpan(end - 1);
361 recUndo(start, undopit - 1);
362 setLayout(start, end, layout);
363 updateCounters(cur.buffer());
370 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
371 Paragraph const & par, int max_depth)
373 if (par.layout()->labeltype == LABEL_BIBLIO)
375 int const depth = par.params().depth();
376 if (type == LyXText::INC_DEPTH && depth < max_depth)
378 if (type == LyXText::DEC_DEPTH && depth > 0)
387 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
389 BOOST_ASSERT(this == cur.text());
390 pit_type const beg = cur.selBegin().pit();
391 pit_type const end = cur.selEnd().pit() + 1;
392 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
394 for (pit_type pit = beg; pit != end; ++pit) {
395 if (::changeDepthAllowed(type, pars_[pit], max_depth))
397 max_depth = pars_[pit].getMaxDepthAfter();
403 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
405 BOOST_ASSERT(this == cur.text());
406 pit_type const beg = cur.selBegin().pit();
407 pit_type const end = cur.selEnd().pit() + 1;
408 recordUndoSelection(cur);
409 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
411 for (pit_type pit = beg; pit != end; ++pit) {
412 Paragraph & par = pars_[pit];
413 if (::changeDepthAllowed(type, par, max_depth)) {
414 int const depth = par.params().depth();
415 if (type == INC_DEPTH)
416 par.params().depth(depth + 1);
418 par.params().depth(depth - 1);
420 max_depth = par.getMaxDepthAfter();
422 // this handles the counter labels, and also fixes up
423 // depth values for follow-on (child) paragraphs
424 updateCounters(cur.buffer());
428 // set font over selection
429 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
431 BOOST_ASSERT(this == cur.text());
432 // if there is no selection just set the current_font
433 if (!cur.selection()) {
434 // Determine basis font
436 pit_type pit = cur.pit();
437 if (cur.pos() < pars_[pit].beginOfBody())
438 layoutfont = getLabelFont(pars_[pit]);
440 layoutfont = getLayoutFont(pit);
442 // Update current font
443 real_current_font.update(font,
444 cur.buffer().params().language,
447 // Reduce to implicit settings
448 current_font = real_current_font;
449 current_font.reduce(layoutfont);
450 // And resolve it completely
451 real_current_font.realize(layoutfont);
456 // Ok, we have a selection.
457 recordUndoSelection(cur);
459 DocIterator dit = cur.selectionBegin();
460 DocIterator ditend = cur.selectionEnd();
462 BufferParams const & params = cur.buffer().params();
464 // Don't use forwardChar here as ditend might have
465 // pos() == lastpos() and forwardChar would miss it.
466 // Can't use forwardPos either as this descends into
468 for (; dit != ditend; dit.forwardPosNoDescend()) {
469 if (dit.pos() != dit.lastpos()) {
470 LyXFont f = getFont(dit.paragraph(), dit.pos());
471 f.update(font, params.language, toggleall);
472 setCharFont(dit.pit(), dit.pos(), f);
478 // the cursor set functions have a special mechanism. When they
479 // realize you left an empty paragraph, they will delete it.
481 void LyXText::cursorHome(LCursor & cur)
483 BOOST_ASSERT(this == cur.text());
484 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
486 setCursor(cur, cur.pit(), row.pos());
490 void LyXText::cursorEnd(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 // if not on the last row of the par, put the cursor before
494 // the final space exept if I have a spanning inset or one string
495 // is so long that we force a break.
496 pos_type end = cur.textRow().endpos();
498 // empty text, end-1 is no valid position
500 bool boundary = false;
501 if (end != cur.lastpos()) {
502 if (!cur.paragraph().isLineSeparator(end-1)
503 && !cur.paragraph().isNewline(end-1))
508 setCursor(cur, cur.pit(), end, true, boundary);
512 void LyXText::cursorTop(LCursor & cur)
514 BOOST_ASSERT(this == cur.text());
515 setCursor(cur, 0, 0);
519 void LyXText::cursorBottom(LCursor & cur)
521 BOOST_ASSERT(this == cur.text());
522 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
526 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
528 BOOST_ASSERT(this == cur.text());
529 // If the mask is completely neutral, tell user
530 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
531 // Could only happen with user style
532 cur.message(_("No font change defined. "
533 "Use Character under the Layout menu to define font change."));
537 // Try implicit word selection
538 // If there is a change in the language the implicit word selection
540 CursorSlice resetCursor = cur.top();
541 bool implicitSelection =
542 font.language() == ignore_language
543 && font.number() == LyXFont::IGNORE
544 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
547 setFont(cur, font, toggleall);
549 // Implicit selections are cleared afterwards
550 // and cursor is set to the original position.
551 if (implicitSelection) {
552 cur.clearSelection();
553 cur.top() = resetCursor;
559 string LyXText::getStringToIndex(LCursor const & cur)
561 BOOST_ASSERT(this == cur.text());
564 if (cur.selection()) {
565 idxstring = cur.selectionAsString(false);
567 // Try implicit word selection. If there is a change
568 // in the language the implicit word selection is
570 LCursor tmpcur = cur;
571 selectWord(tmpcur, lyx::PREVIOUS_WORD);
573 if (!tmpcur.selection())
574 cur.message(_("Nothing to index!"));
575 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
576 cur.message(_("Cannot index more than one paragraph!"));
578 idxstring = tmpcur.selectionAsString(false);
585 void LyXText::setParagraph(LCursor & cur,
586 Spacing const & spacing, LyXAlignment align,
587 string const & labelwidthstring, bool noindent)
589 BOOST_ASSERT(cur.text());
590 // make sure that the depth behind the selection are restored, too
591 pit_type undopit = undoSpan(cur.selEnd().pit());
592 recUndo(cur.selBegin().pit(), undopit - 1);
594 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
596 Paragraph & par = pars_[pit];
597 ParagraphParameters & params = par.params();
598 params.spacing(spacing);
600 // does the layout allow the new alignment?
601 LyXLayout_ptr const & layout = par.layout();
603 if (align == LYX_ALIGN_LAYOUT)
604 align = layout->align;
605 if (align & layout->alignpossible) {
606 if (align == layout->align)
607 params.align(LYX_ALIGN_LAYOUT);
611 par.setLabelWidthString(labelwidthstring);
612 params.noindent(noindent);
617 // this really should just insert the inset and not move the cursor.
618 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
620 BOOST_ASSERT(this == cur.text());
622 cur.paragraph().insertInset(cur.pos(), inset);
626 // needed to insert the selection
627 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
629 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
630 current_font, str, autoBreakRows_);
634 // turn double CR to single CR, others are converted into one
635 // blank. Then insertStringAsLines is called
636 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
638 string linestr = str;
639 bool newline_inserted = false;
641 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
642 if (linestr[i] == '\n') {
643 if (newline_inserted) {
644 // we know that \r will be ignored by
645 // insertStringAsLines. Of course, it is a dirty
646 // trick, but it works...
647 linestr[i - 1] = '\r';
651 newline_inserted = true;
653 } else if (IsPrintable(linestr[i])) {
654 newline_inserted = false;
657 insertStringAsLines(cur, linestr);
661 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
662 bool setfont, bool boundary)
665 setCursorIntern(cur, par, pos, setfont, boundary);
666 return deleteEmptyParagraphMechanism(cur, old);
670 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
672 BOOST_ASSERT(par != int(paragraphs().size()));
676 // now some strict checking
677 Paragraph & para = getPar(par);
679 // None of these should happen, but we're scaredy-cats
681 lyxerr << "dont like -1" << endl;
685 if (pos > para.size()) {
686 lyxerr << "dont like 1, pos: " << pos
687 << " size: " << para.size()
688 << " par: " << par << endl;
694 void LyXText::setCursorIntern(LCursor & cur,
695 pit_type par, pos_type pos, bool setfont, bool boundary)
697 cur.boundary(boundary);
698 setCursor(cur.top(), par, pos);
705 void LyXText::setCurrentFont(LCursor & cur)
707 BOOST_ASSERT(this == cur.text());
708 pos_type pos = cur.pos();
709 Paragraph & par = cur.paragraph();
711 if (cur.boundary() && pos > 0)
715 if (pos == cur.lastpos())
717 else // potentional bug... BUG (Lgb)
718 if (par.isSeparator(pos)) {
719 if (pos > cur.textRow().pos() &&
720 bidi.level(pos) % 2 ==
721 bidi.level(pos - 1) % 2)
723 else if (pos + 1 < cur.lastpos())
728 BufferParams const & bufparams = cur.buffer().params();
729 current_font = par.getFontSettings(bufparams, pos);
730 real_current_font = getFont(par, pos);
732 if (cur.pos() == cur.lastpos()
733 && bidi.isBoundary(cur.buffer(), par, cur.pos())
734 && !cur.boundary()) {
735 Language const * lang = par.getParLanguage(bufparams);
736 current_font.setLanguage(lang);
737 current_font.setNumber(LyXFont::OFF);
738 real_current_font.setLanguage(lang);
739 real_current_font.setNumber(LyXFont::OFF);
744 // x is an absolute screen coord
745 // returns the column near the specified x-coordinate of the row
746 // x is set to the real beginning of this column
747 pos_type LyXText::getColumnNearX(pit_type const pit,
748 Row const & row, int & x, bool & boundary) const
750 int const xo = theCoords.get(this, pit).x_;
752 RowMetrics const r = computeRowMetrics(pit, row);
753 Paragraph const & par = pars_[pit];
755 pos_type vc = row.pos();
756 pos_type end = row.endpos();
758 LyXLayout_ptr const & layout = par.layout();
760 bool left_side = false;
762 pos_type body_pos = par.beginOfBody();
765 double last_tmpx = tmpx;
768 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
771 // check for empty row
777 while (vc < end && tmpx <= x) {
778 c = bidi.vis2log(vc);
780 if (body_pos > 0 && c == body_pos - 1) {
781 tmpx += r.label_hfill +
782 font_metrics::width(layout->labelsep, getLabelFont(par));
783 if (par.isLineSeparator(body_pos - 1))
784 tmpx -= singleWidth(par, body_pos - 1);
787 if (hfillExpansion(par, row, c)) {
788 tmpx += singleWidth(par, c);
792 tmpx += r.label_hfill;
793 } else if (par.isSeparator(c)) {
794 tmpx += singleWidth(par, c);
798 tmpx += singleWidth(par, c);
803 if ((tmpx + last_tmpx) / 2 > x) {
808 BOOST_ASSERT(vc <= end); // This shouldn't happen.
811 // This (rtl_support test) is not needed, but gives
812 // some speedup if rtl_support == false
813 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
815 // If lastrow is false, we don't need to compute
817 bool const rtl = lastrow ? isRTL(par) : false;
819 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
820 (!rtl && !left_side && vc == end && x > tmpx + 5)))
822 else if (vc == row.pos()) {
823 c = bidi.vis2log(vc);
824 if (bidi.level(c) % 2 == 1)
827 c = bidi.vis2log(vc - 1);
828 bool const rtl = (bidi.level(c) % 2 == 1);
829 if (left_side == rtl) {
831 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
835 // I believe this code is not needed anymore (Jug 20050717)
837 // The following code is necessary because the cursor position past
838 // the last char in a row is logically equivalent to that before
839 // the first char in the next row. That's why insets causing row
840 // divisions -- Newline and display-style insets -- must be treated
841 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
842 // Newline inset, air gap below:
843 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
844 if (bidi.level(end -1) % 2 == 0)
845 tmpx -= singleWidth(par, end - 1);
847 tmpx += singleWidth(par, end - 1);
851 // Air gap above display inset:
852 if (row.pos() < end && c >= end && end < par.size()
853 && par.isInset(end) && par.getInset(end)->display()) {
856 // Air gap below display inset:
857 if (row.pos() < end && c >= end && par.isInset(end - 1)
858 && par.getInset(end - 1)->display()) {
864 pos_type const col = c - row.pos();
866 if (!c || end == par.size())
869 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
874 return min(col, end - 1 - row.pos());
878 // y is screen coordinate
879 pit_type LyXText::getPitNearY(int y) const
881 BOOST_ASSERT(!paragraphs().empty());
882 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
883 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
885 << BOOST_CURRENT_FUNCTION
886 << ": y: " << y << " cache size: " << cc.size()
889 // look for highest numbered paragraph with y coordinate less than given y
892 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
893 CoordCache::InnerParPosCache::const_iterator et = cc.end();
894 for (; it != et; ++it) {
896 << BOOST_CURRENT_FUNCTION
897 << " examining: pit: " << it->first
898 << " y: " << it->second.y_
901 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
908 << BOOST_CURRENT_FUNCTION
909 << ": found best y: " << yy << " for pit: " << pit
916 Row const & LyXText::getRowNearY(int y, pit_type pit) const
918 Paragraph const & par = pars_[pit];
919 int yy = theCoords.get(this, pit).y_ - par.ascent();
920 BOOST_ASSERT(!par.rows().empty());
921 RowList::const_iterator rit = par.rows().begin();
922 RowList::const_iterator const rlast = boost::prior(par.rows().end());
923 for (; rit != rlast; yy += rit->height(), ++rit)
924 if (yy + rit->height() > y)
930 // x,y are absolute screen coordinates
931 // sets cursor recursively descending into nested editable insets
932 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
934 pit_type pit = getPitNearY(y);
935 BOOST_ASSERT(pit != -1);
936 Row const & row = getRowNearY(y, pit);
939 int xx = x; // is modified by getColumnNearX
940 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
946 // try to descend into nested insets
947 InsetBase * inset = checkInsetHit(x, y);
948 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
950 // Either we deconst editXY or better we move current_font
951 // and real_current_font to LCursor
956 // This should be just before or just behind the
957 // cursor position set above.
958 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
959 || inset == pars_[pit].getInset(pos));
960 // Make sure the cursor points to the position before
962 if (inset == pars_[pit].getInset(pos - 1))
964 inset = inset->editXY(cur, x, y);
965 if (cur.top().text() == this)
971 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
975 if (cur.pos() == cur.lastpos())
977 InsetBase * inset = cur.nextInset();
978 if (!isHighlyEditableInset(inset))
980 inset->edit(cur, front);
985 bool LyXText::cursorLeft(LCursor & cur)
987 if (!cur.boundary() && cur.pos() > 0 &&
988 cur.textRow().pos() == cur.pos() &&
989 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
990 !cur.paragraph().isNewline(cur.pos()-1)) {
991 return setCursor(cur, cur.pit(), cur.pos(), true, true);
993 if (cur.pos() != 0) {
994 bool boundary = cur.boundary();
995 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
996 if (!checkAndActivateInset(cur, false)) {
997 if (false && !boundary &&
998 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1000 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1002 return updateNeeded;
1005 if (cur.pit() != 0) {
1006 // Steps into the paragraph above
1007 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1013 bool LyXText::cursorRight(LCursor & cur)
1015 if (cur.pos() != cur.lastpos()) {
1017 return setCursor(cur, cur.pit(), cur.pos(),
1020 bool updateNeeded = false;
1021 if (!checkAndActivateInset(cur, true)) {
1022 if (cur.textRow().endpos() == cur.pos() + 1 &&
1023 cur.textRow().endpos() != cur.lastpos() &&
1024 !cur.paragraph().isLineSeparator(cur.pos()) &&
1025 !cur.paragraph().isNewline(cur.pos())) {
1028 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1029 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1031 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1033 return updateNeeded;
1036 if (cur.pit() != cur.lastpit())
1037 return setCursor(cur, cur.pit() + 1, 0);
1042 bool LyXText::cursorUp(LCursor & cur)
1044 Paragraph const & par = cur.paragraph();
1046 int const x = cur.targetX();
1048 if (cur.pos() && cur.boundary())
1049 row = par.pos2row(cur.pos()-1);
1051 row = par.pos2row(cur.pos());
1053 if (!cur.selection()) {
1054 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1056 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1057 cur.clearSelection();
1059 // This happens when you move out of an inset.
1060 // And to give the DEPM the possibility of doing
1061 // something we must provide it with two different
1063 LCursor dummy = cur;
1067 return deleteEmptyParagraphMechanism(dummy, old);
1070 bool updateNeeded = false;
1073 updateNeeded |= setCursor(cur, cur.pit(),
1074 x2pos(cur.pit(), row - 1, x));
1075 } else if (cur.pit() > 0) {
1077 //cannot use 'par' now
1078 updateNeeded |= setCursor(cur, cur.pit(),
1079 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1084 return updateNeeded;
1088 bool LyXText::cursorDown(LCursor & cur)
1090 Paragraph const & par = cur.paragraph();
1092 int const x = cur.targetX();
1094 if (cur.pos() && cur.boundary())
1095 row = par.pos2row(cur.pos()-1);
1097 row = par.pos2row(cur.pos());
1099 if (!cur.selection()) {
1100 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1102 editXY(cur, x, y + par.rows()[row].descent() + 1);
1103 cur.clearSelection();
1105 // This happens when you move out of an inset.
1106 // And to give the DEPM the possibility of doing
1107 // something we must provide it with two different
1109 LCursor dummy = cur;
1113 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1115 // Make sure that cur gets back whatever happened to dummy(Lgb)
1122 bool updateNeeded = false;
1124 if (row + 1 < int(par.rows().size())) {
1125 updateNeeded |= setCursor(cur, cur.pit(),
1126 x2pos(cur.pit(), row + 1, x));
1127 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1129 updateNeeded |= setCursor(cur, cur.pit(),
1130 x2pos(cur.pit(), 0, x));
1135 return updateNeeded;
1139 bool LyXText::cursorUpParagraph(LCursor & cur)
1141 bool updated = false;
1143 updated = setCursor(cur, cur.pit(), 0);
1144 else if (cur.pit() != 0)
1145 updated = setCursor(cur, cur.pit() - 1, 0);
1150 bool LyXText::cursorDownParagraph(LCursor & cur)
1152 bool updated = false;
1153 if (cur.pit() != cur.lastpit())
1154 updated = setCursor(cur, cur.pit() + 1, 0);
1156 updated = setCursor(cur, cur.pit(), cur.lastpos());
1161 // fix the cursor `cur' after a characters has been deleted at `where'
1162 // position. Called by deleteEmptyParagraphMechanism
1163 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1165 // Do nothing if cursor is not in the paragraph where the
1166 // deletion occured,
1167 if (cur.pit() != where.pit())
1170 // If cursor position is after the deletion place update it
1171 if (cur.pos() > where.pos())
1174 // Check also if we don't want to set the cursor on a spot behind the
1175 // pagragraph because we erased the last character.
1176 if (cur.pos() > cur.lastpos())
1177 cur.pos() = cur.lastpos();
1181 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1183 // Would be wrong to delete anything if we have a selection.
1184 if (cur.selection())
1187 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1188 // old should point to us
1189 BOOST_ASSERT(old.text() == this);
1191 Paragraph & oldpar = old.paragraph();
1193 // We allow all kinds of "mumbo-jumbo" when freespacing.
1194 if (oldpar.isFreeSpacing())
1197 /* Ok I'll put some comments here about what is missing.
1198 There are still some small problems that can lead to
1199 double spaces stored in the document file or space at
1200 the beginning of paragraphs(). This happens if you have
1201 the cursor between to spaces and then save. Or if you
1202 cut and paste and the selection have a space at the
1203 beginning and then save right after the paste. (Lgb)
1206 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1207 // delete the LineSeparator.
1210 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1211 // delete the LineSeparator.
1214 bool const same_inset = &old.inset() == &cur.inset();
1215 bool const same_par = same_inset && old.pit() == cur.pit();
1216 bool const same_par_pos = same_par && old.pos() == cur.pos();
1218 // If the chars around the old cursor were spaces, delete one of them.
1219 if (!same_par_pos) {
1220 // Only if the cursor has really moved.
1222 && old.pos() < oldpar.size()
1223 && oldpar.isLineSeparator(old.pos())
1224 && oldpar.isLineSeparator(old.pos() - 1)
1225 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1226 // We need to set the text to Change::INSERTED to
1227 // get it erased properly
1228 oldpar.setChange(old.pos() -1, Change::INSERTED);
1229 oldpar.erase(old.pos() - 1);
1230 #ifdef WITH_WARNINGS
1231 #warning This will not work anymore when we have multiple views of the same buffer
1232 // In this case, we will have to correct also the cursors held by
1233 // other bufferviews. It will probably be easier to do that in a more
1234 // automated way in CursorSlice code. (JMarc 26/09/2001)
1236 // correct all cursor parts
1238 fixCursorAfterDelete(cur.top(), old.top());
1245 // only do our magic if we changed paragraph
1249 // don't delete anything if this is the ONLY paragraph!
1250 if (old.lastpit() == 0)
1253 // Do not delete empty paragraphs with keepempty set.
1254 if (oldpar.allowEmpty())
1257 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1259 recordUndo(old, Undo::ATOMIC, old.pit());
1260 ParagraphList & plist = old.text()->paragraphs();
1261 plist.erase(plist.begin() + old.pit());
1263 // see #warning above
1264 if (cur.depth() >= old.depth()) {
1265 CursorSlice & curslice = cur[old.depth() - 1];
1266 if (&curslice.inset() == &old.inset()
1267 && curslice.pit() > old.pit()) {
1269 // since a paragraph has been deleted, all the
1270 // insets after `old' have been copied and
1271 // their address has changed. Therefore we
1272 // need to `regenerate' cur. (JMarc)
1273 cur.updateInsets(&(cur.bottom().inset()));
1277 updateCounters(old.buffer());
1281 if (oldpar.stripLeadingSpaces())
1288 void LyXText::recUndo(pit_type first, pit_type last) const
1290 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1294 void LyXText::recUndo(pit_type par) const
1296 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1300 int defaultRowHeight()
1302 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);