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 Paragraph & par = pars_[pit];
712 BufferParams const & bufparams = buf.params();
713 LyXTextClass const & textclass = bufparams.getLyXTextClass();
714 LyXLayout_ptr const & layout = par.layout();
715 par_type first_pit = 0;
716 Counters & counters = textclass.counters();
721 if (pit == first_pit) {
722 par.params().appendix(par.params().startOfAppendix());
724 par.params().appendix(pars_[pit - 1].params().appendix());
725 if (!par.params().appendix() &&
726 par.params().startOfAppendix()) {
727 par.params().appendix(true);
728 textclass.counters().reset();
731 // Maybe we have to increment the item depth.
732 incrementItemDepth(pars_, pit, first_pit);
735 // erase what was there before
736 par.params().labelString(string());
738 if (layout->margintype == MARGIN_MANUAL) {
739 if (par.params().labelWidthString().empty())
740 par.setLabelWidthString(layout->labelstring());
742 par.setLabelWidthString(string());
745 // is it a layout that has an automatic label?
746 if (layout->labeltype == LABEL_COUNTER) {
747 BufferParams const & bufparams = buf.params();
748 LyXTextClass const & textclass = bufparams.getLyXTextClass();
749 counters.step(layout->counter);
750 string label = expandLabel(textclass, layout, par.params().appendix());
751 par.params().labelString(label);
752 } else if (layout->labeltype == LABEL_ITEMIZE) {
753 // At some point of time we should do something more
754 // clever here, like:
755 // par.params().labelString(
756 // bufparams.user_defined_bullet(par.itemdepth).getText());
757 // for now, use a simple hardcoded label
759 switch (par.itemdepth) {
774 par.params().labelString(itemlabel);
775 } else if (layout->labeltype == LABEL_ENUMERATE) {
776 // Maybe we have to reset the enumeration counter.
777 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
780 // Yes I know this is a really, really! bad solution
782 string enumcounter = "enum";
784 switch (par.itemdepth) {
796 // not a valid enumdepth...
800 counters.step(enumcounter);
802 par.params().labelString(counters.enumLabel(enumcounter));
803 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
804 counters.step("bibitem");
805 int number = counters.value("bibitem");
807 par.bibitem()->setCounter(number);
808 par.params().labelString(layout->labelstring());
810 // In biblio should't be following counters but...
812 string s = buf.B_(layout->labelstring());
815 if (layout->labeltype == LABEL_SENSITIVE) {
816 par_type end = paragraphs().size();
817 par_type tmppit = pit;
820 while (tmppit != end) {
821 in = pars_[tmppit].inInset();
822 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
823 in->lyxCode() == InsetBase::WRAP_CODE) {
827 Paragraph const * owner = &ownerPar(buf, in);
829 for ( ; tmppit != end; ++tmppit)
830 if (&pars_[tmppit] == owner)
838 if (in->lyxCode() == InsetBase::FLOAT_CODE)
839 type = static_cast<InsetFloat*>(in)->params().type;
840 else if (in->lyxCode() == InsetBase::WRAP_CODE)
841 type = static_cast<InsetWrap*>(in)->params().type;
845 Floating const & fl = textclass.floats().getType(type);
847 counters.step(fl.type());
849 // Doesn't work... yet.
850 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
852 // par->SetLayout(0);
853 // s = layout->labelstring;
854 s = _("Senseless: ");
857 par.params().labelString(s);
863 // Updates all counters.
864 void LyXText::updateCounters()
867 bv()->buffer()->params().getLyXTextClass().counters().reset();
869 bool update_pos = false;
871 par_type end = paragraphs().size();
872 for (par_type pit = 0; pit != end; ++pit) {
873 string const oldLabel = pars_[pit].params().labelString();
876 maxdepth = pars_[pit - 1].getMaxDepthAfter();
878 if (pars_[pit].params().depth() > maxdepth)
879 pars_[pit].params().depth(maxdepth);
881 // setCounter can potentially change the labelString.
882 setCounter(*bv()->buffer(), pit);
883 string const & newLabel = pars_[pit].params().labelString();
884 if (oldLabel != newLabel) {
885 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
886 // << newLabel << endl;
887 redoParagraphInternal(pit);
892 updateParPositions();
896 // this really should just inset the inset and not move the cursor.
897 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
899 BOOST_ASSERT(this == cur.text());
901 cur.paragraph().insertInset(cur.pos(), inset);
906 // needed to insert the selection
907 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
909 par_type pit = cur.par();
910 par_type endpit = cur.par() + 1;
911 pos_type pos = cur.pos();
914 // only to be sure, should not be neccessary
915 cur.clearSelection();
916 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
918 redoParagraphs(cur.par(), endpit);
920 setCursor(cur, cur.par(), pos);
925 // turn double CR to single CR, others are converted into one
926 // blank. Then insertStringAsLines is called
927 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
929 string linestr = str;
930 bool newline_inserted = false;
932 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
933 if (linestr[i] == '\n') {
934 if (newline_inserted) {
935 // we know that \r will be ignored by
936 // insertStringAsLines. Of course, it is a dirty
937 // trick, but it works...
938 linestr[i - 1] = '\r';
942 newline_inserted = true;
944 } else if (IsPrintable(linestr[i])) {
945 newline_inserted = false;
948 insertStringAsLines(cur, linestr);
952 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
953 bool setfont, bool boundary)
956 setCursorIntern(cur, par, pos, setfont, boundary);
957 return deleteEmptyParagraphMechanism(cur, old);
961 void LyXText::setCursor(CursorSlice & cur, par_type par,
962 pos_type pos, bool boundary)
964 BOOST_ASSERT(par != int(paragraphs().size()));
968 cur.boundary() = boundary;
970 // no rows, no fun...
971 if (paragraphs().begin()->rows.empty())
974 // now some strict checking
975 Paragraph & para = getPar(par);
976 Row const & row = *para.getRow(pos);
977 pos_type const end = row.endpos();
979 // None of these should happen, but we're scaredy-cats
981 lyxerr << "dont like -1" << endl;
985 if (pos > para.size()) {
986 lyxerr << "dont like 1, pos: " << pos
987 << " size: " << para.size()
988 << " row.pos():" << row.pos()
989 << " par: " << par << endl;
994 lyxerr << "dont like 2, pos: " << pos
995 << " size: " << para.size()
996 << " row.pos():" << row.pos()
997 << " par: " << par << endl;
998 // This shouldn't happen.
1002 if (pos < row.pos()) {
1003 lyxerr << "dont like 3 please report pos:" << pos
1004 << " size: " << para.size()
1005 << " row.pos():" << row.pos()
1006 << " par: " << par << endl;
1007 BOOST_ASSERT(false);
1012 void LyXText::setCursorIntern(LCursor & cur,
1013 par_type par, pos_type pos, bool setfont, bool boundary)
1015 setCursor(cur.top(), par, pos, boundary);
1016 cur.x_target() = cursorX(cur.top());
1018 setCurrentFont(cur);
1022 void LyXText::setCurrentFont(LCursor & cur)
1024 BOOST_ASSERT(this == cur.text());
1025 pos_type pos = cur.pos();
1026 Paragraph & par = cur.paragraph();
1028 if (cur.boundary() && pos > 0)
1032 if (pos == cur.lastpos())
1034 else // potentional bug... BUG (Lgb)
1035 if (par.isSeparator(pos)) {
1036 if (pos > cur.textRow().pos() &&
1037 bidi.level(pos) % 2 ==
1038 bidi.level(pos - 1) % 2)
1040 else if (pos + 1 < cur.lastpos())
1045 BufferParams const & bufparams = cur.buffer().params();
1046 current_font = par.getFontSettings(bufparams, pos);
1047 real_current_font = getFont(par, pos);
1049 if (cur.pos() == cur.lastpos()
1050 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1051 && !cur.boundary()) {
1052 Language const * lang = par.getParLanguage(bufparams);
1053 current_font.setLanguage(lang);
1054 current_font.setNumber(LyXFont::OFF);
1055 real_current_font.setLanguage(lang);
1056 real_current_font.setNumber(LyXFont::OFF);
1061 // x is an absolute screen coord
1062 // returns the column near the specified x-coordinate of the row
1063 // x is set to the real beginning of this column
1064 pos_type LyXText::getColumnNearX(par_type const pit,
1065 Row const & row, int & x, bool & boundary) const
1068 RowMetrics const r = computeRowMetrics(pit, row);
1069 Paragraph const & par = pars_[pit];
1071 pos_type vc = row.pos();
1072 pos_type end = row.endpos();
1074 LyXLayout_ptr const & layout = par.layout();
1076 bool left_side = false;
1078 pos_type body_pos = par.beginOfBody();
1081 double last_tmpx = tmpx;
1084 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1087 // check for empty row
1089 x = int(tmpx) + xo_;
1093 while (vc < end && tmpx <= x) {
1094 c = bidi.vis2log(vc);
1096 if (body_pos > 0 && c == body_pos - 1) {
1097 tmpx += r.label_hfill +
1098 font_metrics::width(layout->labelsep, getLabelFont(par));
1099 if (par.isLineSeparator(body_pos - 1))
1100 tmpx -= singleWidth(par, body_pos - 1);
1103 if (hfillExpansion(par, row, c)) {
1104 tmpx += singleWidth(par, c);
1108 tmpx += r.label_hfill;
1109 } else if (par.isSeparator(c)) {
1110 tmpx += singleWidth(par, c);
1112 tmpx += r.separator;
1114 tmpx += singleWidth(par, c);
1119 if ((tmpx + last_tmpx) / 2 > x) {
1124 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1127 // This (rtl_support test) is not needed, but gives
1128 // some speedup if rtl_support == false
1129 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1131 // If lastrow is false, we don't need to compute
1132 // the value of rtl.
1133 bool const rtl = lastrow ? isRTL(par) : false;
1135 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1136 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1138 else if (vc == row.pos()) {
1139 c = bidi.vis2log(vc);
1140 if (bidi.level(c) % 2 == 1)
1143 c = bidi.vis2log(vc - 1);
1144 bool const rtl = (bidi.level(c) % 2 == 1);
1145 if (left_side == rtl) {
1147 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1151 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1152 if (bidi.level(end -1) % 2 == 0)
1153 tmpx -= singleWidth(par, end - 1);
1155 tmpx += singleWidth(par, end - 1);
1159 x = int(tmpx) + xo_;
1160 return c - row.pos();
1164 // y is relative to this LyXText's top
1165 // this is only used in the two functions below
1166 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1168 BOOST_ASSERT(!paragraphs().empty());
1169 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1170 par_type const pend = paragraphs().size() - 1;
1172 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1175 RowList::iterator rit = pars_[pit].rows.end();
1176 RowList::iterator const rbegin = pars_[pit].rows.begin();
1179 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1185 // x,y are absolute coordinates
1186 // sets cursor only within this LyXText
1187 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1192 Row const & row = getRowNearY(y, pit);
1193 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1194 << row.pos() << endl;
1196 int xx = x + xo_; // getRowNearX get absolute x coords
1197 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1198 setCursor(cur, pit, pos, true, bound);
1202 // x,y are absolute screen coordinates
1203 // sets cursor recursively descending into nested editable insets
1204 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1207 Row const & row = getRowNearY(y - yo_, pit);
1210 int xx = x; // is modified by getColumnNearX
1211 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1214 cur.boundary() = bound;
1216 // try to descend into nested insets
1217 InsetBase * inset = checkInsetHit(x, y);
1218 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1222 // This should be just before or just behind the
1223 // cursor position set above.
1224 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1225 || inset == pars_[pit].getInset(pos));
1226 // Make sure the cursor points to the position before
1228 if (inset == pars_[pit].getInset(pos - 1))
1230 return inset->editXY(cur, x, y);
1234 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1236 if (cur.selection())
1238 if (cur.pos() == cur.lastpos())
1240 InsetBase * inset = cur.nextInset();
1241 if (!isHighlyEditableInset(inset))
1243 inset->edit(cur, front);
1248 void LyXText::cursorLeft(LCursor & cur)
1250 if (cur.pos() != 0) {
1251 bool boundary = cur.boundary();
1252 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1253 if (!checkAndActivateInset(cur, false)) {
1254 if (false && !boundary &&
1255 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1256 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1261 if (cur.par() != 0) {
1262 // steps into the paragraph above
1263 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1268 void LyXText::cursorRight(LCursor & cur)
1270 if (false && cur.boundary()) {
1271 setCursor(cur, cur.par(), cur.pos(), true, false);
1275 if (cur.pos() != cur.lastpos()) {
1276 if (!checkAndActivateInset(cur, true)) {
1277 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1278 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1280 setCursor(cur, cur.par(), cur.pos(), true, true);
1285 if (cur.par() != cur.lastpar())
1286 setCursor(cur, cur.par() + 1, 0);
1290 void LyXText::cursorUp(LCursor & cur)
1292 Row const & row = cur.textRow();
1293 int x = cur.x_target();
1294 int y = cursorY(cur.top()) - row.baseline() - 1;
1295 setCursorFromCoordinates(cur, x, y);
1297 if (!cur.selection()) {
1298 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1299 if (inset_hit && isHighlyEditableInset(inset_hit))
1300 inset_hit->editXY(cur, cur.x_target(), y);
1305 void LyXText::cursorDown(LCursor & cur)
1307 Row const & row = cur.textRow();
1308 int x = cur.x_target();
1309 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1310 setCursorFromCoordinates(cur, x, y);
1312 if (!cur.selection()) {
1313 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1314 if (inset_hit && isHighlyEditableInset(inset_hit))
1315 inset_hit->editXY(cur, cur.x_target(), y);
1320 void LyXText::cursorUpParagraph(LCursor & cur)
1323 setCursor(cur, cur.par(), 0);
1324 else if (cur.par() != 0)
1325 setCursor(cur, cur.par() - 1, 0);
1329 void LyXText::cursorDownParagraph(LCursor & cur)
1331 if (cur.par() != cur.lastpar())
1332 setCursor(cur, cur.par() + 1, 0);
1334 setCursor(cur, cur.par(), cur.lastpos());
1338 // fix the cursor `cur' after a characters has been deleted at `where'
1339 // position. Called by deleteEmptyParagraphMechanism
1340 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1342 // do notheing if cursor is not in the paragraph where the
1343 // deletion occured,
1344 if (cur.par() != where.par())
1347 // if cursor position is after the deletion place update it
1348 if (cur.pos() > where.pos())
1351 // check also if we don't want to set the cursor on a spot behind the
1352 // pagragraph because we erased the last character.
1353 if (cur.pos() > cur.lastpos())
1354 cur.pos() = cur.lastpos();
1358 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1360 BOOST_ASSERT(cur.size() == old.size());
1361 // Would be wrong to delete anything if we have a selection.
1362 if (cur.selection())
1365 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1366 Paragraph const & oldpar = pars_[old.par()];
1368 // We allow all kinds of "mumbo-jumbo" when freespacing.
1369 if (oldpar.isFreeSpacing())
1372 /* Ok I'll put some comments here about what is missing.
1373 I have fixed BackSpace (and thus Delete) to not delete
1374 double-spaces automagically. I have also changed Cut,
1375 Copy and Paste to hopefully do some sensible things.
1376 There are still some small problems that can lead to
1377 double spaces stored in the document file or space at
1378 the beginning of paragraphs(). This happens if you have
1379 the cursor between to spaces and then save. Or if you
1380 cut and paste and the selection have a space at the
1381 beginning and then save right after the paste. I am
1382 sure none of these are very hard to fix, but I will
1383 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1384 that I can get some feedback. (Lgb)
1387 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1388 // delete the LineSeparator.
1391 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1392 // delete the LineSeparator.
1395 // If the chars around the old cursor were spaces, delete one of them.
1396 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1398 // Only if the cursor has really moved.
1400 && old.pos() < oldpar.size()
1401 && oldpar.isLineSeparator(old.pos())
1402 && oldpar.isLineSeparator(old.pos() - 1)) {
1403 pars_[old.par()].erase(old.pos() - 1);
1404 #ifdef WITH_WARNINGS
1405 #warning This will not work anymore when we have multiple views of the same buffer
1406 // In this case, we will have to correct also the cursors held by
1407 // other bufferviews. It will probably be easier to do that in a more
1408 // automated way in CursorSlice code. (JMarc 26/09/2001)
1410 // correct all cursor parts
1411 fixCursorAfterDelete(cur.top(), old.top());
1412 #ifdef WITH_WARNINGS
1413 #warning DEPM, look here
1415 //fixCursorAfterDelete(cur.anchor(), old.top());
1420 // only do our magic if we changed paragraph
1421 if (old.par() == cur.par())
1424 // don't delete anything if this is the ONLY paragraph!
1425 if (pars_.size() == 1)
1428 // Do not delete empty paragraphs with keepempty set.
1429 if (oldpar.allowEmpty())
1432 // record if we have deleted a paragraph
1433 // we can't possibly have deleted a paragraph before this point
1434 bool deleted = false;
1436 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1437 // ok, we will delete something
1438 CursorSlice tmpcursor;
1442 bool selection_position_was_oldcursor_position =
1443 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1445 // This is a bit of a overkill. We change the old and the cur par
1446 // at max, certainly not everything in between...
1447 recUndo(old.par(), cur.par());
1450 pars_.erase(pars_.begin() + old.par());
1452 // Update cursor par offset if necessary.
1453 // Some 'iterator registration' would be nice that takes care of
1454 // such events. Maybe even signal/slot?
1455 if (cur.par() > old.par())
1457 #ifdef WITH_WARNINGS
1458 #warning DEPM, look here
1460 // if (cur.anchor().par() > old.par())
1461 // --cur.anchor().par();
1463 if (selection_position_was_oldcursor_position) {
1464 // correct selection
1472 if (pars_[old.par()].stripLeadingSpaces())
1479 ParagraphList & LyXText::paragraphs() const
1481 return const_cast<ParagraphList &>(pars_);
1485 void LyXText::recUndo(par_type first, par_type last) const
1487 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1491 void LyXText::recUndo(par_type par) const
1493 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1497 int defaultRowHeight()
1499 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);