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/tostr.h"
67 using lyx::support::bformat;
70 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv)
75 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
76 background_color_(LColor::background),
77 bv_owner(bv), xo_(0), yo_(0)
81 void LyXText::init(BufferView * bv)
85 maxwidth_ = bv->workWidth();
89 par_type const end = paragraphs().size();
90 for (par_type pit = 0; pit != end; ++pit)
91 pars_[pit].rows.clear();
93 current_font = getFont(pars_[0], 0);
94 redoParagraphs(0, end);
99 bool LyXText::isMainText() const
101 return &bv()->buffer()->text() == this;
105 // takes absolute x,y coordinates
106 InsetBase * LyXText::checkInsetHit(int x, int y) const
111 getParsInRange(paragraphs(),
113 bv()->top_y() - yo_ + bv()->workHeight(),
116 // convert to screen-absolute y coordinate
118 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
119 lyxerr << " pit: " << pit << " end: " << end << endl;
120 for (; pit != end; ++pit) {
121 InsetList::const_iterator iit = pars_[pit].insetlist.begin();
122 InsetList::const_iterator iend = pars_[pit].insetlist.end();
123 for (; iit != iend; ++iit) {
124 InsetBase * inset = iit->inset;
126 lyxerr << "examining inset " << inset << endl;
127 if (theCoords.insets_.has(inset))
129 << " xo: " << inset->xo() << "..." << inset->xo() + inset->width()
130 << " yo: " << inset->yo() - inset->ascent() << "..."
131 << inset->yo() + inset->descent() << endl;
133 lyxerr << " inset has no cached position";
135 if (inset->covers(x, y)) {
136 lyxerr << "Hit inset: " << inset << endl;
141 lyxerr << "No inset hit. " << endl;
147 // Gets the fully instantiated font at a given position in a paragraph
148 // Basically the same routine as Paragraph::getFont() in paragraph.C.
149 // The difference is that this one is used for displaying, and thus we
150 // are allowed to make cosmetic improvements. For instance make footnotes
152 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
154 BOOST_ASSERT(pos >= 0);
156 LyXLayout_ptr const & layout = par.layout();
160 BufferParams const & params = bv()->buffer()->params();
161 pos_type const body_pos = par.beginOfBody();
163 // We specialize the 95% common case:
164 if (!par.getDepth()) {
165 LyXFont f = par.getFontSettings(params, pos);
168 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
169 return f.realize(layout->reslabelfont);
171 return f.realize(layout->resfont);
174 // The uncommon case need not be optimized as much
177 layoutfont = layout->labelfont;
179 layoutfont = layout->font;
181 LyXFont font = par.getFontSettings(params, pos);
182 font.realize(layoutfont);
187 // Realize with the fonts of lesser depth.
188 font.realize(defaultfont_);
194 LyXFont LyXText::getLayoutFont(par_type const pit) const
196 LyXLayout_ptr const & layout = pars_[pit].layout();
198 if (!pars_[pit].getDepth())
199 return layout->resfont;
201 LyXFont font = layout->font;
202 // Realize with the fonts of lesser depth.
203 //font.realize(outerFont(pit, paragraphs()));
204 font.realize(defaultfont_);
210 LyXFont LyXText::getLabelFont(Paragraph const & par) const
212 LyXLayout_ptr const & layout = par.layout();
215 return layout->reslabelfont;
217 LyXFont font = layout->labelfont;
218 // Realize with the fonts of lesser depth.
219 font.realize(defaultfont_);
225 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
228 LyXLayout_ptr const & layout = pars_[pit].layout();
230 // Get concrete layout font to reduce against
233 if (pos < pars_[pit].beginOfBody())
234 layoutfont = layout->labelfont;
236 layoutfont = layout->font;
238 // Realize against environment font information
239 if (pars_[pit].getDepth()) {
241 while (!layoutfont.resolved() &&
242 tp != par_type(paragraphs().size()) &&
243 pars_[tp].getDepth()) {
244 tp = outerHook(tp, paragraphs());
245 if (tp != par_type(paragraphs().size()))
246 layoutfont.realize(pars_[tp].layout()->font);
250 layoutfont.realize(defaultfont_);
252 // Now, reduce font against full layout font
253 font.reduce(layoutfont);
255 pars_[pit].setFont(pos, font);
260 // Asger is not sure we want to do this...
261 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
264 LyXLayout_ptr const & layout = par.layout();
265 pos_type const psize = par.size();
268 for (pos_type pos = 0; pos < psize; ++pos) {
269 if (pos < par.beginOfBody())
270 layoutfont = layout->labelfont;
272 layoutfont = layout->font;
274 LyXFont tmpfont = par.getFontSettings(params, pos);
275 tmpfont.reduce(layoutfont);
276 par.setFont(pos, tmpfont);
281 // return past-the-last paragraph influenced by a layout change on pit
282 par_type LyXText::undoSpan(par_type pit)
284 par_type end = paragraphs().size();
285 par_type nextpit = pit + 1;
288 //because of parindents
289 if (!pars_[pit].getDepth())
290 return boost::next(nextpit);
291 //because of depth constrains
292 for (; nextpit != end; ++pit, ++nextpit) {
293 if (!pars_[pit].getDepth())
300 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
302 BOOST_ASSERT(start != end);
303 par_type undopit = undoSpan(end - 1);
304 recUndo(start, undopit - 1);
306 BufferParams const & bufparams = bv()->buffer()->params();
307 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
309 for (par_type pit = start; pit != end; ++pit) {
310 pars_[pit].applyLayout(lyxlayout);
311 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
312 if (lyxlayout->margintype == MARGIN_MANUAL)
313 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
320 // set layout over selection and make a total rebreak of those paragraphs
321 void LyXText::setLayout(LCursor & cur, string const & layout)
323 BOOST_ASSERT(this == cur.text());
324 // special handling of new environment insets
325 BufferView & bv = cur.bv();
326 BufferParams const & params = bv.buffer()->params();
327 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
328 if (lyxlayout->is_environment) {
329 // move everything in a new environment inset
330 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
331 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
332 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
333 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
334 InsetBase * inset = new InsetEnvironment(params, layout);
335 insertInset(cur, inset);
336 //inset->edit(cur, true);
337 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
341 par_type start = cur.selBegin().par();
342 par_type end = cur.selEnd().par() + 1;
343 par_type endpit = setLayout(start, end, layout);
344 redoParagraphs(start, endpit);
352 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
354 if (!cur.selection()) {
358 beg = cur.selBegin().par();
359 end = cur.selEnd().par() + 1;
364 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
365 Paragraph const & par, int max_depth)
367 if (par.layout()->labeltype == LABEL_BIBLIO)
369 int const depth = par.params().depth();
370 if (type == LyXText::INC_DEPTH && depth < max_depth)
372 if (type == LyXText::DEC_DEPTH && depth > 0)
381 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
383 BOOST_ASSERT(this == cur.text());
385 getSelectionSpan(cur, beg, end);
388 max_depth = pars_[beg - 1].getMaxDepthAfter();
390 for (par_type pit = beg; pit != end; ++pit) {
391 if (::changeDepthAllowed(type, pars_[pit], max_depth))
393 max_depth = pars_[pit].getMaxDepthAfter();
399 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
401 BOOST_ASSERT(this == cur.text());
403 getSelectionSpan(cur, beg, end);
404 recordUndoSelection(cur);
408 max_depth = pars_[beg - 1].getMaxDepthAfter();
410 for (par_type pit = beg; pit != end; ++pit) {
411 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
412 int const depth = pars_[pit].params().depth();
413 if (type == INC_DEPTH)
414 pars_[pit].params().depth(depth + 1);
416 pars_[pit].params().depth(depth - 1);
418 max_depth = pars_[pit].getMaxDepthAfter();
420 // this handles the counter labels, and also fixes up
421 // depth values for follow-on (child) paragraphs
426 // set font over selection
427 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
429 BOOST_ASSERT(this == cur.text());
430 // if there is no selection just set the current_font
431 if (!cur.selection()) {
432 // Determine basis font
434 par_type pit = cur.par();
435 if (cur.pos() < pars_[pit].beginOfBody())
436 layoutfont = getLabelFont(pars_[pit]);
438 layoutfont = getLayoutFont(pit);
440 // Update current font
441 real_current_font.update(font,
442 cur.buffer().params().language,
445 // Reduce to implicit settings
446 current_font = real_current_font;
447 current_font.reduce(layoutfont);
448 // And resolve it completely
449 real_current_font.realize(layoutfont);
454 // Ok, we have a selection.
455 recordUndoSelection(cur);
457 par_type const beg = cur.selBegin().par();
458 par_type const end = cur.selEnd().par();
460 DocIterator dit = cur.selectionBegin();
461 DocIterator ditend = cur.selectionEnd();
463 BufferParams const & params = cur.buffer().params();
465 // Don't use forwardChar here as ditend might have
466 // pos() == lastpos() and forwardChar would miss it.
467 for (; dit != ditend; dit.forwardPos()) {
468 if (dit.pos() != dit.lastpos()) {
469 LyXFont f = getFont(dit.paragraph(), dit.pos());
470 f.update(font, params.language, toggleall);
471 setCharFont(dit.par(), dit.pos(), f);
475 redoParagraphs(beg, end + 1);
479 // the cursor set functions have a special mechanism. When they
480 // realize you left an empty paragraph, they will delete it.
482 void LyXText::cursorHome(LCursor & cur)
484 BOOST_ASSERT(this == cur.text());
485 setCursor(cur, cur.par(), cur.textRow().pos());
489 void LyXText::cursorEnd(LCursor & cur)
491 BOOST_ASSERT(this == cur.text());
492 // if not on the last row of the par, put the cursor before
494 pos_type const end = cur.textRow().endpos();
495 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
499 void LyXText::cursorTop(LCursor & cur)
501 BOOST_ASSERT(this == cur.text());
502 setCursor(cur, 0, 0);
506 void LyXText::cursorBottom(LCursor & cur)
508 BOOST_ASSERT(this == cur.text());
509 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
513 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
515 BOOST_ASSERT(this == cur.text());
516 // If the mask is completely neutral, tell user
517 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
518 // Could only happen with user style
519 cur.message(_("No font change defined. "
520 "Use Character under the Layout menu to define font change."));
524 // Try implicit word selection
525 // If there is a change in the language the implicit word selection
527 CursorSlice resetCursor = cur.top();
528 bool implicitSelection =
529 font.language() == ignore_language
530 && font.number() == LyXFont::IGNORE
531 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
534 setFont(cur, font, toggleall);
536 // Implicit selections are cleared afterwards
537 // and cursor is set to the original position.
538 if (implicitSelection) {
539 cur.clearSelection();
540 cur.top() = resetCursor;
546 string LyXText::getStringToIndex(LCursor & cur)
548 BOOST_ASSERT(this == cur.text());
549 // Try implicit word selection
550 // If there is a change in the language the implicit word selection
552 CursorSlice const reset_cursor = cur.top();
553 bool const implicitSelection =
554 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
557 if (!cur.selection())
558 cur.message(_("Nothing to index!"));
559 else if (cur.selBegin().par() != cur.selEnd().par())
560 cur.message(_("Cannot index more than one paragraph!"));
562 idxstring = cur.selectionAsString(false);
564 // Reset cursors to their original position.
565 cur.top() = reset_cursor;
568 // Clear the implicit selection.
569 if (implicitSelection)
570 cur.clearSelection();
576 void LyXText::setParagraph(LCursor & cur,
577 Spacing const & spacing, LyXAlignment align,
578 string const & labelwidthstring, bool noindent)
580 BOOST_ASSERT(cur.text());
581 // make sure that the depth behind the selection are restored, too
582 par_type undopit = undoSpan(cur.selEnd().par());
583 recUndo(cur.selBegin().par(), undopit - 1);
585 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
587 Paragraph & par = pars_[pit];
588 ParagraphParameters & params = par.params();
589 params.spacing(spacing);
591 // does the layout allow the new alignment?
592 LyXLayout_ptr const & layout = par.layout();
594 if (align == LYX_ALIGN_LAYOUT)
595 align = layout->align;
596 if (align & layout->alignpossible) {
597 if (align == layout->align)
598 params.align(LYX_ALIGN_LAYOUT);
602 par.setLabelWidthString(labelwidthstring);
603 params.noindent(noindent);
606 redoParagraphs(cur.selBegin().par(), undopit);
610 string expandLabel(LyXTextClass const & textclass,
611 LyXLayout_ptr const & layout, bool appendix)
613 string fmt = appendix ?
614 layout->labelstring_appendix() : layout->labelstring();
616 // handle 'inherited level parts' in 'fmt',
617 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
618 size_t const i = fmt.find('@', 0);
619 if (i != string::npos) {
620 size_t const j = fmt.find('@', i + 1);
621 if (j != string::npos) {
622 string parent(fmt, i + 1, j - i - 1);
623 string label = expandLabel(textclass, textclass[parent], appendix);
624 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
628 return textclass.counters().counterLabel(fmt);
634 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
636 int const cur_labeltype = pars[pit].layout()->labeltype;
638 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
641 int const cur_depth = pars[pit].getDepth();
643 par_type prev_pit = pit - 1;
645 int const prev_depth = pars[prev_pit].getDepth();
646 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
647 if (prev_depth == 0 && cur_depth > 0) {
648 if (prev_labeltype == cur_labeltype) {
649 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
652 } else if (prev_depth < cur_depth) {
653 if (prev_labeltype == cur_labeltype) {
654 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
657 } else if (prev_depth == cur_depth) {
658 if (prev_labeltype == cur_labeltype) {
659 pars[pit].itemdepth = pars[prev_pit].itemdepth;
663 if (prev_pit == first_pit)
671 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
672 par_type firstpit, Counters & counters)
677 int const cur_depth = pars[pit].getDepth();
678 par_type prev_pit = pit - 1;
680 int const prev_depth = pars[prev_pit].getDepth();
681 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
682 if (prev_depth <= cur_depth) {
683 if (prev_labeltype != LABEL_ENUMERATE) {
684 switch (pars[pit].itemdepth) {
686 counters.reset("enumi");
688 counters.reset("enumii");
690 counters.reset("enumiii");
692 counters.reset("enumiv");
698 if (prev_pit == firstpit)
708 // set the counter of a paragraph. This includes the labels
709 void LyXText::setCounter(Buffer const & buf, par_type pit)
711 BufferParams const & bufparams = buf.params();
712 LyXTextClass const & textclass = bufparams.getLyXTextClass();
713 LyXLayout_ptr const & layout = pars_[pit].layout();
714 par_type first_pit = 0;
715 Counters & counters = textclass.counters();
718 pars_[pit].itemdepth = 0;
720 if (pit == first_pit) {
721 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
723 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
724 if (!pars_[pit].params().appendix() &&
725 pars_[pit].params().startOfAppendix()) {
726 pars_[pit].params().appendix(true);
727 textclass.counters().reset();
730 // Maybe we have to increment the item depth.
731 incrementItemDepth(pars_, pit, first_pit);
734 // erase what was there before
735 pars_[pit].params().labelString(string());
737 if (layout->margintype == MARGIN_MANUAL) {
738 if (pars_[pit].params().labelWidthString().empty())
739 pars_[pit].setLabelWidthString(layout->labelstring());
741 pars_[pit].setLabelWidthString(string());
744 // is it a layout that has an automatic label?
745 if (layout->labeltype == LABEL_COUNTER) {
746 BufferParams const & bufparams = buf.params();
747 LyXTextClass const & textclass = bufparams.getLyXTextClass();
748 counters.step(layout->counter);
749 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
750 pars_[pit].params().labelString(label);
751 } else if (layout->labeltype == LABEL_ITEMIZE) {
752 // At some point of time we should do something more
753 // clever here, like:
754 // pars_[pit].params().labelString(
755 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
756 // for now, use a simple hardcoded label
758 switch (pars_[pit].itemdepth) {
773 pars_[pit].params().labelString(itemlabel);
774 } else if (layout->labeltype == LABEL_ENUMERATE) {
775 // Maybe we have to reset the enumeration counter.
776 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
779 // Yes I know this is a really, really! bad solution
781 string enumcounter = "enum";
783 switch (pars_[pit].itemdepth) {
795 // not a valid enumdepth...
799 counters.step(enumcounter);
801 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
802 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
803 counters.step("bibitem");
804 int number = counters.value("bibitem");
805 if (pars_[pit].bibitem()) {
806 pars_[pit].bibitem()->setCounter(number);
807 pars_[pit].params().labelString(layout->labelstring());
809 // In biblio should't be following counters but...
811 string s = buf.B_(layout->labelstring());
814 if (layout->labeltype == LABEL_SENSITIVE) {
815 par_type end = paragraphs().size();
816 par_type tmppit = pit;
819 while (tmppit != end) {
820 in = pars_[tmppit].inInset();
821 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
822 in->lyxCode() == InsetBase::WRAP_CODE) {
826 Paragraph const * owner = &ownerPar(buf, in);
828 for ( ; tmppit != end; ++tmppit)
829 if (&pars_[tmppit] == owner)
837 if (in->lyxCode() == InsetBase::FLOAT_CODE)
838 type = static_cast<InsetFloat*>(in)->params().type;
839 else if (in->lyxCode() == InsetBase::WRAP_CODE)
840 type = static_cast<InsetWrap*>(in)->params().type;
844 Floating const & fl = textclass.floats().getType(type);
846 counters.step(fl.type());
848 // Doesn't work... yet.
849 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
851 // par->SetLayout(0);
852 // s = layout->labelstring;
853 s = _("Senseless: ");
856 pars_[pit].params().labelString(s);
862 // Updates all counters.
863 void LyXText::updateCounters()
866 bv()->buffer()->params().getLyXTextClass().counters().reset();
868 bool update_pos = false;
870 par_type end = paragraphs().size();
871 for (par_type pit = 0; pit != end; ++pit) {
872 string const oldLabel = pars_[pit].params().labelString();
875 maxdepth = pars_[pit - 1].getMaxDepthAfter();
877 if (pars_[pit].params().depth() > maxdepth)
878 pars_[pit].params().depth(maxdepth);
880 // setCounter can potentially change the labelString.
881 setCounter(*bv()->buffer(), pit);
882 string const & newLabel = pars_[pit].params().labelString();
883 if (oldLabel != newLabel) {
884 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
885 // << newLabel << endl;
886 redoParagraphInternal(pit);
891 updateParPositions();
895 // this really should just inset the inset and not move the cursor.
896 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
898 BOOST_ASSERT(this == cur.text());
900 cur.paragraph().insertInset(cur.pos(), inset);
905 // needed to insert the selection
906 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
908 par_type pit = cur.par();
909 par_type endpit = cur.par() + 1;
910 pos_type pos = cur.pos();
913 // only to be sure, should not be neccessary
914 cur.clearSelection();
915 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
917 redoParagraphs(cur.par(), endpit);
919 setCursor(cur, cur.par(), pos);
924 // turn double CR to single CR, others are converted into one
925 // blank. Then insertStringAsLines is called
926 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
928 string linestr = str;
929 bool newline_inserted = false;
931 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
932 if (linestr[i] == '\n') {
933 if (newline_inserted) {
934 // we know that \r will be ignored by
935 // insertStringAsLines. Of course, it is a dirty
936 // trick, but it works...
937 linestr[i - 1] = '\r';
941 newline_inserted = true;
943 } else if (IsPrintable(linestr[i])) {
944 newline_inserted = false;
947 insertStringAsLines(cur, linestr);
951 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
952 bool setfont, bool boundary)
955 setCursorIntern(cur, par, pos, setfont, boundary);
956 return deleteEmptyParagraphMechanism(cur, old);
960 void LyXText::setCursor(CursorSlice & cur, par_type par,
961 pos_type pos, bool boundary)
963 BOOST_ASSERT(par != int(paragraphs().size()));
967 cur.boundary() = boundary;
969 // no rows, no fun...
970 if (paragraphs().begin()->rows.empty())
973 // now some strict checking
974 Paragraph & para = getPar(par);
975 Row const & row = *para.getRow(pos);
976 pos_type const end = row.endpos();
978 // None of these should happen, but we're scaredy-cats
980 lyxerr << "dont like -1" << endl;
984 if (pos > para.size()) {
985 lyxerr << "dont like 1, pos: " << pos
986 << " size: " << para.size()
987 << " row.pos():" << row.pos()
988 << " par: " << par << endl;
993 lyxerr << "dont like 2, pos: " << pos
994 << " size: " << para.size()
995 << " row.pos():" << row.pos()
996 << " par: " << par << endl;
997 // This shouldn't happen.
1001 if (pos < row.pos()) {
1002 lyxerr << "dont like 3 please report pos:" << pos
1003 << " size: " << para.size()
1004 << " row.pos():" << row.pos()
1005 << " par: " << par << endl;
1006 BOOST_ASSERT(false);
1011 void LyXText::setCursorIntern(LCursor & cur,
1012 par_type par, pos_type pos, bool setfont, bool boundary)
1014 setCursor(cur.top(), par, pos, boundary);
1015 cur.x_target() = cursorX(cur.top());
1017 setCurrentFont(cur);
1021 void LyXText::setCurrentFont(LCursor & cur)
1023 BOOST_ASSERT(this == cur.text());
1024 pos_type pos = cur.pos();
1025 Paragraph & par = cur.paragraph();
1027 if (cur.boundary() && pos > 0)
1031 if (pos == cur.lastpos())
1033 else // potentional bug... BUG (Lgb)
1034 if (par.isSeparator(pos)) {
1035 if (pos > cur.textRow().pos() &&
1036 bidi.level(pos) % 2 ==
1037 bidi.level(pos - 1) % 2)
1039 else if (pos + 1 < cur.lastpos())
1044 BufferParams const & bufparams = cur.buffer().params();
1045 current_font = par.getFontSettings(bufparams, pos);
1046 real_current_font = getFont(par, pos);
1048 if (cur.pos() == cur.lastpos()
1049 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1050 && !cur.boundary()) {
1051 Language const * lang = par.getParLanguage(bufparams);
1052 current_font.setLanguage(lang);
1053 current_font.setNumber(LyXFont::OFF);
1054 real_current_font.setLanguage(lang);
1055 real_current_font.setNumber(LyXFont::OFF);
1060 // x is an absolute screen coord
1061 // returns the column near the specified x-coordinate of the row
1062 // x is set to the real beginning of this column
1063 pos_type LyXText::getColumnNearX(par_type const pit,
1064 Row const & row, int & x, bool & boundary) const
1067 RowMetrics const r = computeRowMetrics(pit, row);
1068 Paragraph const & par = pars_[pit];
1070 pos_type vc = row.pos();
1071 pos_type end = row.endpos();
1073 LyXLayout_ptr const & layout = par.layout();
1075 bool left_side = false;
1077 pos_type body_pos = par.beginOfBody();
1080 double last_tmpx = tmpx;
1083 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1086 // check for empty row
1088 x = int(tmpx) + xo_;
1092 while (vc < end && tmpx <= x) {
1093 c = bidi.vis2log(vc);
1095 if (body_pos > 0 && c == body_pos - 1) {
1096 tmpx += r.label_hfill +
1097 font_metrics::width(layout->labelsep, getLabelFont(par));
1098 if (par.isLineSeparator(body_pos - 1))
1099 tmpx -= singleWidth(par, body_pos - 1);
1102 if (hfillExpansion(par, row, c)) {
1103 tmpx += singleWidth(par, c);
1107 tmpx += r.label_hfill;
1108 } else if (par.isSeparator(c)) {
1109 tmpx += singleWidth(par, c);
1111 tmpx += r.separator;
1113 tmpx += singleWidth(par, c);
1118 if ((tmpx + last_tmpx) / 2 > x) {
1123 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1126 // This (rtl_support test) is not needed, but gives
1127 // some speedup if rtl_support == false
1128 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1130 // If lastrow is false, we don't need to compute
1131 // the value of rtl.
1132 bool const rtl = lastrow ? isRTL(par) : false;
1134 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1135 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1137 else if (vc == row.pos()) {
1138 c = bidi.vis2log(vc);
1139 if (bidi.level(c) % 2 == 1)
1142 c = bidi.vis2log(vc - 1);
1143 bool const rtl = (bidi.level(c) % 2 == 1);
1144 if (left_side == rtl) {
1146 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1150 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1151 if (bidi.level(end -1) % 2 == 0)
1152 tmpx -= singleWidth(par, end - 1);
1154 tmpx += singleWidth(par, end - 1);
1158 x = int(tmpx) + xo_;
1159 return c - row.pos();
1163 // y is relative to this LyXText's top
1164 // this is only used in the two functions below
1165 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1167 BOOST_ASSERT(!paragraphs().empty());
1168 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1169 par_type const pend = paragraphs().size() - 1;
1171 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1174 RowList::iterator rit = pars_[pit].rows.end();
1175 RowList::iterator const rbegin = pars_[pit].rows.begin();
1178 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1184 // x,y are absolute coordinates
1185 // sets cursor only within this LyXText
1186 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1191 Row const & row = getRowNearY(y, pit);
1192 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1193 << row.pos() << endl;
1195 int xx = x + xo_; // getRowNearX get absolute x coords
1196 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1197 setCursor(cur, pit, pos, true, bound);
1201 // x,y are absolute screen coordinates
1202 // sets cursor recursively descending into nested editable insets
1203 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1206 Row const & row = getRowNearY(y - yo_, pit);
1209 int xx = x; // is modified by getColumnNearX
1210 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1213 cur.boundary() = bound;
1215 // try to descend into nested insets
1216 InsetBase * inset = checkInsetHit(x, y);
1217 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1221 // This should be just before or just behind the
1222 // cursor position set above.
1223 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1224 || inset == pars_[pit].getInset(pos));
1225 // Make sure the cursor points to the position before
1227 if (inset == pars_[pit].getInset(pos - 1))
1229 return inset->editXY(cur, x, y);
1233 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1235 if (cur.selection())
1237 if (cur.pos() == cur.lastpos())
1239 InsetBase * inset = cur.nextInset();
1240 if (!isHighlyEditableInset(inset))
1242 inset->edit(cur, front);
1247 void LyXText::cursorLeft(LCursor & cur)
1249 if (cur.pos() != 0) {
1250 bool boundary = cur.boundary();
1251 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1252 if (!checkAndActivateInset(cur, false)) {
1253 if (false && !boundary &&
1254 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1255 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1260 if (cur.par() != 0) {
1261 // steps into the paragraph above
1262 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1267 void LyXText::cursorRight(LCursor & cur)
1269 if (false && cur.boundary()) {
1270 setCursor(cur, cur.par(), cur.pos(), true, false);
1274 if (cur.pos() != cur.lastpos()) {
1275 if (!checkAndActivateInset(cur, true)) {
1276 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1277 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1279 setCursor(cur, cur.par(), cur.pos(), true, true);
1284 if (cur.par() != cur.lastpar())
1285 setCursor(cur, cur.par() + 1, 0);
1289 void LyXText::cursorUp(LCursor & cur)
1291 Row const & row = cur.textRow();
1292 int x = cur.x_target();
1293 int y = cursorY(cur.top()) - row.baseline() - 1;
1294 setCursorFromCoordinates(cur, x, y);
1296 if (!cur.selection()) {
1297 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1298 if (inset_hit && isHighlyEditableInset(inset_hit))
1299 inset_hit->editXY(cur, cur.x_target(), y);
1304 void LyXText::cursorDown(LCursor & cur)
1306 Row const & row = cur.textRow();
1307 int x = cur.x_target();
1308 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1309 setCursorFromCoordinates(cur, x, y);
1311 if (!cur.selection()) {
1312 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1313 if (inset_hit && isHighlyEditableInset(inset_hit))
1314 inset_hit->editXY(cur, cur.x_target(), y);
1319 void LyXText::cursorUpParagraph(LCursor & cur)
1322 setCursor(cur, cur.par(), 0);
1323 else if (cur.par() != 0)
1324 setCursor(cur, cur.par() - 1, 0);
1328 void LyXText::cursorDownParagraph(LCursor & cur)
1330 if (cur.par() != cur.lastpar())
1331 setCursor(cur, cur.par() + 1, 0);
1333 setCursor(cur, cur.par(), cur.lastpos());
1337 // fix the cursor `cur' after a characters has been deleted at `where'
1338 // position. Called by deleteEmptyParagraphMechanism
1339 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1341 // do notheing if cursor is not in the paragraph where the
1342 // deletion occured,
1343 if (cur.par() != where.par())
1346 // if cursor position is after the deletion place update it
1347 if (cur.pos() > where.pos())
1350 // check also if we don't want to set the cursor on a spot behind the
1351 // pagragraph because we erased the last character.
1352 if (cur.pos() > cur.lastpos())
1353 cur.pos() = cur.lastpos();
1357 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1359 BOOST_ASSERT(cur.size() == old.size());
1360 // Would be wrong to delete anything if we have a selection.
1361 if (cur.selection())
1364 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1365 Paragraph const & oldpar = pars_[old.par()];
1367 // We allow all kinds of "mumbo-jumbo" when freespacing.
1368 if (oldpar.isFreeSpacing())
1371 /* Ok I'll put some comments here about what is missing.
1372 I have fixed BackSpace (and thus Delete) to not delete
1373 double-spaces automagically. I have also changed Cut,
1374 Copy and Paste to hopefully do some sensible things.
1375 There are still some small problems that can lead to
1376 double spaces stored in the document file or space at
1377 the beginning of paragraphs(). This happens if you have
1378 the cursor between to spaces and then save. Or if you
1379 cut and paste and the selection have a space at the
1380 beginning and then save right after the paste. I am
1381 sure none of these are very hard to fix, but I will
1382 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1383 that I can get some feedback. (Lgb)
1386 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1387 // delete the LineSeparator.
1390 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1391 // delete the LineSeparator.
1394 // If the chars around the old cursor were spaces, delete one of them.
1395 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1397 // Only if the cursor has really moved.
1399 && old.pos() < oldpar.size()
1400 && oldpar.isLineSeparator(old.pos())
1401 && oldpar.isLineSeparator(old.pos() - 1)) {
1402 pars_[old.par()].erase(old.pos() - 1);
1403 #ifdef WITH_WARNINGS
1404 #warning This will not work anymore when we have multiple views of the same buffer
1405 // In this case, we will have to correct also the cursors held by
1406 // other bufferviews. It will probably be easier to do that in a more
1407 // automated way in CursorSlice code. (JMarc 26/09/2001)
1409 // correct all cursor parts
1410 fixCursorAfterDelete(cur.top(), old.top());
1411 #ifdef WITH_WARNINGS
1412 #warning DEPM, look here
1414 //fixCursorAfterDelete(cur.anchor(), old.top());
1419 // only do our magic if we changed paragraph
1420 if (old.par() == cur.par())
1423 // don't delete anything if this is the ONLY paragraph!
1424 if (pars_.size() == 1)
1427 // Do not delete empty paragraphs with keepempty set.
1428 if (oldpar.allowEmpty())
1431 // record if we have deleted a paragraph
1432 // we can't possibly have deleted a paragraph before this point
1433 bool deleted = false;
1435 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1436 // ok, we will delete something
1437 CursorSlice tmpcursor;
1441 bool selection_position_was_oldcursor_position =
1442 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1444 // This is a bit of a overkill. We change the old and the cur par
1445 // at max, certainly not everything in between...
1446 recUndo(old.par(), cur.par());
1449 pars_.erase(pars_.begin() + old.par());
1451 // Update cursor par offset if necessary.
1452 // Some 'iterator registration' would be nice that takes care of
1453 // such events. Maybe even signal/slot?
1454 if (cur.par() > old.par())
1456 #ifdef WITH_WARNINGS
1457 #warning DEPM, look here
1459 // if (cur.anchor().par() > old.par())
1460 // --cur.anchor().par();
1462 if (selection_position_was_oldcursor_position) {
1463 // correct selection
1471 if (pars_[old.par()].stripLeadingSpaces())
1478 ParagraphList & LyXText::paragraphs() const
1480 return const_cast<ParagraphList &>(pars_);
1484 void LyXText::recUndo(par_type first, par_type last) const
1486 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1490 void LyXText::recUndo(par_type par) const
1492 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1496 int defaultRowHeight()
1498 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);