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 // Can't use forwardPos either as this descends into
445 for (; dit != ditend; dit.forwardPosNoDescend()) {
446 if (dit.pos() != dit.lastpos()) {
447 LyXFont f = getFont(dit.paragraph(), dit.pos());
448 f.update(font, params.language, toggleall);
449 setCharFont(dit.pit(), dit.pos(), f);
455 // the cursor set functions have a special mechanism. When they
456 // realize you left an empty paragraph, they will delete it.
458 void LyXText::cursorHome(LCursor & cur)
460 BOOST_ASSERT(this == cur.text());
461 setCursor(cur, cur.pit(), cur.textRow().pos());
465 void LyXText::cursorEnd(LCursor & cur)
467 BOOST_ASSERT(this == cur.text());
468 // if not on the last row of the par, put the cursor before
470 // FIXME: does this final space exist?
471 pos_type const end = cur.textRow().endpos();
472 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
476 void LyXText::cursorTop(LCursor & cur)
478 BOOST_ASSERT(this == cur.text());
479 setCursor(cur, 0, 0);
483 void LyXText::cursorBottom(LCursor & cur)
485 BOOST_ASSERT(this == cur.text());
486 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
490 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
492 BOOST_ASSERT(this == cur.text());
493 // If the mask is completely neutral, tell user
494 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
495 // Could only happen with user style
496 cur.message(_("No font change defined. "
497 "Use Character under the Layout menu to define font change."));
501 // Try implicit word selection
502 // If there is a change in the language the implicit word selection
504 CursorSlice resetCursor = cur.top();
505 bool implicitSelection =
506 font.language() == ignore_language
507 && font.number() == LyXFont::IGNORE
508 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
511 setFont(cur, font, toggleall);
513 // Implicit selections are cleared afterwards
514 // and cursor is set to the original position.
515 if (implicitSelection) {
516 cur.clearSelection();
517 cur.top() = resetCursor;
523 string LyXText::getStringToIndex(LCursor & cur)
525 BOOST_ASSERT(this == cur.text());
526 // Try implicit word selection
527 // If there is a change in the language the implicit word selection
529 CursorSlice const reset_cursor = cur.top();
530 bool const implicitSelection =
531 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
534 if (!cur.selection())
535 cur.message(_("Nothing to index!"));
536 else if (cur.selBegin().pit() != cur.selEnd().pit())
537 cur.message(_("Cannot index more than one paragraph!"));
539 idxstring = cur.selectionAsString(false);
541 // Reset cursors to their original position.
542 cur.top() = reset_cursor;
545 // Clear the implicit selection.
546 if (implicitSelection)
547 cur.clearSelection();
553 void LyXText::setParagraph(LCursor & cur,
554 Spacing const & spacing, LyXAlignment align,
555 string const & labelwidthstring, bool noindent)
557 BOOST_ASSERT(cur.text());
558 // make sure that the depth behind the selection are restored, too
559 pit_type undopit = undoSpan(cur.selEnd().pit());
560 recUndo(cur.selBegin().pit(), undopit - 1);
562 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
564 Paragraph & par = pars_[pit];
565 ParagraphParameters & params = par.params();
566 params.spacing(spacing);
568 // does the layout allow the new alignment?
569 LyXLayout_ptr const & layout = par.layout();
571 if (align == LYX_ALIGN_LAYOUT)
572 align = layout->align;
573 if (align & layout->alignpossible) {
574 if (align == layout->align)
575 params.align(LYX_ALIGN_LAYOUT);
579 par.setLabelWidthString(labelwidthstring);
580 params.noindent(noindent);
585 string expandLabel(LyXTextClass const & textclass,
586 LyXLayout_ptr const & layout, bool appendix)
588 string fmt = appendix ?
589 layout->labelstring_appendix() : layout->labelstring();
591 // handle 'inherited level parts' in 'fmt',
592 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
593 size_t const i = fmt.find('@', 0);
594 if (i != string::npos) {
595 size_t const j = fmt.find('@', i + 1);
596 if (j != string::npos) {
597 string parent(fmt, i + 1, j - i - 1);
598 string label = expandLabel(textclass, textclass[parent], appendix);
599 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
603 return textclass.counters().counterLabel(fmt);
609 void incrementItemDepth(ParagraphList & pars, pit_type pit, pit_type first_pit)
611 int const cur_labeltype = pars[pit].layout()->labeltype;
613 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
616 int const cur_depth = pars[pit].getDepth();
618 pit_type prev_pit = pit - 1;
620 int const prev_depth = pars[prev_pit].getDepth();
621 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
622 if (prev_depth == 0 && cur_depth > 0) {
623 if (prev_labeltype == cur_labeltype) {
624 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
627 } else if (prev_depth < cur_depth) {
628 if (prev_labeltype == cur_labeltype) {
629 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
632 } else if (prev_depth == cur_depth) {
633 if (prev_labeltype == cur_labeltype) {
634 pars[pit].itemdepth = pars[prev_pit].itemdepth;
638 if (prev_pit == first_pit)
646 void resetEnumCounterIfNeeded(ParagraphList & pars, pit_type pit,
647 pit_type firstpit, Counters & counters)
652 int const cur_depth = pars[pit].getDepth();
653 pit_type prev_pit = pit - 1;
655 int const prev_depth = pars[prev_pit].getDepth();
656 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
657 if (prev_depth <= cur_depth) {
658 if (prev_labeltype != LABEL_ENUMERATE) {
659 switch (pars[pit].itemdepth) {
661 counters.reset("enumi");
663 counters.reset("enumii");
665 counters.reset("enumiii");
667 counters.reset("enumiv");
673 if (prev_pit == firstpit)
683 // set the counter of a paragraph. This includes the labels
684 void LyXText::setCounter(Buffer const & buf, pit_type pit)
686 Paragraph & par = pars_[pit];
687 BufferParams const & bufparams = buf.params();
688 LyXTextClass const & textclass = bufparams.getLyXTextClass();
689 LyXLayout_ptr const & layout = par.layout();
690 Counters & counters = textclass.counters();
696 par.params().appendix(par.params().startOfAppendix());
698 par.params().appendix(pars_[pit - 1].params().appendix());
699 if (!par.params().appendix() &&
700 par.params().startOfAppendix()) {
701 par.params().appendix(true);
702 textclass.counters().reset();
705 // Maybe we have to increment the item depth.
706 incrementItemDepth(pars_, pit, 0);
709 // erase what was there before
710 par.params().labelString(string());
712 if (layout->margintype == MARGIN_MANUAL) {
713 if (par.params().labelWidthString().empty())
714 par.setLabelWidthString(layout->labelstring());
716 par.setLabelWidthString(string());
719 // is it a layout that has an automatic label?
720 if (layout->labeltype == LABEL_COUNTER) {
721 BufferParams const & bufparams = buf.params();
722 LyXTextClass const & textclass = bufparams.getLyXTextClass();
723 counters.step(layout->counter);
724 string label = expandLabel(textclass, layout, par.params().appendix());
725 par.params().labelString(label);
726 } else if (layout->labeltype == LABEL_ITEMIZE) {
727 // At some point of time we should do something more
728 // clever here, like:
729 // par.params().labelString(
730 // bufparams.user_defined_bullet(par.itemdepth).getText());
731 // for now, use a simple hardcoded label
733 switch (par.itemdepth) {
748 par.params().labelString(itemlabel);
749 } else if (layout->labeltype == LABEL_ENUMERATE) {
750 // Maybe we have to reset the enumeration counter.
751 resetEnumCounterIfNeeded(pars_, pit, 0, counters);
754 // Yes I know this is a really, really! bad solution
756 string enumcounter = "enum";
758 switch (par.itemdepth) {
770 // not a valid enumdepth...
774 counters.step(enumcounter);
776 par.params().labelString(counters.enumLabel(enumcounter));
777 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
778 counters.step("bibitem");
779 int number = counters.value("bibitem");
781 par.bibitem()->setCounter(number);
782 par.params().labelString(layout->labelstring());
784 // In biblio should't be following counters but...
786 string s = buf.B_(layout->labelstring());
789 if (layout->labeltype == LABEL_SENSITIVE) {
790 pit_type end = paragraphs().size();
791 pit_type tmppit = pit;
794 while (tmppit != end) {
795 in = pars_[tmppit].inInset();
796 // FIXME: in should be always valid.
798 (in->lyxCode() == InsetBase::FLOAT_CODE ||
799 in->lyxCode() == InsetBase::WRAP_CODE)) {
804 #warning replace this code by something that works
805 // This code does not work because we have currently no way to move up
806 // in the hierarchy of insets (JMarc 16/08/2004)
809 /* I think this code is supposed to be useful when one has a caption
810 * in a minipage in a figure inset. We need to go up to be able to see
811 * that the caption should use "Figure" as label
814 Paragraph const * owner = &ownerPar(buf, in);
816 for ( ; tmppit != end; ++tmppit)
817 if (&pars_[tmppit] == owner)
828 if (in->lyxCode() == InsetBase::FLOAT_CODE)
829 type = static_cast<InsetFloat*>(in)->params().type;
830 else if (in->lyxCode() == InsetBase::WRAP_CODE)
831 type = static_cast<InsetWrap*>(in)->params().type;
835 Floating const & fl = textclass.floats().getType(type);
837 counters.step(fl.type());
839 // Doesn't work... yet.
840 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
842 // par->SetLayout(0);
843 // s = layout->labelstring;
844 s = _("Senseless: ");
847 par.params().labelString(s);
853 // Updates all counters.
854 void LyXText::updateCounters()
857 bv()->buffer()->params().getLyXTextClass().counters().reset();
859 bool update_pos = false;
861 pit_type end = paragraphs().size();
862 for (pit_type pit = 0; pit != end; ++pit) {
863 string const oldLabel = pars_[pit].params().labelString();
866 maxdepth = pars_[pit - 1].getMaxDepthAfter();
868 if (pars_[pit].params().depth() > maxdepth)
869 pars_[pit].params().depth(maxdepth);
871 // setCounter can potentially change the labelString.
872 setCounter(*bv()->buffer(), pit);
873 string const & newLabel = pars_[pit].params().labelString();
874 if (oldLabel != newLabel) {
875 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
876 // << newLabel << endl;
883 // this really should just insert the inset and not move the cursor.
884 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
886 BOOST_ASSERT(this == cur.text());
888 cur.paragraph().insertInset(cur.pos(), inset);
892 // needed to insert the selection
893 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
895 pit_type pit = cur.pit();
896 pos_type pos = cur.pos();
899 // only to be sure, should not be neccessary
900 cur.clearSelection();
901 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
905 setCursor(cur, cur.pit(), pos);
910 // turn double CR to single CR, others are converted into one
911 // blank. Then insertStringAsLines is called
912 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
914 string linestr = str;
915 bool newline_inserted = false;
917 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
918 if (linestr[i] == '\n') {
919 if (newline_inserted) {
920 // we know that \r will be ignored by
921 // insertStringAsLines. Of course, it is a dirty
922 // trick, but it works...
923 linestr[i - 1] = '\r';
927 newline_inserted = true;
929 } else if (IsPrintable(linestr[i])) {
930 newline_inserted = false;
933 insertStringAsLines(cur, linestr);
937 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
938 bool setfont, bool boundary)
941 setCursorIntern(cur, par, pos, setfont, boundary);
942 return deleteEmptyParagraphMechanism(cur, old);
946 void LyXText::setCursor(CursorSlice & cur, pit_type par,
947 pos_type pos, bool boundary)
949 BOOST_ASSERT(par != int(paragraphs().size()));
952 cur.boundary() = boundary;
954 // now some strict checking
955 Paragraph & para = getPar(par);
957 // None of these should happen, but we're scaredy-cats
959 lyxerr << "dont like -1" << endl;
963 if (pos > para.size()) {
964 lyxerr << "dont like 1, pos: " << pos
965 << " size: " << para.size()
966 << " par: " << par << endl;
972 void LyXText::setCursorIntern(LCursor & cur,
973 pit_type par, pos_type pos, bool setfont, bool boundary)
975 setCursor(cur.top(), par, pos, boundary);
982 void LyXText::setCurrentFont(LCursor & cur)
984 BOOST_ASSERT(this == cur.text());
985 pos_type pos = cur.pos();
986 Paragraph & par = cur.paragraph();
988 if (cur.boundary() && pos > 0)
992 if (pos == cur.lastpos())
994 else // potentional bug... BUG (Lgb)
995 if (par.isSeparator(pos)) {
996 if (pos > cur.textRow().pos() &&
997 bidi.level(pos) % 2 ==
998 bidi.level(pos - 1) % 2)
1000 else if (pos + 1 < cur.lastpos())
1005 BufferParams const & bufparams = cur.buffer().params();
1006 current_font = par.getFontSettings(bufparams, pos);
1007 real_current_font = getFont(par, pos);
1009 if (cur.pos() == cur.lastpos()
1010 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1011 && !cur.boundary()) {
1012 Language const * lang = par.getParLanguage(bufparams);
1013 current_font.setLanguage(lang);
1014 current_font.setNumber(LyXFont::OFF);
1015 real_current_font.setLanguage(lang);
1016 real_current_font.setNumber(LyXFont::OFF);
1021 // x is an absolute screen coord
1022 // returns the column near the specified x-coordinate of the row
1023 // x is set to the real beginning of this column
1024 pos_type LyXText::getColumnNearX(pit_type const pit,
1025 Row const & row, int & x, bool & boundary) const
1027 int const xo = theCoords.get(this, pit).x_;
1029 RowMetrics const r = computeRowMetrics(pit, row);
1030 Paragraph const & par = pars_[pit];
1032 pos_type vc = row.pos();
1033 pos_type end = row.endpos();
1035 LyXLayout_ptr const & layout = par.layout();
1037 bool left_side = false;
1039 pos_type body_pos = par.beginOfBody();
1042 double last_tmpx = tmpx;
1045 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1048 // check for empty row
1054 while (vc < end && tmpx <= x) {
1055 c = bidi.vis2log(vc);
1057 if (body_pos > 0 && c == body_pos - 1) {
1058 tmpx += r.label_hfill +
1059 font_metrics::width(layout->labelsep, getLabelFont(par));
1060 if (par.isLineSeparator(body_pos - 1))
1061 tmpx -= singleWidth(par, body_pos - 1);
1064 if (hfillExpansion(par, row, c)) {
1065 tmpx += singleWidth(par, c);
1069 tmpx += r.label_hfill;
1070 } else if (par.isSeparator(c)) {
1071 tmpx += singleWidth(par, c);
1073 tmpx += r.separator;
1075 tmpx += singleWidth(par, c);
1080 if ((tmpx + last_tmpx) / 2 > x) {
1085 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1088 // This (rtl_support test) is not needed, but gives
1089 // some speedup if rtl_support == false
1090 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1092 // If lastrow is false, we don't need to compute
1093 // the value of rtl.
1094 bool const rtl = lastrow ? isRTL(par) : false;
1096 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1097 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1099 else if (vc == row.pos()) {
1100 c = bidi.vis2log(vc);
1101 if (bidi.level(c) % 2 == 1)
1104 c = bidi.vis2log(vc - 1);
1105 bool const rtl = (bidi.level(c) % 2 == 1);
1106 if (left_side == rtl) {
1108 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1112 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1113 if (bidi.level(end -1) % 2 == 0)
1114 tmpx -= singleWidth(par, end - 1);
1116 tmpx += singleWidth(par, end - 1);
1121 return c - row.pos();
1125 // y is screen coordinate
1126 pit_type LyXText::getPitNearY(int y) const
1128 BOOST_ASSERT(!paragraphs().empty());
1129 BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
1130 CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
1131 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
1132 << cc.size() << endl;
1134 // look for highest numbered paragraph with y coordinate less than given y
1137 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
1138 CoordCache::InnerParPosCache::const_iterator et = cc.end();
1139 for (; it != et; ++it) {
1140 lyxerr << " examining: pit: " << it->first << " y: "
1141 << it->second.y_ << endl;
1142 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
1148 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
1153 Row const & LyXText::getRowNearY(int y, pit_type pit) const
1155 Paragraph const & par = pars_[pit];
1156 int yy = theCoords.get(this, pit).y_ - par.ascent();
1157 BOOST_ASSERT(!par.rows().empty());
1158 RowList::const_iterator rit = par.rows().begin();
1159 RowList::const_iterator const rlast = boost::prior(par.rows().end());
1160 for (; rit != rlast; yy += rit->height(), ++rit)
1161 if (yy + rit->height() > y)
1167 // x,y are absolute screen coordinates
1168 // sets cursor recursively descending into nested editable insets
1169 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1171 pit_type pit = getPitNearY(y);
1172 BOOST_ASSERT(pit != -1);
1173 Row const & row = getRowNearY(y, pit);
1176 int xx = x; // is modified by getColumnNearX
1177 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1180 cur.boundary() = bound;
1183 // try to descend into nested insets
1184 InsetBase * inset = checkInsetHit(x, y);
1185 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1187 // Either we deconst editXY or better we move current_font
1188 // and real_current_font to LCursor
1189 const_cast<LyXText *>(this)->setCurrentFont(cur);
1193 // This should be just before or just behind the
1194 // cursor position set above.
1195 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1196 || inset == pars_[pit].getInset(pos));
1197 // Make sure the cursor points to the position before
1199 if (inset == pars_[pit].getInset(pos - 1))
1201 inset = inset->editXY(cur, x, y);
1202 if (cur.top().text() == this)
1203 const_cast<LyXText *>(this)->setCurrentFont(cur);
1208 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1210 if (cur.selection())
1212 if (cur.pos() == cur.lastpos())
1214 InsetBase * inset = cur.nextInset();
1215 if (!isHighlyEditableInset(inset))
1217 inset->edit(cur, front);
1222 bool LyXText::cursorLeft(LCursor & cur)
1224 if (cur.pos() != 0) {
1225 bool boundary = cur.boundary();
1226 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1227 if (!checkAndActivateInset(cur, false)) {
1228 if (false && !boundary &&
1229 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1231 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1233 return updateNeeded;
1236 if (cur.pit() != 0) {
1237 // Steps into the paragraph above
1238 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1244 bool LyXText::cursorRight(LCursor & cur)
1246 if (false && cur.boundary()) {
1247 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1250 if (cur.pos() != cur.lastpos()) {
1251 bool updateNeeded = false;
1252 if (!checkAndActivateInset(cur, true)) {
1253 updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1254 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1256 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1258 return updateNeeded;
1261 if (cur.pit() != cur.lastpit())
1262 return setCursor(cur, cur.pit() + 1, 0);
1267 bool LyXText::cursorUp(LCursor & cur)
1269 Paragraph const & par = cur.paragraph();
1270 int const row = par.pos2row(cur.pos());
1271 int const x = cur.targetX();
1273 if (!cur.selection()) {
1274 int const y = bv_funcs::getPos(cur).y_;
1276 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1278 // This happens when you move out of an inset.
1279 // And to give the DEPM the possibility of doing
1280 // something we must provide it with two different
1282 LCursor dummy = cur;
1286 return deleteEmptyParagraphMechanism(dummy, old);
1289 bool updateNeeded = false;
1292 updateNeeded |= setCursor(cur, cur.pit(),
1293 x2pos(cur.pit(), row - 1, x));
1294 } else if (cur.pit() > 0) {
1296 updateNeeded |= setCursor(cur, cur.pit(),
1297 x2pos(cur.pit(), par.rows().size() - 1, x));
1302 return updateNeeded;
1306 bool LyXText::cursorDown(LCursor & cur)
1308 Paragraph const & par = cur.paragraph();
1309 int const row = par.pos2row(cur.pos());
1310 int const x = cur.targetX();
1312 if (!cur.selection()) {
1313 int const y = bv_funcs::getPos(cur).y_;
1315 editXY(cur, x, y + par.rows()[row].descent() + 1);
1317 // This happens when you move out of an inset.
1318 // And to give the DEPM the possibility of doing
1319 // something we must provide it with two different
1321 LCursor dummy = cur;
1325 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1327 // Make sure that cur gets back whatever happened to dummy(Lgb)
1335 bool updateNeeded = false;
1337 if (row + 1 < int(par.rows().size())) {
1338 updateNeeded |= setCursor(cur, cur.pit(),
1339 x2pos(cur.pit(), row + 1, x));
1340 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1342 updateNeeded |= setCursor(cur, cur.pit(),
1343 x2pos(cur.pit(), 0, x));
1348 return updateNeeded;
1352 bool LyXText::cursorUpParagraph(LCursor & cur)
1354 bool updated = false;
1356 updated = setCursor(cur, cur.pit(), 0);
1357 else if (cur.pit() != 0)
1358 updated = setCursor(cur, cur.pit() - 1, 0);
1363 bool LyXText::cursorDownParagraph(LCursor & cur)
1365 bool updated = false;
1366 if (cur.pit() != cur.lastpit())
1367 updated = setCursor(cur, cur.pit() + 1, 0);
1369 updated = setCursor(cur, cur.pit(), cur.lastpos());
1374 // fix the cursor `cur' after a characters has been deleted at `where'
1375 // position. Called by deleteEmptyParagraphMechanism
1376 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1378 // Do nothing if cursor is not in the paragraph where the
1379 // deletion occured,
1380 if (cur.pit() != where.pit())
1383 // If cursor position is after the deletion place update it
1384 if (cur.pos() > where.pos())
1387 // Check also if we don't want to set the cursor on a spot behind the
1388 // pagragraph because we erased the last character.
1389 if (cur.pos() > cur.lastpos())
1390 cur.pos() = cur.lastpos();
1394 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1396 // Would be wrong to delete anything if we have a selection.
1397 if (cur.selection())
1400 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1401 Paragraph const & oldpar = pars_[old.pit()];
1403 // We allow all kinds of "mumbo-jumbo" when freespacing.
1404 if (oldpar.isFreeSpacing())
1407 /* Ok I'll put some comments here about what is missing.
1408 I have fixed BackSpace (and thus Delete) to not delete
1409 double-spaces automagically. I have also changed Cut,
1410 Copy and Paste to hopefully do some sensible things.
1411 There are still some small problems that can lead to
1412 double spaces stored in the document file or space at
1413 the beginning of paragraphs(). This happens if you have
1414 the cursor between to spaces and then save. Or if you
1415 cut and paste and the selection have a space at the
1416 beginning and then save right after the paste. I am
1417 sure none of these are very hard to fix, but I will
1418 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1419 that I can get some feedback. (Lgb)
1422 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1423 // delete the LineSeparator.
1426 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1427 // delete the LineSeparator.
1430 // If the chars around the old cursor were spaces, delete one of them.
1431 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1433 // Only if the cursor has really moved.
1435 && old.pos() < oldpar.size()
1436 && oldpar.isLineSeparator(old.pos())
1437 && oldpar.isLineSeparator(old.pos() - 1)) {
1438 pars_[old.pit()].erase(old.pos() - 1);
1439 #ifdef WITH_WARNINGS
1440 #warning This will not work anymore when we have multiple views of the same buffer
1441 // In this case, we will have to correct also the cursors held by
1442 // other bufferviews. It will probably be easier to do that in a more
1443 // automated way in CursorSlice code. (JMarc 26/09/2001)
1445 // correct all cursor parts
1446 fixCursorAfterDelete(cur.top(), old.top());
1447 #ifdef WITH_WARNINGS
1448 #warning DEPM, look here
1450 //fixCursorAfterDelete(cur.anchor(), old.top());
1455 // only do our magic if we changed paragraph
1456 if (old.pit() == cur.pit())
1459 // don't delete anything if this is the ONLY paragraph!
1460 if (pars_.size() == 1)
1463 // Do not delete empty paragraphs with keepempty set.
1464 if (oldpar.allowEmpty())
1467 // record if we have deleted a paragraph
1468 // we can't possibly have deleted a paragraph before this point
1469 bool deleted = false;
1471 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1472 // ok, we will delete something
1475 bool selection_position_was_oldcursor_position =
1476 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1478 // This is a bit of a overkill. We change the old and the cur par
1479 // at max, certainly not everything in between...
1480 recUndo(old.pit(), cur.pit());
1483 pars_.erase(pars_.begin() + old.pit());
1485 // Update cursor par offset if necessary.
1486 // Some 'iterator registration' would be nice that takes care of
1487 // such events. Maybe even signal/slot?
1488 if (cur.pit() > old.pit())
1490 #ifdef WITH_WARNINGS
1491 #warning DEPM, look here
1493 // if (cur.anchor().pit() > old.pit())
1494 // --cur.anchor().pit();
1496 if (selection_position_was_oldcursor_position) {
1497 // correct selection
1505 if (pars_[old.pit()].stripLeadingSpaces())
1512 ParagraphList & LyXText::paragraphs() const
1514 return const_cast<ParagraphList &>(pars_);
1518 void LyXText::recUndo(pit_type first, pit_type last) const
1520 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1524 void LyXText::recUndo(pit_type par) const
1526 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1530 int defaultRowHeight()
1532 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);