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"
30 #include "coordcache.h"
32 #include "CutAndPaste.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
37 #include "FloatList.h"
38 #include "funcrequest.h"
44 #include "lyxrow_funcs.h"
45 #include "paragraph.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
51 #include "frontends/font_metrics.h"
52 #include "frontends/LyXView.h"
54 #include "insets/insetbibitem.h"
55 #include "insets/insetenv.h"
56 #include "insets/insetfloat.h"
57 #include "insets/insetwrap.h"
59 #include "support/lstrings.h"
60 #include "support/textutils.h"
61 #include "support/convert.h"
67 using lyx::support::bformat;
70 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv)
75 : maxwidth_(bv ? bv->workWidth() : 100),
76 background_color_(LColor::background),
82 void LyXText::init(BufferView * bv)
86 maxwidth_ = bv->workWidth();
91 pit_type const end = paragraphs().size();
92 for (pit_type pit = 0; pit != end; ++pit)
93 pars_[pit].rows().clear();
95 current_font = getFont(pars_[0], 0);
100 bool LyXText::isMainText() const
102 return &bv()->buffer()->text() == this;
106 //takes screen x,y coordinates
107 InsetBase * LyXText::checkInsetHit(int x, int y) const
109 pit_type pit = getPitNearY(y);
110 BOOST_ASSERT(pit != -1);
112 Paragraph const & par = pars_[pit];
114 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
115 lyxerr << " pit: " << pit << endl;
116 InsetList::const_iterator iit = par.insetlist.begin();
117 InsetList::const_iterator iend = par.insetlist.end();
118 for (; iit != iend; ++iit) {
119 InsetBase * inset = iit->inset;
121 lyxerr << "examining inset " << inset << endl;
122 if (theCoords.getInsets().has(inset))
124 << " xo: " << inset->xo() << "..."
125 << inset->xo() + inset->width()
126 << " yo: " << inset->yo() - inset->ascent()
128 << inset->yo() + inset->descent() << endl;
130 lyxerr << " inset has no cached position" << endl;
132 if (inset->covers(x, y)) {
133 lyxerr << "Hit inset: " << inset << endl;
137 lyxerr << "No inset hit. " << endl;
143 // Gets the fully instantiated font at a given position in a paragraph
144 // Basically the same routine as Paragraph::getFont() in paragraph.C.
145 // The difference is that this one is used for displaying, and thus we
146 // are allowed to make cosmetic improvements. For instance make footnotes
148 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
150 BOOST_ASSERT(pos >= 0);
152 LyXLayout_ptr const & layout = par.layout();
156 BufferParams const & params = bv()->buffer()->params();
157 pos_type const body_pos = par.beginOfBody();
159 // We specialize the 95% common case:
160 if (!par.getDepth()) {
161 LyXFont f = par.getFontSettings(params, pos);
164 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
165 return f.realize(layout->reslabelfont);
167 return f.realize(layout->resfont);
170 // The uncommon case need not be optimized as much
173 layoutfont = layout->labelfont;
175 layoutfont = layout->font;
177 LyXFont font = par.getFontSettings(params, pos);
178 font.realize(layoutfont);
183 // Realize with the fonts of lesser depth.
184 font.realize(defaultfont_);
190 LyXFont LyXText::getLayoutFont(pit_type const pit) const
192 LyXLayout_ptr const & layout = pars_[pit].layout();
194 if (!pars_[pit].getDepth())
195 return layout->resfont;
197 LyXFont font = layout->font;
198 // Realize with the fonts of lesser depth.
199 //font.realize(outerFont(pit, paragraphs()));
200 font.realize(defaultfont_);
206 LyXFont LyXText::getLabelFont(Paragraph const & par) const
208 LyXLayout_ptr const & layout = par.layout();
211 return layout->reslabelfont;
213 LyXFont font = layout->labelfont;
214 // Realize with the fonts of lesser depth.
215 font.realize(defaultfont_);
221 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
224 LyXLayout_ptr const & layout = pars_[pit].layout();
226 // Get concrete layout font to reduce against
229 if (pos < pars_[pit].beginOfBody())
230 layoutfont = layout->labelfont;
232 layoutfont = layout->font;
234 // Realize against environment font information
235 if (pars_[pit].getDepth()) {
237 while (!layoutfont.resolved() &&
238 tp != pit_type(paragraphs().size()) &&
239 pars_[tp].getDepth()) {
240 tp = outerHook(tp, paragraphs());
241 if (tp != pit_type(paragraphs().size()))
242 layoutfont.realize(pars_[tp].layout()->font);
246 layoutfont.realize(defaultfont_);
248 // Now, reduce font against full layout font
249 font.reduce(layoutfont);
251 pars_[pit].setFont(pos, font);
256 // Asger is not sure we want to do this...
257 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
260 LyXLayout_ptr const & layout = par.layout();
261 pos_type const psize = par.size();
264 for (pos_type pos = 0; pos < psize; ++pos) {
265 if (pos < par.beginOfBody())
266 layoutfont = layout->labelfont;
268 layoutfont = layout->font;
270 LyXFont tmpfont = par.getFontSettings(params, pos);
271 tmpfont.reduce(layoutfont);
272 par.setFont(pos, tmpfont);
277 // return past-the-last paragraph influenced by a layout change on pit
278 pit_type LyXText::undoSpan(pit_type pit)
280 pit_type end = paragraphs().size();
281 pit_type nextpit = pit + 1;
284 //because of parindents
285 if (!pars_[pit].getDepth())
286 return boost::next(nextpit);
287 //because of depth constrains
288 for (; nextpit != end; ++pit, ++nextpit) {
289 if (!pars_[pit].getDepth())
296 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
298 BOOST_ASSERT(start != end);
299 pit_type undopit = undoSpan(end - 1);
300 recUndo(start, undopit - 1);
302 BufferParams const & bufparams = bv()->buffer()->params();
303 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
305 for (pit_type pit = start; pit != end; ++pit) {
306 pars_[pit].applyLayout(lyxlayout);
307 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
308 if (lyxlayout->margintype == MARGIN_MANUAL)
309 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
316 // set layout over selection and make a total rebreak of those paragraphs
317 void LyXText::setLayout(LCursor & cur, string const & layout)
319 BOOST_ASSERT(this == cur.text());
320 // special handling of new environment insets
321 BufferView & bv = cur.bv();
322 BufferParams const & params = bv.buffer()->params();
323 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
324 if (lyxlayout->is_environment) {
325 // move everything in a new environment inset
326 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
327 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
328 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
329 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
330 InsetBase * inset = new InsetEnvironment(params, layout);
331 insertInset(cur, inset);
332 //inset->edit(cur, true);
333 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
337 pit_type start = cur.selBegin().pit();
338 pit_type end = cur.selEnd().pit() + 1;
339 setLayout(start, end, layout);
347 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
348 Paragraph const & par, int max_depth)
350 if (par.layout()->labeltype == LABEL_BIBLIO)
352 int const depth = par.params().depth();
353 if (type == LyXText::INC_DEPTH && depth < max_depth)
355 if (type == LyXText::DEC_DEPTH && depth > 0)
364 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
366 BOOST_ASSERT(this == cur.text());
367 pit_type const beg = cur.selBegin().pit();
368 pit_type const end = cur.selEnd().pit() + 1;
369 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
371 for (pit_type pit = beg; pit != end; ++pit) {
372 if (::changeDepthAllowed(type, pars_[pit], max_depth))
374 max_depth = pars_[pit].getMaxDepthAfter();
380 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
382 BOOST_ASSERT(this == cur.text());
383 pit_type const beg = cur.selBegin().pit();
384 pit_type const end = cur.selEnd().pit() + 1;
385 recordUndoSelection(cur);
386 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
388 for (pit_type pit = beg; pit != end; ++pit) {
389 Paragraph & par = pars_[pit];
390 if (::changeDepthAllowed(type, par, max_depth)) {
391 int const depth = par.params().depth();
392 if (type == INC_DEPTH)
393 par.params().depth(depth + 1);
395 par.params().depth(depth - 1);
397 max_depth = par.getMaxDepthAfter();
399 // this handles the counter labels, and also fixes up
400 // depth values for follow-on (child) paragraphs
405 // set font over selection
406 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
408 BOOST_ASSERT(this == cur.text());
409 // if there is no selection just set the current_font
410 if (!cur.selection()) {
411 // Determine basis font
413 pit_type pit = cur.pit();
414 if (cur.pos() < pars_[pit].beginOfBody())
415 layoutfont = getLabelFont(pars_[pit]);
417 layoutfont = getLayoutFont(pit);
419 // Update current font
420 real_current_font.update(font,
421 cur.buffer().params().language,
424 // Reduce to implicit settings
425 current_font = real_current_font;
426 current_font.reduce(layoutfont);
427 // And resolve it completely
428 real_current_font.realize(layoutfont);
433 // Ok, we have a selection.
434 recordUndoSelection(cur);
436 DocIterator dit = cur.selectionBegin();
437 DocIterator ditend = cur.selectionEnd();
439 BufferParams const & params = cur.buffer().params();
441 // Don't use forwardChar here as ditend might have
442 // pos() == lastpos() and forwardChar would miss it.
443 for (; dit != ditend; dit.forwardPos()) {
444 if (dit.pos() != dit.lastpos()) {
445 LyXFont f = getFont(dit.paragraph(), dit.pos());
446 f.update(font, params.language, toggleall);
447 setCharFont(dit.pit(), dit.pos(), f);
453 // the cursor set functions have a special mechanism. When they
454 // realize you left an empty paragraph, they will delete it.
456 void LyXText::cursorHome(LCursor & cur)
458 BOOST_ASSERT(this == cur.text());
459 setCursor(cur, cur.pit(), cur.textRow().pos());
463 void LyXText::cursorEnd(LCursor & cur)
465 BOOST_ASSERT(this == cur.text());
466 // if not on the last row of the par, put the cursor before
468 // FIXME: does this final space exist?
469 pos_type const end = cur.textRow().endpos();
470 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
474 void LyXText::cursorTop(LCursor & cur)
476 BOOST_ASSERT(this == cur.text());
477 setCursor(cur, 0, 0);
481 void LyXText::cursorBottom(LCursor & cur)
483 BOOST_ASSERT(this == cur.text());
484 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
488 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
490 BOOST_ASSERT(this == cur.text());
491 // If the mask is completely neutral, tell user
492 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
493 // Could only happen with user style
494 cur.message(_("No font change defined. "
495 "Use Character under the Layout menu to define font change."));
499 // Try implicit word selection
500 // If there is a change in the language the implicit word selection
502 CursorSlice resetCursor = cur.top();
503 bool implicitSelection =
504 font.language() == ignore_language
505 && font.number() == LyXFont::IGNORE
506 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
509 setFont(cur, font, toggleall);
511 // Implicit selections are cleared afterwards
512 // and cursor is set to the original position.
513 if (implicitSelection) {
514 cur.clearSelection();
515 cur.top() = resetCursor;
521 string LyXText::getStringToIndex(LCursor & cur)
523 BOOST_ASSERT(this == cur.text());
524 // Try implicit word selection
525 // If there is a change in the language the implicit word selection
527 CursorSlice const reset_cursor = cur.top();
528 bool const implicitSelection =
529 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
532 if (!cur.selection())
533 cur.message(_("Nothing to index!"));
534 else if (cur.selBegin().pit() != cur.selEnd().pit())
535 cur.message(_("Cannot index more than one paragraph!"));
537 idxstring = cur.selectionAsString(false);
539 // Reset cursors to their original position.
540 cur.top() = reset_cursor;
543 // Clear the implicit selection.
544 if (implicitSelection)
545 cur.clearSelection();
551 void LyXText::setParagraph(LCursor & cur,
552 Spacing const & spacing, LyXAlignment align,
553 string const & labelwidthstring, bool noindent)
555 BOOST_ASSERT(cur.text());
556 // make sure that the depth behind the selection are restored, too
557 pit_type undopit = undoSpan(cur.selEnd().pit());
558 recUndo(cur.selBegin().pit(), undopit - 1);
560 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
562 Paragraph & par = pars_[pit];
563 ParagraphParameters & params = par.params();
564 params.spacing(spacing);
566 // does the layout allow the new alignment?
567 LyXLayout_ptr const & layout = par.layout();
569 if (align == LYX_ALIGN_LAYOUT)
570 align = layout->align;
571 if (align & layout->alignpossible) {
572 if (align == layout->align)
573 params.align(LYX_ALIGN_LAYOUT);
577 par.setLabelWidthString(labelwidthstring);
578 params.noindent(noindent);
583 string expandLabel(LyXTextClass const & textclass,
584 LyXLayout_ptr const & layout, bool appendix)
586 string fmt = appendix ?
587 layout->labelstring_appendix() : layout->labelstring();
589 // handle 'inherited level parts' in 'fmt',
590 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
591 size_t const i = fmt.find('@', 0);
592 if (i != string::npos) {
593 size_t const j = fmt.find('@', i + 1);
594 if (j != string::npos) {
595 string parent(fmt, i + 1, j - i - 1);
596 string label = expandLabel(textclass, textclass[parent], appendix);
597 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
601 return textclass.counters().counterLabel(fmt);
607 void incrementItemDepth(ParagraphList & pars, pit_type pit, pit_type first_pit)
609 int const cur_labeltype = pars[pit].layout()->labeltype;
611 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
614 int const cur_depth = pars[pit].getDepth();
616 pit_type prev_pit = pit - 1;
618 int const prev_depth = pars[prev_pit].getDepth();
619 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
620 if (prev_depth == 0 && cur_depth > 0) {
621 if (prev_labeltype == cur_labeltype) {
622 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
625 } else if (prev_depth < cur_depth) {
626 if (prev_labeltype == cur_labeltype) {
627 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
630 } else if (prev_depth == cur_depth) {
631 if (prev_labeltype == cur_labeltype) {
632 pars[pit].itemdepth = pars[prev_pit].itemdepth;
636 if (prev_pit == first_pit)
644 void resetEnumCounterIfNeeded(ParagraphList & pars, pit_type pit,
645 pit_type firstpit, Counters & counters)
650 int const cur_depth = pars[pit].getDepth();
651 pit_type prev_pit = pit - 1;
653 int const prev_depth = pars[prev_pit].getDepth();
654 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
655 if (prev_depth <= cur_depth) {
656 if (prev_labeltype != LABEL_ENUMERATE) {
657 switch (pars[pit].itemdepth) {
659 counters.reset("enumi");
661 counters.reset("enumii");
663 counters.reset("enumiii");
665 counters.reset("enumiv");
671 if (prev_pit == firstpit)
681 // set the counter of a paragraph. This includes the labels
682 void LyXText::setCounter(Buffer const & buf, pit_type pit)
684 Paragraph & par = pars_[pit];
685 BufferParams const & bufparams = buf.params();
686 LyXTextClass const & textclass = bufparams.getLyXTextClass();
687 LyXLayout_ptr const & layout = par.layout();
688 Counters & counters = textclass.counters();
694 par.params().appendix(par.params().startOfAppendix());
696 par.params().appendix(pars_[pit - 1].params().appendix());
697 if (!par.params().appendix() &&
698 par.params().startOfAppendix()) {
699 par.params().appendix(true);
700 textclass.counters().reset();
703 // Maybe we have to increment the item depth.
704 incrementItemDepth(pars_, pit, 0);
707 // erase what was there before
708 par.params().labelString(string());
710 if (layout->margintype == MARGIN_MANUAL) {
711 if (par.params().labelWidthString().empty())
712 par.setLabelWidthString(layout->labelstring());
714 par.setLabelWidthString(string());
717 // is it a layout that has an automatic label?
718 if (layout->labeltype == LABEL_COUNTER) {
719 BufferParams const & bufparams = buf.params();
720 LyXTextClass const & textclass = bufparams.getLyXTextClass();
721 counters.step(layout->counter);
722 string label = expandLabel(textclass, layout, par.params().appendix());
723 par.params().labelString(label);
724 } else if (layout->labeltype == LABEL_ITEMIZE) {
725 // At some point of time we should do something more
726 // clever here, like:
727 // par.params().labelString(
728 // bufparams.user_defined_bullet(par.itemdepth).getText());
729 // for now, use a simple hardcoded label
731 switch (par.itemdepth) {
746 par.params().labelString(itemlabel);
747 } else if (layout->labeltype == LABEL_ENUMERATE) {
748 // Maybe we have to reset the enumeration counter.
749 resetEnumCounterIfNeeded(pars_, pit, 0, counters);
752 // Yes I know this is a really, really! bad solution
754 string enumcounter = "enum";
756 switch (par.itemdepth) {
768 // not a valid enumdepth...
772 counters.step(enumcounter);
774 par.params().labelString(counters.enumLabel(enumcounter));
775 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
776 counters.step("bibitem");
777 int number = counters.value("bibitem");
779 par.bibitem()->setCounter(number);
780 par.params().labelString(layout->labelstring());
782 // In biblio should't be following counters but...
784 string s = buf.B_(layout->labelstring());
787 if (layout->labeltype == LABEL_SENSITIVE) {
788 pit_type end = paragraphs().size();
789 pit_type tmppit = pit;
792 while (tmppit != end) {
793 in = pars_[tmppit].inInset();
794 // FIXME: in should be always valid.
796 (in->lyxCode() == InsetBase::FLOAT_CODE ||
797 in->lyxCode() == InsetBase::WRAP_CODE)) {
802 #warning replace this code by something that works
803 // This code does not work because we have currently no way to move up
804 // in the hierarchy of insets (JMarc 16/08/2004)
807 /* I think this code is supposed to be useful when one has a caption
808 * in a minipage in a figure inset. We need to go up to be able to see
809 * that the caption should use "Figure" as label
812 Paragraph const * owner = &ownerPar(buf, in);
814 for ( ; tmppit != end; ++tmppit)
815 if (&pars_[tmppit] == owner)
826 if (in->lyxCode() == InsetBase::FLOAT_CODE)
827 type = static_cast<InsetFloat*>(in)->params().type;
828 else if (in->lyxCode() == InsetBase::WRAP_CODE)
829 type = static_cast<InsetWrap*>(in)->params().type;
833 Floating const & fl = textclass.floats().getType(type);
835 counters.step(fl.type());
837 // Doesn't work... yet.
838 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
840 // par->SetLayout(0);
841 // s = layout->labelstring;
842 s = _("Senseless: ");
845 par.params().labelString(s);
851 // Updates all counters.
852 void LyXText::updateCounters()
855 bv()->buffer()->params().getLyXTextClass().counters().reset();
857 bool update_pos = false;
859 pit_type end = paragraphs().size();
860 for (pit_type pit = 0; pit != end; ++pit) {
861 string const oldLabel = pars_[pit].params().labelString();
864 maxdepth = pars_[pit - 1].getMaxDepthAfter();
866 if (pars_[pit].params().depth() > maxdepth)
867 pars_[pit].params().depth(maxdepth);
869 // setCounter can potentially change the labelString.
870 setCounter(*bv()->buffer(), pit);
871 string const & newLabel = pars_[pit].params().labelString();
872 if (oldLabel != newLabel) {
873 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
874 // << newLabel << endl;
881 // this really should just insert the inset and not move the cursor.
882 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
884 BOOST_ASSERT(this == cur.text());
886 cur.paragraph().insertInset(cur.pos(), inset);
890 // needed to insert the selection
891 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
893 pit_type pit = cur.pit();
894 pos_type pos = cur.pos();
897 // only to be sure, should not be neccessary
898 cur.clearSelection();
899 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
903 setCursor(cur, cur.pit(), pos);
908 // turn double CR to single CR, others are converted into one
909 // blank. Then insertStringAsLines is called
910 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
912 string linestr = str;
913 bool newline_inserted = false;
915 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
916 if (linestr[i] == '\n') {
917 if (newline_inserted) {
918 // we know that \r will be ignored by
919 // insertStringAsLines. Of course, it is a dirty
920 // trick, but it works...
921 linestr[i - 1] = '\r';
925 newline_inserted = true;
927 } else if (IsPrintable(linestr[i])) {
928 newline_inserted = false;
931 insertStringAsLines(cur, linestr);
935 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
936 bool setfont, bool boundary)
939 setCursorIntern(cur, par, pos, setfont, boundary);
940 return deleteEmptyParagraphMechanism(cur, old);
944 void LyXText::setCursor(CursorSlice & cur, pit_type par,
945 pos_type pos, bool boundary)
947 BOOST_ASSERT(par != int(paragraphs().size()));
950 cur.boundary() = boundary;
952 // now some strict checking
953 Paragraph & para = getPar(par);
955 // None of these should happen, but we're scaredy-cats
957 lyxerr << "dont like -1" << endl;
961 if (pos > para.size()) {
962 lyxerr << "dont like 1, pos: " << pos
963 << " size: " << para.size()
964 << " par: " << par << endl;
970 void LyXText::setCursorIntern(LCursor & cur,
971 pit_type par, pos_type pos, bool setfont, bool boundary)
973 setCursor(cur.top(), par, pos, boundary);
980 void LyXText::setCurrentFont(LCursor & cur)
982 BOOST_ASSERT(this == cur.text());
983 pos_type pos = cur.pos();
984 Paragraph & par = cur.paragraph();
986 if (cur.boundary() && pos > 0)
990 if (pos == cur.lastpos())
992 else // potentional bug... BUG (Lgb)
993 if (par.isSeparator(pos)) {
994 if (pos > cur.textRow().pos() &&
995 bidi.level(pos) % 2 ==
996 bidi.level(pos - 1) % 2)
998 else if (pos + 1 < cur.lastpos())
1003 BufferParams const & bufparams = cur.buffer().params();
1004 current_font = par.getFontSettings(bufparams, pos);
1005 real_current_font = getFont(par, pos);
1007 if (cur.pos() == cur.lastpos()
1008 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1009 && !cur.boundary()) {
1010 Language const * lang = par.getParLanguage(bufparams);
1011 current_font.setLanguage(lang);
1012 current_font.setNumber(LyXFont::OFF);
1013 real_current_font.setLanguage(lang);
1014 real_current_font.setNumber(LyXFont::OFF);
1019 // x is an absolute screen coord
1020 // returns the column near the specified x-coordinate of the row
1021 // x is set to the real beginning of this column
1022 pos_type LyXText::getColumnNearX(pit_type const pit,
1023 Row const & row, int & x, bool & boundary) const
1025 int const xo = theCoords.get(this, pit).x_;
1027 RowMetrics const r = computeRowMetrics(pit, row);
1028 Paragraph const & par = pars_[pit];
1030 pos_type vc = row.pos();
1031 pos_type end = row.endpos();
1033 LyXLayout_ptr const & layout = par.layout();
1035 bool left_side = false;
1037 pos_type body_pos = par.beginOfBody();
1040 double last_tmpx = tmpx;
1043 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1046 // check for empty row
1052 while (vc < end && tmpx <= x) {
1053 c = bidi.vis2log(vc);
1055 if (body_pos > 0 && c == body_pos - 1) {
1056 tmpx += r.label_hfill +
1057 font_metrics::width(layout->labelsep, getLabelFont(par));
1058 if (par.isLineSeparator(body_pos - 1))
1059 tmpx -= singleWidth(par, body_pos - 1);
1062 if (hfillExpansion(par, row, c)) {
1063 tmpx += singleWidth(par, c);
1067 tmpx += r.label_hfill;
1068 } else if (par.isSeparator(c)) {
1069 tmpx += singleWidth(par, c);
1071 tmpx += r.separator;
1073 tmpx += singleWidth(par, c);
1078 if ((tmpx + last_tmpx) / 2 > x) {
1083 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1086 // This (rtl_support test) is not needed, but gives
1087 // some speedup if rtl_support == false
1088 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1090 // If lastrow is false, we don't need to compute
1091 // the value of rtl.
1092 bool const rtl = lastrow ? isRTL(par) : false;
1094 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1095 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1097 else if (vc == row.pos()) {
1098 c = bidi.vis2log(vc);
1099 if (bidi.level(c) % 2 == 1)
1102 c = bidi.vis2log(vc - 1);
1103 bool const rtl = (bidi.level(c) % 2 == 1);
1104 if (left_side == rtl) {
1106 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1110 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1111 if (bidi.level(end -1) % 2 == 0)
1112 tmpx -= singleWidth(par, end - 1);
1114 tmpx += singleWidth(par, end - 1);
1119 return c - row.pos();
1123 // y is screen coordinate
1124 pit_type LyXText::getPitNearY(int y) const
1126 BOOST_ASSERT(!paragraphs().empty());
1127 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
1128 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
1129 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
1130 << cc.size() << endl;
1132 // look for highest numbered paragraph with y coordinate less than given y
1135 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
1136 CoordCache::InnerParPosCache::const_iterator et = cc.end();
1137 for (; it != et; ++it) {
1138 lyxerr << " examining: pit: " << it->first << " y: "
1139 << it->second.y_ << endl;
1140 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
1146 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
1151 Row const & LyXText::getRowNearY(int y, pit_type pit) const
1153 Paragraph const & par = pars_[pit];
1154 int yy = theCoords.get(this, pit).y_ - par.ascent();
1155 BOOST_ASSERT(!par.rows().empty());
1156 RowList::const_iterator rit = par.rows().begin();
1157 RowList::const_iterator const rlast = boost::prior(par.rows().end());
1158 for (; rit != rlast; yy += rit->height(), ++rit)
1159 if (yy + rit->height() > y)
1165 // x,y are absolute screen coordinates
1166 // sets cursor recursively descending into nested editable insets
1167 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1169 pit_type pit = getPitNearY(y);
1170 BOOST_ASSERT(pit != -1);
1171 Row const & row = getRowNearY(y, pit);
1174 int xx = x; // is modified by getColumnNearX
1175 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1178 cur.boundary() = bound;
1181 // try to descend into nested insets
1182 InsetBase * inset = checkInsetHit(x, y);
1183 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1185 // Either we deconst editXY or better we move current_font
1186 // and real_current_font to LCursor
1187 const_cast<LyXText *>(this)->setCurrentFont(cur);
1191 // This should be just before or just behind the
1192 // cursor position set above.
1193 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1194 || inset == pars_[pit].getInset(pos));
1195 // Make sure the cursor points to the position before
1197 if (inset == pars_[pit].getInset(pos - 1))
1199 inset = inset->editXY(cur, x, y);
1200 if (cur.top().text() == this)
1201 const_cast<LyXText *>(this)->setCurrentFont(cur);
1206 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1208 if (cur.selection())
1210 if (cur.pos() == cur.lastpos())
1212 InsetBase * inset = cur.nextInset();
1213 if (!isHighlyEditableInset(inset))
1215 inset->edit(cur, front);
1220 bool LyXText::cursorLeft(LCursor & cur)
1222 if (cur.pos() != 0) {
1223 bool boundary = cur.boundary();
1224 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1225 if (!checkAndActivateInset(cur, false)) {
1226 if (false && !boundary &&
1227 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1229 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1231 return updateNeeded;
1234 if (cur.pit() != 0) {
1235 // Steps into the paragraph above
1236 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1242 bool LyXText::cursorRight(LCursor & cur)
1244 if (false && cur.boundary()) {
1245 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1248 if (cur.pos() != cur.lastpos()) {
1249 bool updateNeeded = false;
1250 if (!checkAndActivateInset(cur, true)) {
1251 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1252 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1254 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1256 return updateNeeded;
1259 if (cur.pit() != cur.lastpit())
1260 return setCursor(cur, cur.pit() + 1, 0);
1265 bool LyXText::cursorUp(LCursor & cur)
1267 Paragraph const & par = cur.paragraph();
1268 int const row = par.pos2row(cur.pos());
1269 int const x = cur.targetX();
1271 if (!cur.selection()) {
1272 int const y = bv_funcs::getPos(cur).y_;
1274 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1276 // This happens when you move out of an inset.
1277 // And to give the DEPM the possibility of doing
1278 // something we must provide it with two different
1280 LCursor dummy = cur;
1284 return deleteEmptyParagraphMechanism(dummy, old);
1287 bool updateNeeded = false;
1290 updateNeeded |= setCursor(cur, cur.pit(),
1291 x2pos(cur.pit(), row - 1, x));
1292 } else if (cur.pit() > 0) {
1294 updateNeeded |= setCursor(cur, cur.pit(),
1295 x2pos(cur.pit(), par.rows().size() - 1, x));
1300 return updateNeeded;
1304 bool LyXText::cursorDown(LCursor & cur)
1306 Paragraph const & par = cur.paragraph();
1307 int const row = par.pos2row(cur.pos());
1308 int const x = cur.targetX();
1310 if (!cur.selection()) {
1311 int const y = bv_funcs::getPos(cur).y_;
1313 editXY(cur, x, y + par.rows()[row].descent() + 1);
1315 // This happens when you move out of an inset.
1316 // And to give the DEPM the possibility of doing
1317 // something we must provide it with two different
1319 LCursor dummy = cur;
1323 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1325 // Make sure that cur gets back whatever happened to dummy(Lgb)
1333 bool updateNeeded = false;
1335 if (row + 1 < int(par.rows().size())) {
1336 updateNeeded |= setCursor(cur, cur.pit(),
1337 x2pos(cur.pit(), row + 1, x));
1338 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1340 updateNeeded |= setCursor(cur, cur.pit(),
1341 x2pos(cur.pit(), 0, x));
1346 return updateNeeded;
1350 bool LyXText::cursorUpParagraph(LCursor & cur)
1352 bool updated = false;
1354 updated = setCursor(cur, cur.pit(), 0);
1355 else if (cur.pit() != 0)
1356 updated = setCursor(cur, cur.pit() - 1, 0);
1361 bool LyXText::cursorDownParagraph(LCursor & cur)
1363 bool updated = false;
1364 if (cur.pit() != cur.lastpit())
1365 updated = setCursor(cur, cur.pit() + 1, 0);
1367 updated = setCursor(cur, cur.pit(), cur.lastpos());
1372 // fix the cursor `cur' after a characters has been deleted at `where'
1373 // position. Called by deleteEmptyParagraphMechanism
1374 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1376 // Do nothing if cursor is not in the paragraph where the
1377 // deletion occured,
1378 if (cur.pit() != where.pit())
1381 // If cursor position is after the deletion place update it
1382 if (cur.pos() > where.pos())
1385 // Check also if we don't want to set the cursor on a spot behind the
1386 // pagragraph because we erased the last character.
1387 if (cur.pos() > cur.lastpos())
1388 cur.pos() = cur.lastpos();
1392 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1394 // Would be wrong to delete anything if we have a selection.
1395 if (cur.selection())
1398 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1399 Paragraph const & oldpar = pars_[old.pit()];
1401 // We allow all kinds of "mumbo-jumbo" when freespacing.
1402 if (oldpar.isFreeSpacing())
1405 /* Ok I'll put some comments here about what is missing.
1406 I have fixed BackSpace (and thus Delete) to not delete
1407 double-spaces automagically. I have also changed Cut,
1408 Copy and Paste to hopefully do some sensible things.
1409 There are still some small problems that can lead to
1410 double spaces stored in the document file or space at
1411 the beginning of paragraphs(). This happens if you have
1412 the cursor between to spaces and then save. Or if you
1413 cut and paste and the selection have a space at the
1414 beginning and then save right after the paste. I am
1415 sure none of these are very hard to fix, but I will
1416 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1417 that I can get some feedback. (Lgb)
1420 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1421 // delete the LineSeparator.
1424 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1425 // delete the LineSeparator.
1428 // If the chars around the old cursor were spaces, delete one of them.
1429 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1431 // Only if the cursor has really moved.
1433 && old.pos() < oldpar.size()
1434 && oldpar.isLineSeparator(old.pos())
1435 && oldpar.isLineSeparator(old.pos() - 1)) {
1436 pars_[old.pit()].erase(old.pos() - 1);
1437 #ifdef WITH_WARNINGS
1438 #warning This will not work anymore when we have multiple views of the same buffer
1439 // In this case, we will have to correct also the cursors held by
1440 // other bufferviews. It will probably be easier to do that in a more
1441 // automated way in CursorSlice code. (JMarc 26/09/2001)
1443 // correct all cursor parts
1444 fixCursorAfterDelete(cur.top(), old.top());
1445 #ifdef WITH_WARNINGS
1446 #warning DEPM, look here
1448 //fixCursorAfterDelete(cur.anchor(), old.top());
1453 // only do our magic if we changed paragraph
1454 if (old.pit() == cur.pit())
1457 // don't delete anything if this is the ONLY paragraph!
1458 if (pars_.size() == 1)
1461 // Do not delete empty paragraphs with keepempty set.
1462 if (oldpar.allowEmpty())
1465 // record if we have deleted a paragraph
1466 // we can't possibly have deleted a paragraph before this point
1467 bool deleted = false;
1469 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1470 // ok, we will delete something
1473 bool selection_position_was_oldcursor_position =
1474 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1476 // This is a bit of a overkill. We change the old and the cur par
1477 // at max, certainly not everything in between...
1478 recUndo(old.pit(), cur.pit());
1481 pars_.erase(pars_.begin() + old.pit());
1483 // Update cursor par offset if necessary.
1484 // Some 'iterator registration' would be nice that takes care of
1485 // such events. Maybe even signal/slot?
1486 if (cur.pit() > old.pit())
1488 #ifdef WITH_WARNINGS
1489 #warning DEPM, look here
1491 // if (cur.anchor().pit() > old.pit())
1492 // --cur.anchor().pit();
1494 if (selection_position_was_oldcursor_position) {
1495 // correct selection
1503 if (pars_[old.pit()].stripLeadingSpaces())
1510 ParagraphList & LyXText::paragraphs() const
1512 return const_cast<ParagraphList &>(pars_);
1516 void LyXText::recUndo(pit_type first, pit_type last) const
1518 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1522 void LyXText::recUndo(pit_type par) const
1524 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1528 int defaultRowHeight()
1530 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);