3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
29 #include "coordcache.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "funcrequest.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "pariterator.h"
49 #include "frontends/font_metrics.h"
50 #include "frontends/LyXView.h"
52 #include "insets/insetenv.h"
54 #include "support/textutils.h"
62 using std::ostringstream;
67 LyXText::LyXText(BufferView * bv)
68 : maxwidth_(bv ? bv->workWidth() : 100),
69 background_color_(LColor::background),
75 void LyXText::init(BufferView * bv)
79 maxwidth_ = bv->workWidth();
84 pit_type const end = paragraphs().size();
85 for (pit_type pit = 0; pit != end; ++pit)
86 pars_[pit].rows().clear();
88 current_font = getFont(pars_[0], 0);
89 updateCounters(*bv->buffer());
93 bool LyXText::isMainText() const
95 return &bv()->buffer()->text() == this;
99 //takes screen x,y coordinates
100 InsetBase * LyXText::checkInsetHit(int x, int y) const
102 pit_type pit = getPitNearY(y);
103 BOOST_ASSERT(pit != -1);
105 Paragraph const & par = pars_[pit];
108 << BOOST_CURRENT_FUNCTION
113 InsetList::const_iterator iit = par.insetlist.begin();
114 InsetList::const_iterator iend = par.insetlist.end();
115 for (; iit != iend; ++iit) {
116 InsetBase * inset = iit->inset;
119 << BOOST_CURRENT_FUNCTION
120 << ": examining inset " << inset << endl;
122 if (theCoords.getInsets().has(inset))
124 << BOOST_CURRENT_FUNCTION
125 << ": xo: " << inset->xo() << "..."
126 << inset->xo() + inset->width()
127 << " yo: " << inset->yo() - inset->ascent()
129 << inset->yo() + inset->descent()
133 << BOOST_CURRENT_FUNCTION
134 << ": inset has no cached position" << endl;
136 if (inset->covers(x, y)) {
138 << BOOST_CURRENT_FUNCTION
139 << ": Hit inset: " << inset << endl;
144 << BOOST_CURRENT_FUNCTION
145 << ": No inset hit. " << endl;
151 // Gets the fully instantiated font at a given position in a paragraph
152 // Basically the same routine as Paragraph::getFont() in paragraph.C.
153 // The difference is that this one is used for displaying, and thus we
154 // are allowed to make cosmetic improvements. For instance make footnotes
156 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
158 BOOST_ASSERT(pos >= 0);
160 LyXLayout_ptr const & layout = par.layout();
164 BufferParams const & params = bv()->buffer()->params();
165 pos_type const body_pos = par.beginOfBody();
167 // We specialize the 95% common case:
168 if (!par.getDepth()) {
169 LyXFont f = par.getFontSettings(params, pos);
172 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
173 return f.realize(layout->reslabelfont);
175 return f.realize(layout->resfont);
178 // The uncommon case need not be optimized as much
181 layoutfont = layout->labelfont;
183 layoutfont = layout->font;
185 LyXFont font = par.getFontSettings(params, pos);
186 font.realize(layoutfont);
189 applyOuterFont(font);
191 // Realize with the fonts of lesser depth.
192 font.realize(defaultfont_);
197 // There are currently two font mechanisms in LyX:
198 // 1. The font attributes in a lyxtext, and
199 // 2. The inset-specific font properties, defined in an inset's
200 // metrics() and draw() methods and handed down the inset chain through
201 // the pi/mi parameters, and stored locally in a lyxtext in font_.
202 // This is where the two are integrated in the final fully realized
204 void LyXText::applyOuterFont(LyXFont & font) const {
206 lf.reduce(defaultfont_);
208 lf.setLanguage(font.language());
213 LyXFont LyXText::getLayoutFont(pit_type const pit) const
215 LyXLayout_ptr const & layout = pars_[pit].layout();
217 if (!pars_[pit].getDepth())
218 return layout->resfont;
220 LyXFont font = layout->font;
221 // Realize with the fonts of lesser depth.
222 //font.realize(outerFont(pit, paragraphs()));
223 font.realize(defaultfont_);
229 LyXFont LyXText::getLabelFont(Paragraph const & par) const
231 LyXLayout_ptr const & layout = par.layout();
234 return layout->reslabelfont;
236 LyXFont font = layout->labelfont;
237 // Realize with the fonts of lesser depth.
238 font.realize(defaultfont_);
244 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
247 LyXLayout_ptr const & layout = pars_[pit].layout();
249 // Get concrete layout font to reduce against
252 if (pos < pars_[pit].beginOfBody())
253 layoutfont = layout->labelfont;
255 layoutfont = layout->font;
257 // Realize against environment font information
258 if (pars_[pit].getDepth()) {
260 while (!layoutfont.resolved() &&
261 tp != pit_type(paragraphs().size()) &&
262 pars_[tp].getDepth()) {
263 tp = outerHook(tp, paragraphs());
264 if (tp != pit_type(paragraphs().size()))
265 layoutfont.realize(pars_[tp].layout()->font);
269 layoutfont.realize(defaultfont_);
271 // Now, reduce font against full layout font
272 font.reduce(layoutfont);
274 pars_[pit].setFont(pos, font);
279 // Asger is not sure we want to do this...
280 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
283 LyXLayout_ptr const & layout = par.layout();
284 pos_type const psize = par.size();
287 for (pos_type pos = 0; pos < psize; ++pos) {
288 if (pos < par.beginOfBody())
289 layoutfont = layout->labelfont;
291 layoutfont = layout->font;
293 LyXFont tmpfont = par.getFontSettings(params, pos);
294 tmpfont.reduce(layoutfont);
295 par.setFont(pos, tmpfont);
300 // return past-the-last paragraph influenced by a layout change on pit
301 pit_type LyXText::undoSpan(pit_type pit)
303 pit_type end = paragraphs().size();
304 pit_type nextpit = pit + 1;
307 //because of parindents
308 if (!pars_[pit].getDepth())
309 return boost::next(nextpit);
310 //because of depth constrains
311 for (; nextpit != end; ++pit, ++nextpit) {
312 if (!pars_[pit].getDepth())
319 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
321 BOOST_ASSERT(start != end);
322 pit_type undopit = undoSpan(end - 1);
323 recUndo(start, undopit - 1);
325 BufferParams const & bufparams = bv()->buffer()->params();
326 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
328 for (pit_type pit = start; pit != end; ++pit) {
329 pars_[pit].applyLayout(lyxlayout);
330 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
331 if (lyxlayout->margintype == MARGIN_MANUAL)
332 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
339 // set layout over selection and make a total rebreak of those paragraphs
340 void LyXText::setLayout(LCursor & cur, string const & layout)
342 BOOST_ASSERT(this == cur.text());
343 // special handling of new environment insets
344 BufferView & bv = cur.bv();
345 BufferParams const & params = bv.buffer()->params();
346 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
347 if (lyxlayout->is_environment) {
348 // move everything in a new environment inset
349 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
350 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
351 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
352 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
353 InsetBase * inset = new InsetEnvironment(params, layout);
354 insertInset(cur, inset);
355 //inset->edit(cur, true);
356 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
360 pit_type start = cur.selBegin().pit();
361 pit_type end = cur.selEnd().pit() + 1;
362 setLayout(start, end, layout);
363 updateCounters(cur.buffer());
370 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
371 Paragraph const & par, int max_depth)
373 if (par.layout()->labeltype == LABEL_BIBLIO)
375 int const depth = par.params().depth();
376 if (type == LyXText::INC_DEPTH && depth < max_depth)
378 if (type == LyXText::DEC_DEPTH && depth > 0)
387 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
389 BOOST_ASSERT(this == cur.text());
390 pit_type const beg = cur.selBegin().pit();
391 pit_type const end = cur.selEnd().pit() + 1;
392 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
394 for (pit_type pit = beg; pit != end; ++pit) {
395 if (::changeDepthAllowed(type, pars_[pit], max_depth))
397 max_depth = pars_[pit].getMaxDepthAfter();
403 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
405 BOOST_ASSERT(this == cur.text());
406 pit_type const beg = cur.selBegin().pit();
407 pit_type const end = cur.selEnd().pit() + 1;
408 recordUndoSelection(cur);
409 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
411 for (pit_type pit = beg; pit != end; ++pit) {
412 Paragraph & par = pars_[pit];
413 if (::changeDepthAllowed(type, par, max_depth)) {
414 int const depth = par.params().depth();
415 if (type == INC_DEPTH)
416 par.params().depth(depth + 1);
418 par.params().depth(depth - 1);
420 max_depth = par.getMaxDepthAfter();
422 // this handles the counter labels, and also fixes up
423 // depth values for follow-on (child) paragraphs
424 updateCounters(cur.buffer());
428 // set font over selection
429 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
431 BOOST_ASSERT(this == cur.text());
432 // if there is no selection just set the current_font
433 if (!cur.selection()) {
434 // Determine basis font
436 pit_type pit = cur.pit();
437 if (cur.pos() < pars_[pit].beginOfBody())
438 layoutfont = getLabelFont(pars_[pit]);
440 layoutfont = getLayoutFont(pit);
442 // Update current font
443 real_current_font.update(font,
444 cur.buffer().params().language,
447 // Reduce to implicit settings
448 current_font = real_current_font;
449 current_font.reduce(layoutfont);
450 // And resolve it completely
451 real_current_font.realize(layoutfont);
456 // Ok, we have a selection.
457 recordUndoSelection(cur);
459 DocIterator dit = cur.selectionBegin();
460 DocIterator ditend = cur.selectionEnd();
462 BufferParams const & params = cur.buffer().params();
464 // Don't use forwardChar here as ditend might have
465 // pos() == lastpos() and forwardChar would miss it.
466 // Can't use forwardPos either as this descends into
468 for (; dit != ditend; dit.forwardPosNoDescend()) {
469 if (dit.pos() != dit.lastpos()) {
470 LyXFont f = getFont(dit.paragraph(), dit.pos());
471 f.update(font, params.language, toggleall);
472 setCharFont(dit.pit(), dit.pos(), f);
478 // the cursor set functions have a special mechanism. When they
479 // realize you left an empty paragraph, they will delete it.
481 void LyXText::cursorHome(LCursor & cur)
483 BOOST_ASSERT(this == cur.text());
484 Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
486 setCursor(cur, cur.pit(), row.pos());
490 void LyXText::cursorEnd(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 // if not on the last row of the par, put the cursor before
494 // the final space exept if I have a spanning inset or one string
495 // is so long that we force a break.
496 pos_type end = cur.textRow().endpos();
497 bool boundary = false;
498 if (!cur.paragraph().isLineSeparator(end-1) &&
499 !cur.paragraph().isNewline(end-1))
502 } else if (end != cur.lastpos())
504 setCursor(cur, cur.pit(), end, true, boundary);
508 void LyXText::cursorTop(LCursor & cur)
510 BOOST_ASSERT(this == cur.text());
511 setCursor(cur, 0, 0);
515 void LyXText::cursorBottom(LCursor & cur)
517 BOOST_ASSERT(this == cur.text());
518 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
522 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
524 BOOST_ASSERT(this == cur.text());
525 // If the mask is completely neutral, tell user
526 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
527 // Could only happen with user style
528 cur.message(_("No font change defined. "
529 "Use Character under the Layout menu to define font change."));
533 // Try implicit word selection
534 // If there is a change in the language the implicit word selection
536 CursorSlice resetCursor = cur.top();
537 bool implicitSelection =
538 font.language() == ignore_language
539 && font.number() == LyXFont::IGNORE
540 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
543 setFont(cur, font, toggleall);
545 // Implicit selections are cleared afterwards
546 // and cursor is set to the original position.
547 if (implicitSelection) {
548 cur.clearSelection();
549 cur.top() = resetCursor;
555 string LyXText::getStringToIndex(LCursor const & cur)
557 BOOST_ASSERT(this == cur.text());
560 if (cur.selection()) {
561 idxstring = cur.selectionAsString(false);
563 // Try implicit word selection. If there is a change
564 // in the language the implicit word selection is
566 LCursor tmpcur = cur;
567 selectWord(tmpcur, lyx::PREVIOUS_WORD);
569 if (!tmpcur.selection())
570 cur.message(_("Nothing to index!"));
571 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
572 cur.message(_("Cannot index more than one paragraph!"));
574 idxstring = tmpcur.selectionAsString(false);
581 void LyXText::setParagraph(LCursor & cur,
582 Spacing const & spacing, LyXAlignment align,
583 string const & labelwidthstring, bool noindent)
585 BOOST_ASSERT(cur.text());
586 // make sure that the depth behind the selection are restored, too
587 pit_type undopit = undoSpan(cur.selEnd().pit());
588 recUndo(cur.selBegin().pit(), undopit - 1);
590 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
592 Paragraph & par = pars_[pit];
593 ParagraphParameters & params = par.params();
594 params.spacing(spacing);
596 // does the layout allow the new alignment?
597 LyXLayout_ptr const & layout = par.layout();
599 if (align == LYX_ALIGN_LAYOUT)
600 align = layout->align;
601 if (align & layout->alignpossible) {
602 if (align == layout->align)
603 params.align(LYX_ALIGN_LAYOUT);
607 par.setLabelWidthString(labelwidthstring);
608 params.noindent(noindent);
613 // this really should just insert the inset and not move the cursor.
614 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
616 BOOST_ASSERT(this == cur.text());
618 cur.paragraph().insertInset(cur.pos(), inset);
622 // needed to insert the selection
623 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
625 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
626 current_font, str, autoBreakRows_);
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()));
673 // now some strict checking
674 Paragraph & para = getPar(par);
676 // None of these should happen, but we're scaredy-cats
678 lyxerr << "dont like -1" << endl;
682 if (pos > para.size()) {
683 lyxerr << "dont like 1, pos: " << pos
684 << " size: " << para.size()
685 << " par: " << par << endl;
691 void LyXText::setCursorIntern(LCursor & cur,
692 pit_type par, pos_type pos, bool setfont, bool boundary)
694 cur.boundary(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 // I believe this code is not needed anymore (Jug 20050717)
834 // The following code is necessary because the cursor position past
835 // the last char in a row is logically equivalent to that before
836 // the first char in the next row. That's why insets causing row
837 // divisions -- Newline and display-style insets -- must be treated
838 // specially, so cursor up/down doesn't get stuck in an air gap -- MV
839 // Newline inset, air gap below:
840 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
841 if (bidi.level(end -1) % 2 == 0)
842 tmpx -= singleWidth(par, end - 1);
844 tmpx += singleWidth(par, end - 1);
848 // Air gap above display inset:
849 if (row.pos() < end && c >= end && end < par.size()
850 && par.isInset(end) && par.getInset(end)->display()) {
853 // Air gap below display inset:
854 if (row.pos() < end && c >= end && par.isInset(end - 1)
855 && par.getInset(end - 1)->display()) {
861 pos_type const col = c - row.pos();
863 if (!c || end == par.size())
866 if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
871 return min(col, end - 1 - row.pos());
875 // y is screen coordinate
876 pit_type LyXText::getPitNearY(int y) const
878 BOOST_ASSERT(!paragraphs().empty());
879 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
880 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
882 << BOOST_CURRENT_FUNCTION
883 << ": y: " << y << " cache size: " << cc.size()
886 // look for highest numbered paragraph with y coordinate less than given y
889 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
890 CoordCache::InnerParPosCache::const_iterator et = cc.end();
891 for (; it != et; ++it) {
893 << BOOST_CURRENT_FUNCTION
894 << " examining: pit: " << it->first
895 << " y: " << it->second.y_
898 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
905 << BOOST_CURRENT_FUNCTION
906 << ": found best y: " << yy << " for pit: " << pit
913 Row const & LyXText::getRowNearY(int y, pit_type pit) const
915 Paragraph const & par = pars_[pit];
916 int yy = theCoords.get(this, pit).y_ - par.ascent();
917 BOOST_ASSERT(!par.rows().empty());
918 RowList::const_iterator rit = par.rows().begin();
919 RowList::const_iterator const rlast = boost::prior(par.rows().end());
920 for (; rit != rlast; yy += rit->height(), ++rit)
921 if (yy + rit->height() > y)
927 // x,y are absolute screen coordinates
928 // sets cursor recursively descending into nested editable insets
929 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
931 pit_type pit = getPitNearY(y);
932 BOOST_ASSERT(pit != -1);
933 Row const & row = getRowNearY(y, pit);
936 int xx = x; // is modified by getColumnNearX
937 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
943 // try to descend into nested insets
944 InsetBase * inset = checkInsetHit(x, y);
945 //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
947 // Either we deconst editXY or better we move current_font
948 // and real_current_font to LCursor
953 // This should be just before or just behind the
954 // cursor position set above.
955 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
956 || inset == pars_[pit].getInset(pos));
957 // Make sure the cursor points to the position before
959 if (inset == pars_[pit].getInset(pos - 1))
961 inset = inset->editXY(cur, x, y);
962 if (cur.top().text() == this)
968 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
972 if (cur.pos() == cur.lastpos())
974 InsetBase * inset = cur.nextInset();
975 if (!isHighlyEditableInset(inset))
977 inset->edit(cur, front);
982 bool LyXText::cursorLeft(LCursor & cur)
984 if (!cur.boundary() && cur.pos() > 0 &&
985 cur.textRow().pos() == cur.pos() &&
986 !cur.paragraph().isLineSeparator(cur.pos()-1) &&
987 !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.boundary()) {
1014 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1017 if (cur.pos() != cur.lastpos()) {
1018 bool updateNeeded = false;
1019 if (!checkAndActivateInset(cur, true)) {
1020 if (cur.textRow().endpos() == (cur.pos() + 1) &&
1021 !cur.paragraph().isLineSeparator(cur.pos()) &&
1022 !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);
1056 // This happens when you move out of an inset.
1057 // And to give the DEPM the possibility of doing
1058 // something we must provide it with two different
1060 LCursor dummy = cur;
1064 return deleteEmptyParagraphMechanism(dummy, old);
1067 bool updateNeeded = false;
1070 updateNeeded |= setCursor(cur, cur.pit(),
1071 x2pos(cur.pit(), row - 1, x));
1072 } else if (cur.pit() > 0) {
1074 //cannot use 'par' now
1075 updateNeeded |= setCursor(cur, cur.pit(),
1076 x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1081 return updateNeeded;
1085 bool LyXText::cursorDown(LCursor & cur)
1087 Paragraph const & par = cur.paragraph();
1089 int const x = cur.targetX();
1091 if (cur.pos() && cur.boundary())
1092 row = par.pos2row(cur.pos()-1);
1094 row = par.pos2row(cur.pos());
1096 if (!cur.selection()) {
1097 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1099 editXY(cur, x, y + par.rows()[row].descent() + 1);
1101 // This happens when you move out of an inset.
1102 // And to give the DEPM the possibility of doing
1103 // something we must provide it with two different
1105 LCursor dummy = cur;
1109 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1111 // Make sure that cur gets back whatever happened to dummy(Lgb)
1119 bool updateNeeded = false;
1121 if (row + 1 < int(par.rows().size())) {
1122 updateNeeded |= setCursor(cur, cur.pit(),
1123 x2pos(cur.pit(), row + 1, x));
1124 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1126 updateNeeded |= setCursor(cur, cur.pit(),
1127 x2pos(cur.pit(), 0, x));
1132 return updateNeeded;
1136 bool LyXText::cursorUpParagraph(LCursor & cur)
1138 bool updated = false;
1140 updated = setCursor(cur, cur.pit(), 0);
1141 else if (cur.pit() != 0)
1142 updated = setCursor(cur, cur.pit() - 1, 0);
1147 bool LyXText::cursorDownParagraph(LCursor & cur)
1149 bool updated = false;
1150 if (cur.pit() != cur.lastpit())
1151 updated = setCursor(cur, cur.pit() + 1, 0);
1153 updated = setCursor(cur, cur.pit(), cur.lastpos());
1158 // fix the cursor `cur' after a characters has been deleted at `where'
1159 // position. Called by deleteEmptyParagraphMechanism
1160 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1162 // Do nothing if cursor is not in the paragraph where the
1163 // deletion occured,
1164 if (cur.pit() != where.pit())
1167 // If cursor position is after the deletion place update it
1168 if (cur.pos() > where.pos())
1171 // Check also if we don't want to set the cursor on a spot behind the
1172 // pagragraph because we erased the last character.
1173 if (cur.pos() > cur.lastpos())
1174 cur.pos() = cur.lastpos();
1178 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1180 // Would be wrong to delete anything if we have a selection.
1181 if (cur.selection())
1184 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1185 Paragraph const & oldpar = pars_[old.pit()];
1187 // We allow all kinds of "mumbo-jumbo" when freespacing.
1188 if (oldpar.isFreeSpacing())
1191 /* Ok I'll put some comments here about what is missing.
1192 I have fixed BackSpace (and thus Delete) to not delete
1193 double-spaces automagically. I have also changed Cut,
1194 Copy and Paste to hopefully do some sensible things.
1195 There are still some small problems that can lead to
1196 double spaces stored in the document file or space at
1197 the beginning of paragraphs(). This happens if you have
1198 the cursor between to spaces and then save. Or if you
1199 cut and paste and the selection have a space at the
1200 beginning and then save right after the paste. I am
1201 sure none of these are very hard to fix, but I will
1202 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1203 that I can get some feedback. (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 // If the chars around the old cursor were spaces, delete one of them.
1215 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1217 // Only if the cursor has really moved.
1219 && old.pos() < oldpar.size()
1220 && oldpar.isLineSeparator(old.pos())
1221 && oldpar.isLineSeparator(old.pos() - 1)) {
1222 // We need to set the text to Change::INSERTED to
1223 // get it erased properly
1224 pars_[old.pit()].setChange(old.pos() -1,
1226 pars_[old.pit()].erase(old.pos() - 1);
1227 #ifdef WITH_WARNINGS
1228 #warning This will not work anymore when we have multiple views of the same buffer
1229 // In this case, we will have to correct also the cursors held by
1230 // other bufferviews. It will probably be easier to do that in a more
1231 // automated way in CursorSlice code. (JMarc 26/09/2001)
1233 // correct all cursor parts
1234 fixCursorAfterDelete(cur.top(), old.top());
1235 #ifdef WITH_WARNINGS
1236 #warning DEPM, look here
1238 //fixCursorAfterDelete(cur.anchor(), old.top());
1243 // only do our magic if we changed paragraph
1244 if (old.pit() == cur.pit())
1247 // don't delete anything if this is the ONLY paragraph!
1248 if (pars_.size() == 1)
1251 // Do not delete empty paragraphs with keepempty set.
1252 if (oldpar.allowEmpty())
1255 // record if we have deleted a paragraph
1256 // we can't possibly have deleted a paragraph before this point
1257 bool deleted = false;
1259 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1260 // ok, we will delete something
1263 bool selection_position_was_oldcursor_position =
1264 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1266 // This is a bit of a overkill. We change the old and the cur par
1267 // at max, certainly not everything in between...
1268 recUndo(old.pit(), cur.pit());
1271 pars_.erase(pars_.begin() + old.pit());
1273 // Update cursor par offset if necessary.
1274 // Some 'iterator registration' would be nice that takes care of
1275 // such events. Maybe even signal/slot?
1276 if (cur.pit() > old.pit())
1278 #ifdef WITH_WARNINGS
1279 #warning DEPM, look here
1281 // if (cur.anchor().pit() > old.pit())
1282 // --cur.anchor().pit();
1284 if (selection_position_was_oldcursor_position) {
1285 // correct selection
1291 updateCounters(cur.buffer());
1295 if (pars_[old.pit()].stripLeadingSpaces())
1302 void LyXText::recUndo(pit_type first, pit_type last) const
1304 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1308 void LyXText::recUndo(pit_type par) const
1310 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1314 int defaultRowHeight()
1316 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);