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 sould use "Figure" as label
837 Paragraph const * owner = &ownerPar(buf, in);
839 for ( ; tmppit != end; ++tmppit)
840 if (&pars_[tmppit] == owner)
849 if (in->lyxCode() == InsetBase::FLOAT_CODE)
850 type = static_cast<InsetFloat*>(in)->params().type;
851 else if (in->lyxCode() == InsetBase::WRAP_CODE)
852 type = static_cast<InsetWrap*>(in)->params().type;
856 Floating const & fl = textclass.floats().getType(type);
858 counters.step(fl.type());
860 // Doesn't work... yet.
861 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
863 // par->SetLayout(0);
864 // s = layout->labelstring;
865 s = _("Senseless: ");
868 par.params().labelString(s);
874 // Updates all counters.
875 void LyXText::updateCounters()
878 bv()->buffer()->params().getLyXTextClass().counters().reset();
880 bool update_pos = false;
882 par_type end = paragraphs().size();
883 for (par_type pit = 0; pit != end; ++pit) {
884 string const oldLabel = pars_[pit].params().labelString();
887 maxdepth = pars_[pit - 1].getMaxDepthAfter();
889 if (pars_[pit].params().depth() > maxdepth)
890 pars_[pit].params().depth(maxdepth);
892 // setCounter can potentially change the labelString.
893 setCounter(*bv()->buffer(), pit);
894 string const & newLabel = pars_[pit].params().labelString();
895 if (oldLabel != newLabel) {
896 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
897 // << newLabel << endl;
898 redoParagraphInternal(pit);
903 updateParPositions();
907 // this really should just insert the inset and not move the cursor.
908 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
910 BOOST_ASSERT(this == cur.text());
912 cur.paragraph().insertInset(cur.pos(), inset);
917 // needed to insert the selection
918 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
920 par_type pit = cur.par();
921 par_type endpit = cur.par() + 1;
922 pos_type pos = cur.pos();
925 // only to be sure, should not be neccessary
926 cur.clearSelection();
927 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
929 redoParagraphs(cur.par(), endpit);
931 setCursor(cur, cur.par(), pos);
936 // turn double CR to single CR, others are converted into one
937 // blank. Then insertStringAsLines is called
938 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
940 string linestr = str;
941 bool newline_inserted = false;
943 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
944 if (linestr[i] == '\n') {
945 if (newline_inserted) {
946 // we know that \r will be ignored by
947 // insertStringAsLines. Of course, it is a dirty
948 // trick, but it works...
949 linestr[i - 1] = '\r';
953 newline_inserted = true;
955 } else if (IsPrintable(linestr[i])) {
956 newline_inserted = false;
959 insertStringAsLines(cur, linestr);
963 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
964 bool setfont, bool boundary)
967 setCursorIntern(cur, par, pos, setfont, boundary);
968 return deleteEmptyParagraphMechanism(cur, old);
972 void LyXText::setCursor(CursorSlice & cur, par_type par,
973 pos_type pos, bool boundary)
975 BOOST_ASSERT(par != int(paragraphs().size()));
979 cur.boundary() = boundary;
981 // no rows, no fun...
982 if (paragraphs().begin()->rows.empty())
985 // now some strict checking
986 Paragraph & para = getPar(par);
987 Row const & row = *para.getRow(pos);
988 pos_type const end = row.endpos();
990 // None of these should happen, but we're scaredy-cats
992 lyxerr << "dont like -1" << endl;
996 if (pos > para.size()) {
997 lyxerr << "dont like 1, pos: " << pos
998 << " size: " << para.size()
999 << " row.pos():" << row.pos()
1000 << " par: " << par << endl;
1001 BOOST_ASSERT(false);
1005 lyxerr << "dont like 2, pos: " << pos
1006 << " size: " << para.size()
1007 << " row.pos():" << row.pos()
1008 << " par: " << par << endl;
1009 // This shouldn't happen.
1010 BOOST_ASSERT(false);
1013 if (pos < row.pos()) {
1014 lyxerr << "dont like 3 please report pos:" << pos
1015 << " size: " << para.size()
1016 << " row.pos():" << row.pos()
1017 << " par: " << par << endl;
1018 BOOST_ASSERT(false);
1023 void LyXText::setCursorIntern(LCursor & cur,
1024 par_type par, pos_type pos, bool setfont, bool boundary)
1026 setCursor(cur.top(), par, pos, boundary);
1027 cur.x_target() = cursorX(cur.top());
1029 setCurrentFont(cur);
1033 void LyXText::setCurrentFont(LCursor & cur)
1035 BOOST_ASSERT(this == cur.text());
1036 pos_type pos = cur.pos();
1037 Paragraph & par = cur.paragraph();
1039 if (cur.boundary() && pos > 0)
1043 if (pos == cur.lastpos())
1045 else // potentional bug... BUG (Lgb)
1046 if (par.isSeparator(pos)) {
1047 if (pos > cur.textRow().pos() &&
1048 bidi.level(pos) % 2 ==
1049 bidi.level(pos - 1) % 2)
1051 else if (pos + 1 < cur.lastpos())
1056 BufferParams const & bufparams = cur.buffer().params();
1057 current_font = par.getFontSettings(bufparams, pos);
1058 real_current_font = getFont(par, pos);
1060 if (cur.pos() == cur.lastpos()
1061 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1062 && !cur.boundary()) {
1063 Language const * lang = par.getParLanguage(bufparams);
1064 current_font.setLanguage(lang);
1065 current_font.setNumber(LyXFont::OFF);
1066 real_current_font.setLanguage(lang);
1067 real_current_font.setNumber(LyXFont::OFF);
1072 // x is an absolute screen coord
1073 // returns the column near the specified x-coordinate of the row
1074 // x is set to the real beginning of this column
1075 pos_type LyXText::getColumnNearX(par_type const pit,
1076 Row const & row, int & x, bool & boundary) const
1079 RowMetrics const r = computeRowMetrics(pit, row);
1080 Paragraph const & par = pars_[pit];
1082 pos_type vc = row.pos();
1083 pos_type end = row.endpos();
1085 LyXLayout_ptr const & layout = par.layout();
1087 bool left_side = false;
1089 pos_type body_pos = par.beginOfBody();
1092 double last_tmpx = tmpx;
1095 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1098 // check for empty row
1100 x = int(tmpx) + xo_;
1104 while (vc < end && tmpx <= x) {
1105 c = bidi.vis2log(vc);
1107 if (body_pos > 0 && c == body_pos - 1) {
1108 tmpx += r.label_hfill +
1109 font_metrics::width(layout->labelsep, getLabelFont(par));
1110 if (par.isLineSeparator(body_pos - 1))
1111 tmpx -= singleWidth(par, body_pos - 1);
1114 if (hfillExpansion(par, row, c)) {
1115 tmpx += singleWidth(par, c);
1119 tmpx += r.label_hfill;
1120 } else if (par.isSeparator(c)) {
1121 tmpx += singleWidth(par, c);
1123 tmpx += r.separator;
1125 tmpx += singleWidth(par, c);
1130 if ((tmpx + last_tmpx) / 2 > x) {
1135 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1138 // This (rtl_support test) is not needed, but gives
1139 // some speedup if rtl_support == false
1140 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1142 // If lastrow is false, we don't need to compute
1143 // the value of rtl.
1144 bool const rtl = lastrow ? isRTL(par) : false;
1146 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1147 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1149 else if (vc == row.pos()) {
1150 c = bidi.vis2log(vc);
1151 if (bidi.level(c) % 2 == 1)
1154 c = bidi.vis2log(vc - 1);
1155 bool const rtl = (bidi.level(c) % 2 == 1);
1156 if (left_side == rtl) {
1158 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1162 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1163 if (bidi.level(end -1) % 2 == 0)
1164 tmpx -= singleWidth(par, end - 1);
1166 tmpx += singleWidth(par, end - 1);
1170 x = int(tmpx) + xo_;
1171 return c - row.pos();
1175 // y is relative to this LyXText's top
1176 // this is only used in the two functions below
1177 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1179 BOOST_ASSERT(!paragraphs().empty());
1180 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1181 par_type const pend = paragraphs().size() - 1;
1183 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1186 RowList::iterator rit = pars_[pit].rows.end();
1187 RowList::iterator const rbegin = pars_[pit].rows.begin();
1190 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1196 // x,y are absolute coordinates
1197 // sets cursor only within this LyXText
1198 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1203 Row const & row = getRowNearY(y, pit);
1204 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1205 << row.pos() << endl;
1207 int xx = x + xo_; // getRowNearX get absolute x coords
1208 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1209 setCursor(cur, pit, pos, true, bound);
1213 // x,y are absolute screen coordinates
1214 // sets cursor recursively descending into nested editable insets
1215 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1218 Row const & row = getRowNearY(y - yo_, pit);
1221 int xx = x; // is modified by getColumnNearX
1222 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1225 cur.boundary() = bound;
1227 // try to descend into nested insets
1228 InsetBase * inset = checkInsetHit(x, y);
1229 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1233 // This should be just before or just behind the
1234 // cursor position set above.
1235 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1236 || inset == pars_[pit].getInset(pos));
1237 // Make sure the cursor points to the position before
1239 if (inset == pars_[pit].getInset(pos - 1))
1241 return inset->editXY(cur, x, y);
1245 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1247 if (cur.selection())
1249 if (cur.pos() == cur.lastpos())
1251 InsetBase * inset = cur.nextInset();
1252 if (!isHighlyEditableInset(inset))
1254 inset->edit(cur, front);
1259 void LyXText::cursorLeft(LCursor & cur)
1261 if (cur.pos() != 0) {
1262 bool boundary = cur.boundary();
1263 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1264 if (!checkAndActivateInset(cur, false)) {
1265 if (false && !boundary &&
1266 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1267 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1272 if (cur.par() != 0) {
1273 // steps into the paragraph above
1274 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1279 void LyXText::cursorRight(LCursor & cur)
1281 if (false && cur.boundary()) {
1282 setCursor(cur, cur.par(), cur.pos(), true, false);
1286 if (cur.pos() != cur.lastpos()) {
1287 if (!checkAndActivateInset(cur, true)) {
1288 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1289 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1291 setCursor(cur, cur.par(), cur.pos(), true, true);
1296 if (cur.par() != cur.lastpar())
1297 setCursor(cur, cur.par() + 1, 0);
1301 void LyXText::cursorUp(LCursor & cur)
1303 Row const & row = cur.textRow();
1304 int x = cur.x_target();
1305 int y = cursorY(cur.top()) - row.baseline() - 1;
1306 setCursorFromCoordinates(cur, x, y);
1308 if (!cur.selection()) {
1309 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1310 if (inset_hit && isHighlyEditableInset(inset_hit))
1311 inset_hit->editXY(cur, cur.x_target(), y);
1316 void LyXText::cursorDown(LCursor & cur)
1318 Row const & row = cur.textRow();
1319 int x = cur.x_target();
1320 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1321 setCursorFromCoordinates(cur, x, y);
1323 if (!cur.selection()) {
1324 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1325 if (inset_hit && isHighlyEditableInset(inset_hit))
1326 inset_hit->editXY(cur, cur.x_target(), y);
1331 void LyXText::cursorUpParagraph(LCursor & cur)
1334 setCursor(cur, cur.par(), 0);
1335 else if (cur.par() != 0)
1336 setCursor(cur, cur.par() - 1, 0);
1340 void LyXText::cursorDownParagraph(LCursor & cur)
1342 if (cur.par() != cur.lastpar())
1343 setCursor(cur, cur.par() + 1, 0);
1345 setCursor(cur, cur.par(), cur.lastpos());
1349 // fix the cursor `cur' after a characters has been deleted at `where'
1350 // position. Called by deleteEmptyParagraphMechanism
1351 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1353 // do notheing if cursor is not in the paragraph where the
1354 // deletion occured,
1355 if (cur.par() != where.par())
1358 // if cursor position is after the deletion place update it
1359 if (cur.pos() > where.pos())
1362 // check also if we don't want to set the cursor on a spot behind the
1363 // pagragraph because we erased the last character.
1364 if (cur.pos() > cur.lastpos())
1365 cur.pos() = cur.lastpos();
1369 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1371 BOOST_ASSERT(cur.size() == old.size());
1372 // Would be wrong to delete anything if we have a selection.
1373 if (cur.selection())
1376 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1377 Paragraph const & oldpar = pars_[old.par()];
1379 // We allow all kinds of "mumbo-jumbo" when freespacing.
1380 if (oldpar.isFreeSpacing())
1383 /* Ok I'll put some comments here about what is missing.
1384 I have fixed BackSpace (and thus Delete) to not delete
1385 double-spaces automagically. I have also changed Cut,
1386 Copy and Paste to hopefully do some sensible things.
1387 There are still some small problems that can lead to
1388 double spaces stored in the document file or space at
1389 the beginning of paragraphs(). This happens if you have
1390 the cursor between to spaces and then save. Or if you
1391 cut and paste and the selection have a space at the
1392 beginning and then save right after the paste. I am
1393 sure none of these are very hard to fix, but I will
1394 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1395 that I can get some feedback. (Lgb)
1398 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1399 // delete the LineSeparator.
1402 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1403 // delete the LineSeparator.
1406 // If the chars around the old cursor were spaces, delete one of them.
1407 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1409 // Only if the cursor has really moved.
1411 && old.pos() < oldpar.size()
1412 && oldpar.isLineSeparator(old.pos())
1413 && oldpar.isLineSeparator(old.pos() - 1)) {
1414 pars_[old.par()].erase(old.pos() - 1);
1415 #ifdef WITH_WARNINGS
1416 #warning This will not work anymore when we have multiple views of the same buffer
1417 // In this case, we will have to correct also the cursors held by
1418 // other bufferviews. It will probably be easier to do that in a more
1419 // automated way in CursorSlice code. (JMarc 26/09/2001)
1421 // correct all cursor parts
1422 fixCursorAfterDelete(cur.top(), old.top());
1423 #ifdef WITH_WARNINGS
1424 #warning DEPM, look here
1426 //fixCursorAfterDelete(cur.anchor(), old.top());
1431 // only do our magic if we changed paragraph
1432 if (old.par() == cur.par())
1435 // don't delete anything if this is the ONLY paragraph!
1436 if (pars_.size() == 1)
1439 // Do not delete empty paragraphs with keepempty set.
1440 if (oldpar.allowEmpty())
1443 // record if we have deleted a paragraph
1444 // we can't possibly have deleted a paragraph before this point
1445 bool deleted = false;
1447 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1448 // ok, we will delete something
1449 CursorSlice tmpcursor;
1453 bool selection_position_was_oldcursor_position =
1454 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1456 // This is a bit of a overkill. We change the old and the cur par
1457 // at max, certainly not everything in between...
1458 recUndo(old.par(), cur.par());
1461 pars_.erase(pars_.begin() + old.par());
1463 // Update cursor par offset if necessary.
1464 // Some 'iterator registration' would be nice that takes care of
1465 // such events. Maybe even signal/slot?
1466 if (cur.par() > old.par())
1468 #ifdef WITH_WARNINGS
1469 #warning DEPM, look here
1471 // if (cur.anchor().par() > old.par())
1472 // --cur.anchor().par();
1474 if (selection_position_was_oldcursor_position) {
1475 // correct selection
1483 if (pars_[old.par()].stripLeadingSpaces())
1490 ParagraphList & LyXText::paragraphs() const
1492 return const_cast<ParagraphList &>(pars_);
1496 void LyXText::recUndo(par_type first, par_type last) const
1498 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1502 void LyXText::recUndo(par_type par) const
1504 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1508 int defaultRowHeight()
1510 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);