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;
70 LyXText::LyXText(BufferView * bv)
71 : maxwidth_(bv ? bv->workWidth() : 100),
72 current_font(LyXFont::ALL_INHERIT),
73 background_color_(LColor::background),
79 void LyXText::init(BufferView * bv)
83 maxwidth_ = bv->workWidth();
88 pit_type const end = paragraphs().size();
89 for (pit_type pit = 0; pit != end; ++pit)
90 pars_[pit].rows().clear();
92 current_font = getFont(pars_[0], 0);
93 updateCounters(*bv->buffer());
97 bool LyXText::isMainText() const
99 return &bv()->buffer()->text() == this;
103 //takes screen x,y coordinates
104 InsetBase * LyXText::checkInsetHit(int x, int y) const
106 pit_type pit = getPitNearY(y);
107 BOOST_ASSERT(pit != -1);
109 Paragraph const & par = pars_[pit];
112 << BOOST_CURRENT_FUNCTION
117 InsetList::const_iterator iit = par.insetlist.begin();
118 InsetList::const_iterator iend = par.insetlist.end();
119 for (; iit != iend; ++iit) {
120 InsetBase * inset = iit->inset;
123 << BOOST_CURRENT_FUNCTION
124 << ": examining inset " << inset << endl;
126 if (theCoords.getInsets().has(inset))
128 << BOOST_CURRENT_FUNCTION
129 << ": xo: " << inset->xo() << "..."
130 << inset->xo() + inset->width()
131 << " yo: " << inset->yo() - inset->ascent()
133 << inset->yo() + inset->descent()
137 << BOOST_CURRENT_FUNCTION
138 << ": inset has no cached position" << endl;
140 if (inset->covers(x, y)) {
142 << BOOST_CURRENT_FUNCTION
143 << ": Hit inset: " << inset << endl;
148 << BOOST_CURRENT_FUNCTION
149 << ": No inset hit. " << endl;
155 // Gets the fully instantiated font at a given position in a paragraph
156 // Basically the same routine as Paragraph::getFont() in paragraph.C.
157 // The difference is that this one is used for displaying, and thus we
158 // are allowed to make cosmetic improvements. For instance make footnotes
160 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
162 BOOST_ASSERT(pos >= 0);
164 LyXLayout_ptr const & layout = par.layout();
168 BufferParams const & params = bv()->buffer()->params();
169 pos_type const body_pos = par.beginOfBody();
171 // We specialize the 95% common case:
172 if (!par.getDepth()) {
173 LyXFont f = par.getFontSettings(params, pos);
176 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
177 return f.realize(layout->reslabelfont);
179 return f.realize(layout->resfont);
182 // The uncommon case need not be optimized as much
185 layoutfont = layout->labelfont;
187 layoutfont = layout->font;
189 LyXFont font = par.getFontSettings(params, pos);
190 font.realize(layoutfont);
193 applyOuterFont(font);
195 // Find the pit value belonging to paragraph. This will not break
196 // even if pars_ would not be a vector anymore.
197 // Performance appears acceptable.
199 pit_type pit = pars_.size();
200 for (pit_type it = 0; it < pit; ++it)
201 if (&pars_[it] == &par) {
205 // Realize against environment font information
206 if (pit < pars_.size())
207 font.realize(outerFont(pit, pars_));
209 // Realize with the fonts of lesser depth.
210 font.realize(params.getFont());
215 // There are currently two font mechanisms in LyX:
216 // 1. The font attributes in a lyxtext, and
217 // 2. The inset-specific font properties, defined in an inset's
218 // metrics() and draw() methods and handed down the inset chain through
219 // the pi/mi parameters, and stored locally in a lyxtext in font_.
220 // This is where the two are integrated in the final fully realized
222 void LyXText::applyOuterFont(LyXFont & font) const {
224 lf.reduce(bv()->buffer()->params().getFont());
226 lf.setLanguage(font.language());
231 LyXFont LyXText::getLayoutFont(pit_type const pit) const
233 LyXLayout_ptr const & layout = pars_[pit].layout();
235 if (!pars_[pit].getDepth())
236 return layout->resfont;
238 LyXFont font = layout->font;
239 // Realize with the fonts of lesser depth.
240 //font.realize(outerFont(pit, paragraphs()));
241 font.realize(bv()->buffer()->params().getFont());
247 LyXFont LyXText::getLabelFont(Paragraph const & par) const
249 LyXLayout_ptr const & layout = par.layout();
252 return layout->reslabelfont;
254 LyXFont font = layout->labelfont;
255 // Realize with the fonts of lesser depth.
256 font.realize(bv()->buffer()->params().getFont());
262 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
265 LyXLayout_ptr const & layout = pars_[pit].layout();
267 // Get concrete layout font to reduce against
270 if (pos < pars_[pit].beginOfBody())
271 layoutfont = layout->labelfont;
273 layoutfont = layout->font;
275 // Realize against environment font information
276 if (pars_[pit].getDepth()) {
278 while (!layoutfont.resolved() &&
279 tp != pit_type(paragraphs().size()) &&
280 pars_[tp].getDepth()) {
281 tp = outerHook(tp, paragraphs());
282 if (tp != pit_type(paragraphs().size()))
283 layoutfont.realize(pars_[tp].layout()->font);
287 // Inside inset, apply the inset's font attributes if any
290 layoutfont.realize(font_);
292 layoutfont.realize(bv()->buffer()->params().getFont());
294 // Now, reduce font against full layout font
295 font.reduce(layoutfont);
297 pars_[pit].setFont(pos, font);
301 // return past-the-last paragraph influenced by a layout change on pit
302 pit_type LyXText::undoSpan(pit_type pit)
304 pit_type end = paragraphs().size();
305 pit_type nextpit = pit + 1;
308 //because of parindents
309 if (!pars_[pit].getDepth())
310 return boost::next(nextpit);
311 //because of depth constrains
312 for (; nextpit != end; ++pit, ++nextpit) {
313 if (!pars_[pit].getDepth())
320 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
322 BOOST_ASSERT(start != end);
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 if (lyxlayout->margintype == MARGIN_MANUAL)
330 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
335 // set layout over selection and make a total rebreak of those paragraphs
336 void LyXText::setLayout(LCursor & cur, string const & layout)
338 BOOST_ASSERT(this == cur.text());
339 // special handling of new environment insets
340 BufferView & bv = cur.bv();
341 BufferParams const & params = bv.buffer()->params();
342 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
343 if (lyxlayout->is_environment) {
344 // move everything in a new environment inset
345 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
346 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
347 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
348 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
349 InsetBase * inset = new InsetEnvironment(params, layout);
350 insertInset(cur, inset);
351 //inset->edit(cur, true);
352 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
356 pit_type start = cur.selBegin().pit();
357 pit_type end = cur.selEnd().pit() + 1;
358 pit_type undopit = undoSpan(end - 1);
359 recUndo(start, undopit - 1);
360 setLayout(start, end, layout);
361 updateCounters(cur.buffer());
368 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
369 Paragraph const & par, int max_depth)
371 if (par.layout()->labeltype == LABEL_BIBLIO)
373 int const depth = par.params().depth();
374 if (type == LyXText::INC_DEPTH && depth < max_depth)
376 if (type == LyXText::DEC_DEPTH && depth > 0)
385 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
387 BOOST_ASSERT(this == cur.text());
388 pit_type const beg = cur.selBegin().pit();
389 pit_type const end = cur.selEnd().pit() + 1;
390 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
392 for (pit_type pit = beg; pit != end; ++pit) {
393 if (::changeDepthAllowed(type, pars_[pit], max_depth))
395 max_depth = pars_[pit].getMaxDepthAfter();
401 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
403 BOOST_ASSERT(this == cur.text());
404 pit_type const beg = cur.selBegin().pit();
405 pit_type const end = cur.selEnd().pit() + 1;
406 recordUndoSelection(cur);
407 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
409 for (pit_type pit = beg; pit != end; ++pit) {
410 Paragraph & par = pars_[pit];
411 if (::changeDepthAllowed(type, par, max_depth)) {
412 int const depth = par.params().depth();
413 if (type == INC_DEPTH)
414 par.params().depth(depth + 1);
416 par.params().depth(depth - 1);
418 max_depth = par.getMaxDepthAfter();
420 // this handles the counter labels, and also fixes up
421 // depth values for follow-on (child) paragraphs
422 updateCounters(cur.buffer());
426 // set font over selection
427 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
429 BOOST_ASSERT(this == cur.text());
430 // if there is no selection just set the current_font
431 if (!cur.selection()) {
432 // Determine basis font
434 pit_type pit = cur.pit();
435 if (cur.pos() < pars_[pit].beginOfBody())
436 layoutfont = getLabelFont(pars_[pit]);
438 layoutfont = getLayoutFont(pit);
440 // Update current font
441 real_current_font.update(font,
442 cur.buffer().params().language,
445 // Reduce to implicit settings
446 current_font = real_current_font;
447 current_font.reduce(layoutfont);
448 // And resolve it completely
449 real_current_font.realize(layoutfont);
454 // Ok, we have a selection.
455 recordUndoSelection(cur);
457 DocIterator dit = cur.selectionBegin();
458 DocIterator ditend = cur.selectionEnd();
460 BufferParams const & params = cur.buffer().params();
462 // Don't use forwardChar here as ditend might have
463 // pos() == lastpos() and forwardChar would miss it.
464 // Can't use forwardPos either as this descends into
466 for (; dit != ditend; dit.forwardPosNoDescend()) {
467 if (dit.pos() != dit.lastpos()) {
468 LyXFont f = getFont(dit.paragraph(), dit.pos());
469 f.update(font, params.language, toggleall);
470 setCharFont(dit.pit(), dit.pos(), f);
476 // the cursor set functions have a special mechanism. When they
477 // realize you left an empty paragraph, they will delete it.
479 bool LyXText::cursorHome(LCursor & cur)
481 BOOST_ASSERT(this == cur.text());
482 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
484 return setCursor(cur, cur.pit(), row.pos());
488 bool LyXText::cursorEnd(LCursor & cur)
490 BOOST_ASSERT(this == cur.text());
491 // if not on the last row of the par, put the cursor before
492 // the final space exept if I have a spanning inset or one string
493 // is so long that we force a break.
494 pos_type end = cur.textRow().endpos();
496 // empty text, end-1 is no valid position
498 bool boundary = false;
499 if (end != cur.lastpos()) {
500 if (!cur.paragraph().isLineSeparator(end-1)
501 && !cur.paragraph().isNewline(end-1))
506 return setCursor(cur, cur.pit(), end, true, boundary);
510 bool LyXText::cursorTop(LCursor & cur)
512 BOOST_ASSERT(this == cur.text());
513 return setCursor(cur, 0, 0);
517 bool LyXText::cursorBottom(LCursor & cur)
519 BOOST_ASSERT(this == cur.text());
520 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
524 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
526 BOOST_ASSERT(this == cur.text());
527 // If the mask is completely neutral, tell user
528 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
529 // Could only happen with user style
530 cur.message(_("No font change defined. "
531 "Use Character under the Layout menu to define font change."));
535 // Try implicit word selection
536 // If there is a change in the language the implicit word selection
538 CursorSlice resetCursor = cur.top();
539 bool implicitSelection =
540 font.language() == ignore_language
541 && font.number() == LyXFont::IGNORE
542 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
545 setFont(cur, font, toggleall);
547 // Implicit selections are cleared afterwards
548 // and cursor is set to the original position.
549 if (implicitSelection) {
550 cur.clearSelection();
551 cur.top() = resetCursor;
557 string LyXText::getStringToIndex(LCursor const & cur)
559 BOOST_ASSERT(this == cur.text());
562 if (cur.selection()) {
563 idxstring = cur.selectionAsString(false);
565 // Try implicit word selection. If there is a change
566 // in the language the implicit word selection is
568 LCursor tmpcur = cur;
569 selectWord(tmpcur, lyx::PREVIOUS_WORD);
571 if (!tmpcur.selection())
572 cur.message(_("Nothing to index!"));
573 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
574 cur.message(_("Cannot index more than one paragraph!"));
576 idxstring = tmpcur.selectionAsString(false);
583 void LyXText::setParagraph(LCursor & cur,
584 Spacing const & spacing, LyXAlignment align,
585 string const & labelwidthstring, bool noindent)
587 BOOST_ASSERT(cur.text());
588 // make sure that the depth behind the selection are restored, too
589 pit_type undopit = undoSpan(cur.selEnd().pit());
590 recUndo(cur.selBegin().pit(), undopit - 1);
592 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
594 Paragraph & par = pars_[pit];
595 ParagraphParameters & params = par.params();
596 params.spacing(spacing);
598 // does the layout allow the new alignment?
599 LyXLayout_ptr const & layout = par.layout();
601 if (align == LYX_ALIGN_LAYOUT)
602 align = layout->align;
603 if (align & layout->alignpossible) {
604 if (align == layout->align)
605 params.align(LYX_ALIGN_LAYOUT);
609 par.setLabelWidthString(labelwidthstring);
610 params.noindent(noindent);
615 // this really should just insert the inset and not move the cursor.
616 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
618 BOOST_ASSERT(this == cur.text());
620 cur.paragraph().insertInset(cur.pos(), inset);
624 // needed to insert the selection
625 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
627 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
628 current_font, str, autoBreakRows_);
632 // turn double CR to single CR, others are converted into one
633 // blank. Then insertStringAsLines is called
634 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
636 string linestr = str;
637 bool newline_inserted = false;
639 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
640 if (linestr[i] == '\n') {
641 if (newline_inserted) {
642 // we know that \r will be ignored by
643 // insertStringAsLines. Of course, it is a dirty
644 // trick, but it works...
645 linestr[i - 1] = '\r';
649 newline_inserted = true;
651 } else if (isPrintable(linestr[i])) {
652 newline_inserted = false;
655 insertStringAsLines(cur, linestr);
659 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
660 bool setfont, bool boundary)
663 setCursorIntern(cur, par, pos, setfont, boundary);
664 return deleteEmptyParagraphMechanism(cur, old);
668 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
670 BOOST_ASSERT(par != int(paragraphs().size()));
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 cur.boundary(boundary);
696 setCursor(cur.top(), par, pos);
703 void LyXText::setCurrentFont(LCursor & cur)
705 BOOST_ASSERT(this == cur.text());
706 pos_type pos = cur.pos();
707 Paragraph & par = cur.paragraph();
709 if (cur.boundary() && pos > 0)
713 if (pos == cur.lastpos())
715 else // potentional bug... BUG (Lgb)
716 if (par.isSeparator(pos)) {
717 if (pos > cur.textRow().pos() &&
718 bidi.level(pos) % 2 ==
719 bidi.level(pos - 1) % 2)
721 else if (pos + 1 < cur.lastpos())
726 BufferParams const & bufparams = cur.buffer().params();
727 current_font = par.getFontSettings(bufparams, pos);
728 real_current_font = getFont(par, pos);
730 if (cur.pos() == cur.lastpos()
731 && bidi.isBoundary(cur.buffer(), par, cur.pos())
732 && !cur.boundary()) {
733 Language const * lang = par.getParLanguage(bufparams);
734 current_font.setLanguage(lang);
735 current_font.setNumber(LyXFont::OFF);
736 real_current_font.setLanguage(lang);
737 real_current_font.setNumber(LyXFont::OFF);
742 // x is an absolute screen coord
743 // returns the column near the specified x-coordinate of the row
744 // x is set to the real beginning of this column
745 pos_type LyXText::getColumnNearX(pit_type const pit,
746 Row const & row, int & x, bool & boundary) const
748 int const xo = theCoords.get(this, pit).x_;
750 RowMetrics const r = computeRowMetrics(pit, row);
751 Paragraph const & par = pars_[pit];
753 pos_type vc = row.pos();
754 pos_type end = row.endpos();
756 LyXLayout_ptr const & layout = par.layout();
758 bool left_side = false;
760 pos_type body_pos = par.beginOfBody();
763 double last_tmpx = tmpx;
766 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
769 // check for empty row
775 while (vc < end && tmpx <= x) {
776 c = bidi.vis2log(vc);
778 if (body_pos > 0 && c == body_pos - 1) {
779 tmpx += r.label_hfill +
780 font_metrics::width(layout->labelsep, getLabelFont(par));
781 if (par.isLineSeparator(body_pos - 1))
782 tmpx -= singleWidth(par, body_pos - 1);
785 if (hfillExpansion(par, row, c)) {
786 tmpx += singleWidth(par, c);
790 tmpx += r.label_hfill;
791 } else if (par.isSeparator(c)) {
792 tmpx += singleWidth(par, c);
796 tmpx += singleWidth(par, c);
801 if ((tmpx + last_tmpx) / 2 > x) {
806 BOOST_ASSERT(vc <= end); // This shouldn't happen.
809 // This (rtl_support test) is not needed, but gives
810 // some speedup if rtl_support == false
811 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
813 // If lastrow is false, we don't need to compute
815 bool const rtl = lastrow ? isRTL(par) : false;
817 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
818 (!rtl && !left_side && vc == end && x > tmpx + 5)))
820 else if (vc == row.pos()) {
821 c = bidi.vis2log(vc);
822 if (bidi.level(c) % 2 == 1)
825 c = bidi.vis2log(vc - 1);
826 bool const rtl = (bidi.level(c) % 2 == 1);
827 if (left_side == rtl) {
829 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
833 // I believe this code is not needed anymore (Jug 20050717)
835 // The following code is necessary because the cursor position past
836 // the last char in a row is logically equivalent to that before
837 // the first char in the next row. That's why insets causing row
838 // divisions -- Newline and display-style insets -- must be treated
839 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
840 // Newline inset, air gap below:
841 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
842 if (bidi.level(end -1) % 2 == 0)
843 tmpx -= singleWidth(par, end - 1);
845 tmpx += singleWidth(par, end - 1);
849 // Air gap above display inset:
850 if (row.pos() < end && c >= end && end < par.size()
851 && par.isInset(end) && par.getInset(end)->display()) {
854 // Air gap below display inset:
855 if (row.pos() < end && c >= end && par.isInset(end - 1)
856 && par.getInset(end - 1)->display()) {
862 pos_type const col = c - row.pos();
864 if (!c || end == par.size())
867 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
872 return min(col, end - 1 - row.pos());
876 // y is screen coordinate
877 pit_type LyXText::getPitNearY(int y) const
879 BOOST_ASSERT(!paragraphs().empty());
880 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
881 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
883 << BOOST_CURRENT_FUNCTION
884 << ": y: " << y << " cache size: " << cc.size()
887 // look for highest numbered paragraph with y coordinate less than given y
890 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
891 CoordCache::InnerParPosCache::const_iterator et = cc.end();
892 for (; it != et; ++it) {
894 << BOOST_CURRENT_FUNCTION
895 << " examining: pit: " << it->first
896 << " y: " << it->second.y_
899 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
906 << BOOST_CURRENT_FUNCTION
907 << ": found best y: " << yy << " for pit: " << pit
914 Row const & LyXText::getRowNearY(int y, pit_type pit) const
916 Paragraph const & par = pars_[pit];
917 int yy = theCoords.get(this, pit).y_ - par.ascent();
918 BOOST_ASSERT(!par.rows().empty());
919 RowList::const_iterator rit = par.rows().begin();
920 RowList::const_iterator const rlast = boost::prior(par.rows().end());
921 for (; rit != rlast; yy += rit->height(), ++rit)
922 if (yy + rit->height() > y)
928 // x,y are absolute screen coordinates
929 // sets cursor recursively descending into nested editable insets
930 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
932 pit_type pit = getPitNearY(y);
933 BOOST_ASSERT(pit != -1);
934 Row const & row = getRowNearY(y, pit);
937 int xx = x; // is modified by getColumnNearX
938 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
944 // try to descend into nested insets
945 InsetBase * inset = checkInsetHit(x, y);
946 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
948 // Either we deconst editXY or better we move current_font
949 // and real_current_font to LCursor
954 // This should be just before or just behind the
955 // cursor position set above.
956 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
957 || inset == pars_[pit].getInset(pos));
958 // Make sure the cursor points to the position before
960 if (inset == pars_[pit].getInset(pos - 1))
962 inset = inset->editXY(cur, x, y);
963 if (cur.top().text() == this)
969 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
973 if (cur.pos() == cur.lastpos())
975 InsetBase * inset = cur.nextInset();
976 if (!isHighlyEditableInset(inset))
978 inset->edit(cur, front);
983 bool LyXText::cursorLeft(LCursor & cur)
985 if (!cur.boundary() && cur.pos() > 0 &&
986 cur.textRow().pos() == cur.pos() &&
987 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
988 !cur.paragraph().isNewline(cur.pos()-1)) {
989 return setCursor(cur, cur.pit(), cur.pos(), true, true);
991 if (cur.pos() != 0) {
992 bool boundary = cur.boundary();
993 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
994 if (!checkAndActivateInset(cur, false)) {
995 if (false && !boundary &&
996 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
998 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1000 return updateNeeded;
1003 if (cur.pit() != 0) {
1004 // Steps into the paragraph above
1005 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1011 bool LyXText::cursorRight(LCursor & cur)
1013 if (cur.pos() != cur.lastpos()) {
1015 return setCursor(cur, cur.pit(), cur.pos(),
1018 bool updateNeeded = false;
1019 if (!checkAndActivateInset(cur, true)) {
1020 if (cur.textRow().endpos() == cur.pos() + 1 &&
1021 cur.textRow().endpos() != cur.lastpos() &&
1022 !cur.paragraph().isLineSeparator(cur.pos()) &&
1023 !cur.paragraph().isNewline(cur.pos())) {
1026 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1027 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1029 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1031 return updateNeeded;
1034 if (cur.pit() != cur.lastpit())
1035 return setCursor(cur, cur.pit() + 1, 0);
1040 bool LyXText::cursorUp(LCursor & cur)
1042 Paragraph const & par = cur.paragraph();
1044 int const x = cur.targetX();
1046 if (cur.pos() && cur.boundary())
1047 row = par.pos2row(cur.pos()-1);
1049 row = par.pos2row(cur.pos());
1051 if (!cur.selection()) {
1052 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1054 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1055 cur.clearSelection();
1057 // This happens when you move out of an inset.
1058 // And to give the DEPM the possibility of doing
1059 // something we must provide it with two different
1061 LCursor dummy = cur;
1065 return deleteEmptyParagraphMechanism(dummy, old);
1068 bool updateNeeded = false;
1071 updateNeeded |= setCursor(cur, cur.pit(),
1072 x2pos(cur.pit(), row - 1, x));
1073 } else if (cur.pit() > 0) {
1075 //cannot use 'par' now
1076 updateNeeded |= setCursor(cur, cur.pit(),
1077 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1082 return updateNeeded;
1086 bool LyXText::cursorDown(LCursor & cur)
1088 Paragraph const & par = cur.paragraph();
1090 int const x = cur.targetX();
1092 if (cur.pos() && cur.boundary())
1093 row = par.pos2row(cur.pos()-1);
1095 row = par.pos2row(cur.pos());
1097 if (!cur.selection()) {
1098 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1100 editXY(cur, x, y + par.rows()[row].descent() + 1);
1101 cur.clearSelection();
1103 // This happens when you move out of an inset.
1104 // And to give the DEPM the possibility of doing
1105 // something we must provide it with two different
1107 LCursor dummy = cur;
1111 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1113 // Make sure that cur gets back whatever happened to dummy(Lgb)
1120 bool updateNeeded = false;
1122 if (row + 1 < int(par.rows().size())) {
1123 updateNeeded |= setCursor(cur, cur.pit(),
1124 x2pos(cur.pit(), row + 1, x));
1125 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1127 updateNeeded |= setCursor(cur, cur.pit(),
1128 x2pos(cur.pit(), 0, x));
1133 return updateNeeded;
1137 bool LyXText::cursorUpParagraph(LCursor & cur)
1139 bool updated = false;
1141 updated = setCursor(cur, cur.pit(), 0);
1142 else if (cur.pit() != 0)
1143 updated = setCursor(cur, cur.pit() - 1, 0);
1148 bool LyXText::cursorDownParagraph(LCursor & cur)
1150 bool updated = false;
1151 if (cur.pit() != cur.lastpit())
1152 updated = setCursor(cur, cur.pit() + 1, 0);
1154 updated = setCursor(cur, cur.pit(), cur.lastpos());
1159 // fix the cursor `cur' after a characters has been deleted at `where'
1160 // position. Called by deleteEmptyParagraphMechanism
1161 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1163 // Do nothing if cursor is not in the paragraph where the
1164 // deletion occured,
1165 if (cur.pit() != where.pit())
1168 // If cursor position is after the deletion place update it
1169 if (cur.pos() > where.pos())
1172 // Check also if we don't want to set the cursor on a spot behind the
1173 // pagragraph because we erased the last character.
1174 if (cur.pos() > cur.lastpos())
1175 cur.pos() = cur.lastpos();
1179 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1181 // Would be wrong to delete anything if we have a selection.
1182 if (cur.selection())
1185 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1186 // old should point to us
1187 BOOST_ASSERT(old.text() == this);
1189 Paragraph & oldpar = old.paragraph();
1191 // We allow all kinds of "mumbo-jumbo" when freespacing.
1192 if (oldpar.isFreeSpacing())
1195 /* Ok I'll put some comments here about what is missing.
1196 There are still some small problems that can lead to
1197 double spaces stored in the document file or space at
1198 the beginning of paragraphs(). This happens if you have
1199 the cursor between to spaces and then save. Or if you
1200 cut and paste and the selection have a space at the
1201 beginning and then save right after the paste. (Lgb)
1204 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1205 // delete the LineSeparator.
1208 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1209 // delete the LineSeparator.
1212 bool const same_inset = &old.inset() == &cur.inset();
1213 bool const same_par = same_inset && old.pit() == cur.pit();
1214 bool const same_par_pos = same_par && old.pos() == cur.pos();
1216 // If the chars around the old cursor were spaces, delete one of them.
1217 if (!same_par_pos) {
1218 // Only if the cursor has really moved.
1220 && old.pos() < oldpar.size()
1221 && oldpar.isLineSeparator(old.pos())
1222 && oldpar.isLineSeparator(old.pos() - 1)
1223 && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1224 // We need to set the text to Change::INSERTED to
1225 // get it erased properly
1226 oldpar.setChange(old.pos() -1, Change::INSERTED);
1227 oldpar.erase(old.pos() - 1);
1228 #ifdef WITH_WARNINGS
1229 #warning This will not work anymore when we have multiple views of the same buffer
1230 // In this case, we will have to correct also the cursors held by
1231 // other bufferviews. It will probably be easier to do that in a more
1232 // automated way in CursorSlice code. (JMarc 26/09/2001)
1234 // correct all cursor parts
1236 fixCursorAfterDelete(cur.top(), old.top());
1243 // only do our magic if we changed paragraph
1247 // don't delete anything if this is the ONLY paragraph!
1248 if (old.lastpit() == 0)
1251 // Do not delete empty paragraphs with keepempty set.
1252 if (oldpar.allowEmpty())
1255 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1257 recordUndo(old, Undo::ATOMIC,
1258 max(old.pit() - 1, pit_type(0)),
1259 min(old.pit() + 1, old.lastpit()));
1260 ParagraphList & plist = old.text()->paragraphs();
1261 plist.erase(boost::next(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 ParIterator par_it(old);
1278 if (needsUpdateCounters(old.buffer(), par_it))
1279 updateCounters(old.buffer());
1283 if (oldpar.stripLeadingSpaces())
1290 void LyXText::recUndo(pit_type first, pit_type last) const
1292 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1296 void LyXText::recUndo(pit_type par) const
1298 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1302 int defaultRowHeight()
1304 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);