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 Counters & counters = textclass.counters();
721 par.params().appendix(par.params().startOfAppendix());
723 par.params().appendix(pars_[pit - 1].params().appendix());
724 if (!par.params().appendix() &&
725 par.params().startOfAppendix()) {
726 par.params().appendix(true);
727 textclass.counters().reset();
730 // Maybe we have to increment the item depth.
731 incrementItemDepth(pars_, pit, 0);
734 // erase what was there before
735 par.params().labelString(string());
737 if (layout->margintype == MARGIN_MANUAL) {
738 if (par.params().labelWidthString().empty())
739 par.setLabelWidthString(layout->labelstring());
741 par.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, par.params().appendix());
750 par.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 // par.params().labelString(
755 // bufparams.user_defined_bullet(par.itemdepth).getText());
756 // for now, use a simple hardcoded label
758 switch (par.itemdepth) {
773 par.params().labelString(itemlabel);
774 } else if (layout->labeltype == LABEL_ENUMERATE) {
775 // Maybe we have to reset the enumeration counter.
776 resetEnumCounterIfNeeded(pars_, pit, 0, counters);
779 // Yes I know this is a really, really! bad solution
781 string enumcounter = "enum";
783 switch (par.itemdepth) {
795 // not a valid enumdepth...
799 counters.step(enumcounter);
801 par.params().labelString(counters.enumLabel(enumcounter));
802 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
803 counters.step("bibitem");
804 int number = counters.value("bibitem");
806 par.bibitem()->setCounter(number);
807 par.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) {
827 #warning replace this code by something that works
828 // This code does not work because we have currently no way to move up
829 // in the hierarchy of insets (JMarc 16/08/2004)
832 /* I think this code is supposed to be useful when one has a caption
833 * in a minipage in a figure inset. We need to go up to be able to see
834 * that the caption should use "Figure" as label
837 Paragraph const * owner = &ownerPar(buf, in);
839 for ( ; tmppit != end; ++tmppit)
840 if (&pars_[tmppit] == owner)
851 if (in->lyxCode() == InsetBase::FLOAT_CODE)
852 type = static_cast<InsetFloat*>(in)->params().type;
853 else if (in->lyxCode() == InsetBase::WRAP_CODE)
854 type = static_cast<InsetWrap*>(in)->params().type;
858 Floating const & fl = textclass.floats().getType(type);
860 counters.step(fl.type());
862 // Doesn't work... yet.
863 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
865 // par->SetLayout(0);
866 // s = layout->labelstring;
867 s = _("Senseless: ");
870 par.params().labelString(s);
876 // Updates all counters.
877 void LyXText::updateCounters()
880 bv()->buffer()->params().getLyXTextClass().counters().reset();
882 bool update_pos = false;
884 par_type end = paragraphs().size();
885 for (par_type pit = 0; pit != end; ++pit) {
886 string const oldLabel = pars_[pit].params().labelString();
889 maxdepth = pars_[pit - 1].getMaxDepthAfter();
891 if (pars_[pit].params().depth() > maxdepth)
892 pars_[pit].params().depth(maxdepth);
894 // setCounter can potentially change the labelString.
895 setCounter(*bv()->buffer(), pit);
896 string const & newLabel = pars_[pit].params().labelString();
897 if (oldLabel != newLabel) {
898 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
899 // << newLabel << endl;
900 redoParagraphInternal(pit);
905 updateParPositions();
909 // this really should just insert the inset and not move the cursor.
910 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
912 BOOST_ASSERT(this == cur.text());
914 cur.paragraph().insertInset(cur.pos(), inset);
919 // needed to insert the selection
920 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
922 par_type pit = cur.par();
923 par_type endpit = cur.par() + 1;
924 pos_type pos = cur.pos();
927 // only to be sure, should not be neccessary
928 cur.clearSelection();
929 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
931 redoParagraphs(cur.par(), endpit);
933 setCursor(cur, cur.par(), pos);
938 // turn double CR to single CR, others are converted into one
939 // blank. Then insertStringAsLines is called
940 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
942 string linestr = str;
943 bool newline_inserted = false;
945 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
946 if (linestr[i] == '\n') {
947 if (newline_inserted) {
948 // we know that \r will be ignored by
949 // insertStringAsLines. Of course, it is a dirty
950 // trick, but it works...
951 linestr[i - 1] = '\r';
955 newline_inserted = true;
957 } else if (IsPrintable(linestr[i])) {
958 newline_inserted = false;
961 insertStringAsLines(cur, linestr);
965 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
966 bool setfont, bool boundary)
969 setCursorIntern(cur, par, pos, setfont, boundary);
970 return deleteEmptyParagraphMechanism(cur, old);
974 void LyXText::setCursor(CursorSlice & cur, par_type par,
975 pos_type pos, bool boundary)
977 BOOST_ASSERT(par != int(paragraphs().size()));
981 cur.boundary() = boundary;
983 // no rows, no fun...
984 if (paragraphs().begin()->rows.empty())
987 // now some strict checking
988 Paragraph & para = getPar(par);
989 Row const & row = *para.getRow(pos);
990 pos_type const end = row.endpos();
992 // None of these should happen, but we're scaredy-cats
994 lyxerr << "dont like -1" << endl;
998 if (pos > para.size()) {
999 lyxerr << "dont like 1, pos: " << pos
1000 << " size: " << para.size()
1001 << " row.pos():" << row.pos()
1002 << " par: " << par << endl;
1003 BOOST_ASSERT(false);
1007 lyxerr << "dont like 2, pos: " << pos
1008 << " size: " << para.size()
1009 << " row.pos():" << row.pos()
1010 << " par: " << par << endl;
1011 // This shouldn't happen.
1012 BOOST_ASSERT(false);
1015 if (pos < row.pos()) {
1016 lyxerr << "dont like 3 please report pos:" << pos
1017 << " size: " << para.size()
1018 << " row.pos():" << row.pos()
1019 << " par: " << par << endl;
1020 BOOST_ASSERT(false);
1025 void LyXText::setCursorIntern(LCursor & cur,
1026 par_type par, pos_type pos, bool setfont, bool boundary)
1028 setCursor(cur.top(), par, pos, boundary);
1029 cur.x_target() = cursorX(cur.top());
1031 setCurrentFont(cur);
1035 void LyXText::setCurrentFont(LCursor & cur)
1037 BOOST_ASSERT(this == cur.text());
1038 pos_type pos = cur.pos();
1039 Paragraph & par = cur.paragraph();
1041 if (cur.boundary() && pos > 0)
1045 if (pos == cur.lastpos())
1047 else // potentional bug... BUG (Lgb)
1048 if (par.isSeparator(pos)) {
1049 if (pos > cur.textRow().pos() &&
1050 bidi.level(pos) % 2 ==
1051 bidi.level(pos - 1) % 2)
1053 else if (pos + 1 < cur.lastpos())
1058 BufferParams const & bufparams = cur.buffer().params();
1059 current_font = par.getFontSettings(bufparams, pos);
1060 real_current_font = getFont(par, pos);
1062 if (cur.pos() == cur.lastpos()
1063 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1064 && !cur.boundary()) {
1065 Language const * lang = par.getParLanguage(bufparams);
1066 current_font.setLanguage(lang);
1067 current_font.setNumber(LyXFont::OFF);
1068 real_current_font.setLanguage(lang);
1069 real_current_font.setNumber(LyXFont::OFF);
1074 // x is an absolute screen coord
1075 // returns the column near the specified x-coordinate of the row
1076 // x is set to the real beginning of this column
1077 pos_type LyXText::getColumnNearX(par_type const pit,
1078 Row const & row, int & x, bool & boundary) const
1081 RowMetrics const r = computeRowMetrics(pit, row);
1082 Paragraph const & par = pars_[pit];
1084 pos_type vc = row.pos();
1085 pos_type end = row.endpos();
1087 LyXLayout_ptr const & layout = par.layout();
1089 bool left_side = false;
1091 pos_type body_pos = par.beginOfBody();
1094 double last_tmpx = tmpx;
1097 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1100 // check for empty row
1102 x = int(tmpx) + xo_;
1106 while (vc < end && tmpx <= x) {
1107 c = bidi.vis2log(vc);
1109 if (body_pos > 0 && c == body_pos - 1) {
1110 tmpx += r.label_hfill +
1111 font_metrics::width(layout->labelsep, getLabelFont(par));
1112 if (par.isLineSeparator(body_pos - 1))
1113 tmpx -= singleWidth(par, body_pos - 1);
1116 if (hfillExpansion(par, row, c)) {
1117 tmpx += singleWidth(par, c);
1121 tmpx += r.label_hfill;
1122 } else if (par.isSeparator(c)) {
1123 tmpx += singleWidth(par, c);
1125 tmpx += r.separator;
1127 tmpx += singleWidth(par, c);
1132 if ((tmpx + last_tmpx) / 2 > x) {
1137 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1140 // This (rtl_support test) is not needed, but gives
1141 // some speedup if rtl_support == false
1142 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1144 // If lastrow is false, we don't need to compute
1145 // the value of rtl.
1146 bool const rtl = lastrow ? isRTL(par) : false;
1148 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1149 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1151 else if (vc == row.pos()) {
1152 c = bidi.vis2log(vc);
1153 if (bidi.level(c) % 2 == 1)
1156 c = bidi.vis2log(vc - 1);
1157 bool const rtl = (bidi.level(c) % 2 == 1);
1158 if (left_side == rtl) {
1160 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1164 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1165 if (bidi.level(end -1) % 2 == 0)
1166 tmpx -= singleWidth(par, end - 1);
1168 tmpx += singleWidth(par, end - 1);
1172 x = int(tmpx) + xo_;
1173 return c - row.pos();
1177 // y is relative to this LyXText's top
1178 // this is only used in the two functions below
1179 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1181 BOOST_ASSERT(!paragraphs().empty());
1182 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1183 par_type const pend = paragraphs().size() - 1;
1185 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1188 RowList::iterator rit = pars_[pit].rows.end();
1189 RowList::iterator const rbegin = pars_[pit].rows.begin();
1192 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1198 // x,y are absolute coordinates
1199 // sets cursor only within this LyXText
1200 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1205 Row const & row = getRowNearY(y, pit);
1206 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1207 << row.pos() << endl;
1209 int xx = x + xo_; // getRowNearX get absolute x coords
1210 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1211 setCursor(cur, pit, pos, true, bound);
1215 // x,y are absolute screen coordinates
1216 // sets cursor recursively descending into nested editable insets
1217 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1220 Row const & row = getRowNearY(y - yo_, pit);
1223 int xx = x; // is modified by getColumnNearX
1224 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1227 cur.boundary() = bound;
1229 // try to descend into nested insets
1230 InsetBase * inset = checkInsetHit(x, y);
1231 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1235 // This should be just before or just behind the
1236 // cursor position set above.
1237 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1238 || inset == pars_[pit].getInset(pos));
1239 // Make sure the cursor points to the position before
1241 if (inset == pars_[pit].getInset(pos - 1))
1243 return inset->editXY(cur, x, y);
1247 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1249 if (cur.selection())
1251 if (cur.pos() == cur.lastpos())
1253 InsetBase * inset = cur.nextInset();
1254 if (!isHighlyEditableInset(inset))
1256 inset->edit(cur, front);
1261 void LyXText::cursorLeft(LCursor & cur)
1263 if (cur.pos() != 0) {
1264 bool boundary = cur.boundary();
1265 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1266 if (!checkAndActivateInset(cur, false)) {
1267 if (false && !boundary &&
1268 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1269 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1274 if (cur.par() != 0) {
1275 // steps into the paragraph above
1276 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1281 void LyXText::cursorRight(LCursor & cur)
1283 if (false && cur.boundary()) {
1284 setCursor(cur, cur.par(), cur.pos(), true, false);
1288 if (cur.pos() != cur.lastpos()) {
1289 if (!checkAndActivateInset(cur, true)) {
1290 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1291 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1293 setCursor(cur, cur.par(), cur.pos(), true, true);
1298 if (cur.par() != cur.lastpar())
1299 setCursor(cur, cur.par() + 1, 0);
1303 void LyXText::cursorUp(LCursor & cur)
1305 Row const & row = cur.textRow();
1306 int x = cur.x_target();
1307 int y = cursorY(cur.top()) - row.baseline() - 1;
1308 setCursorFromCoordinates(cur, x, y);
1310 if (!cur.selection()) {
1311 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1312 if (inset_hit && isHighlyEditableInset(inset_hit))
1313 inset_hit->editXY(cur, cur.x_target(), y);
1318 void LyXText::cursorDown(LCursor & cur)
1320 Row const & row = cur.textRow();
1321 int x = cur.x_target();
1322 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1323 setCursorFromCoordinates(cur, x, y);
1325 if (!cur.selection()) {
1326 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1327 if (inset_hit && isHighlyEditableInset(inset_hit))
1328 inset_hit->editXY(cur, cur.x_target(), y);
1333 void LyXText::cursorUpParagraph(LCursor & cur)
1336 setCursor(cur, cur.par(), 0);
1337 else if (cur.par() != 0)
1338 setCursor(cur, cur.par() - 1, 0);
1342 void LyXText::cursorDownParagraph(LCursor & cur)
1344 if (cur.par() != cur.lastpar())
1345 setCursor(cur, cur.par() + 1, 0);
1347 setCursor(cur, cur.par(), cur.lastpos());
1351 // fix the cursor `cur' after a characters has been deleted at `where'
1352 // position. Called by deleteEmptyParagraphMechanism
1353 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1355 // do notheing if cursor is not in the paragraph where the
1356 // deletion occured,
1357 if (cur.par() != where.par())
1360 // if cursor position is after the deletion place update it
1361 if (cur.pos() > where.pos())
1364 // check also if we don't want to set the cursor on a spot behind the
1365 // pagragraph because we erased the last character.
1366 if (cur.pos() > cur.lastpos())
1367 cur.pos() = cur.lastpos();
1371 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1373 BOOST_ASSERT(cur.size() == old.size());
1374 // Would be wrong to delete anything if we have a selection.
1375 if (cur.selection())
1378 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1379 Paragraph const & oldpar = pars_[old.par()];
1381 // We allow all kinds of "mumbo-jumbo" when freespacing.
1382 if (oldpar.isFreeSpacing())
1385 /* Ok I'll put some comments here about what is missing.
1386 I have fixed BackSpace (and thus Delete) to not delete
1387 double-spaces automagically. I have also changed Cut,
1388 Copy and Paste to hopefully do some sensible things.
1389 There are still some small problems that can lead to
1390 double spaces stored in the document file or space at
1391 the beginning of paragraphs(). This happens if you have
1392 the cursor between to spaces and then save. Or if you
1393 cut and paste and the selection have a space at the
1394 beginning and then save right after the paste. I am
1395 sure none of these are very hard to fix, but I will
1396 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1397 that I can get some feedback. (Lgb)
1400 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1401 // delete the LineSeparator.
1404 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1405 // delete the LineSeparator.
1408 // If the chars around the old cursor were spaces, delete one of them.
1409 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1411 // Only if the cursor has really moved.
1413 && old.pos() < oldpar.size()
1414 && oldpar.isLineSeparator(old.pos())
1415 && oldpar.isLineSeparator(old.pos() - 1)) {
1416 pars_[old.par()].erase(old.pos() - 1);
1417 #ifdef WITH_WARNINGS
1418 #warning This will not work anymore when we have multiple views of the same buffer
1419 // In this case, we will have to correct also the cursors held by
1420 // other bufferviews. It will probably be easier to do that in a more
1421 // automated way in CursorSlice code. (JMarc 26/09/2001)
1423 // correct all cursor parts
1424 fixCursorAfterDelete(cur.top(), old.top());
1425 #ifdef WITH_WARNINGS
1426 #warning DEPM, look here
1428 //fixCursorAfterDelete(cur.anchor(), old.top());
1433 // only do our magic if we changed paragraph
1434 if (old.par() == cur.par())
1437 // don't delete anything if this is the ONLY paragraph!
1438 if (pars_.size() == 1)
1441 // Do not delete empty paragraphs with keepempty set.
1442 if (oldpar.allowEmpty())
1445 // record if we have deleted a paragraph
1446 // we can't possibly have deleted a paragraph before this point
1447 bool deleted = false;
1449 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1450 // ok, we will delete something
1451 CursorSlice tmpcursor;
1455 bool selection_position_was_oldcursor_position =
1456 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1458 // This is a bit of a overkill. We change the old and the cur par
1459 // at max, certainly not everything in between...
1460 recUndo(old.par(), cur.par());
1463 pars_.erase(pars_.begin() + old.par());
1465 // Update cursor par offset if necessary.
1466 // Some 'iterator registration' would be nice that takes care of
1467 // such events. Maybe even signal/slot?
1468 if (cur.par() > old.par())
1470 #ifdef WITH_WARNINGS
1471 #warning DEPM, look here
1473 // if (cur.anchor().par() > old.par())
1474 // --cur.anchor().par();
1476 if (selection_position_was_oldcursor_position) {
1477 // correct selection
1485 if (pars_[old.par()].stripLeadingSpaces())
1492 ParagraphList & LyXText::paragraphs() const
1494 return const_cast<ParagraphList &>(pars_);
1498 void LyXText::recUndo(par_type first, par_type last) const
1500 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1504 void LyXText::recUndo(par_type par) const
1506 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1510 int defaultRowHeight()
1512 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);