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(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(par_type const pit, pos_type const pos) const
154 BOOST_ASSERT(pos >= 0);
156 Paragraph const & par = pars_[pit];
157 LyXLayout_ptr const & layout = par.layout();
161 BufferParams const & params = bv()->buffer()->params();
162 pos_type const body_pos = par.beginOfBody();
164 // We specialize the 95% common case:
165 if (!par.getDepth()) {
166 LyXFont f = par.getFontSettings(params, pos);
169 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
170 return f.realize(layout->reslabelfont);
172 return f.realize(layout->resfont);
175 // The uncommon case need not be optimized as much
178 layoutfont = layout->labelfont;
180 layoutfont = layout->font;
182 LyXFont font = par.getFontSettings(params, pos);
183 font.realize(layoutfont);
188 // Realize with the fonts of lesser depth.
189 //font.realize(outerFont(pit, paragraphs()));
190 font.realize(defaultfont_);
196 LyXFont LyXText::getLayoutFont(par_type const pit) const
198 LyXLayout_ptr const & layout = pars_[pit].layout();
200 if (!pars_[pit].getDepth())
201 return layout->resfont;
203 LyXFont font = layout->font;
204 // Realize with the fonts of lesser depth.
205 //font.realize(outerFont(pit, paragraphs()));
206 font.realize(defaultfont_);
212 LyXFont LyXText::getLabelFont(par_type pit) const
214 LyXLayout_ptr const & layout = pars_[pit].layout();
216 if (!pars_[pit].getDepth())
217 return layout->reslabelfont;
219 LyXFont font = layout->labelfont;
220 // Realize with the fonts of lesser depth.
221 font.realize(outerFont(pit, paragraphs()));
222 font.realize(defaultfont_);
228 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
231 LyXLayout_ptr const & layout = pars_[pit].layout();
233 // Get concrete layout font to reduce against
236 if (pos < pars_[pit].beginOfBody())
237 layoutfont = layout->labelfont;
239 layoutfont = layout->font;
241 // Realize against environment font information
242 if (pars_[pit].getDepth()) {
244 while (!layoutfont.resolved() &&
245 tp != par_type(paragraphs().size()) &&
246 pars_[tp].getDepth()) {
247 tp = outerHook(tp, paragraphs());
248 if (tp != par_type(paragraphs().size()))
249 layoutfont.realize(pars_[tp].layout()->font);
253 layoutfont.realize(defaultfont_);
255 // Now, reduce font against full layout font
256 font.reduce(layoutfont);
258 pars_[pit].setFont(pos, font);
263 // Asger is not sure we want to do this...
264 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
267 LyXLayout_ptr const & layout = par.layout();
268 pos_type const psize = par.size();
271 for (pos_type pos = 0; pos < psize; ++pos) {
272 if (pos < par.beginOfBody())
273 layoutfont = layout->labelfont;
275 layoutfont = layout->font;
277 LyXFont tmpfont = par.getFontSettings(params, pos);
278 tmpfont.reduce(layoutfont);
279 par.setFont(pos, tmpfont);
284 // return past-the-last paragraph influenced by a layout change on pit
285 par_type LyXText::undoSpan(par_type pit)
287 par_type end = paragraphs().size();
288 par_type nextpit = pit + 1;
291 //because of parindents
292 if (!pars_[pit].getDepth())
293 return boost::next(nextpit);
294 //because of depth constrains
295 for (; nextpit != end; ++pit, ++nextpit) {
296 if (!pars_[pit].getDepth())
303 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
305 BOOST_ASSERT(start != end);
306 par_type undopit = undoSpan(end - 1);
307 recUndo(start, undopit - 1);
309 BufferParams const & bufparams = bv()->buffer()->params();
310 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
312 for (par_type pit = start; pit != end; ++pit) {
313 pars_[pit].applyLayout(lyxlayout);
314 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
315 if (lyxlayout->margintype == MARGIN_MANUAL)
316 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
323 // set layout over selection and make a total rebreak of those paragraphs
324 void LyXText::setLayout(LCursor & cur, string const & layout)
326 BOOST_ASSERT(this == cur.text());
327 // special handling of new environment insets
328 BufferView & bv = cur.bv();
329 BufferParams const & params = bv.buffer()->params();
330 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
331 if (lyxlayout->is_environment) {
332 // move everything in a new environment inset
333 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
334 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
335 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
336 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
337 InsetBase * inset = new InsetEnvironment(params, layout);
338 insertInset(cur, inset);
339 //inset->edit(cur, true);
340 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
344 par_type start = cur.selBegin().par();
345 par_type end = cur.selEnd().par() + 1;
346 par_type endpit = setLayout(start, end, layout);
347 redoParagraphs(start, endpit);
355 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
357 if (!cur.selection()) {
361 beg = cur.selBegin().par();
362 end = cur.selEnd().par() + 1;
367 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
368 Paragraph const & par, int max_depth)
370 if (par.layout()->labeltype == LABEL_BIBLIO)
372 int const depth = par.params().depth();
373 if (type == LyXText::INC_DEPTH && depth < max_depth)
375 if (type == LyXText::DEC_DEPTH && depth > 0)
384 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
386 BOOST_ASSERT(this == cur.text());
388 getSelectionSpan(cur, beg, end);
391 max_depth = pars_[beg - 1].getMaxDepthAfter();
393 for (par_type pit = beg; pit != end; ++pit) {
394 if (::changeDepthAllowed(type, pars_[pit], max_depth))
396 max_depth = pars_[pit].getMaxDepthAfter();
402 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
404 BOOST_ASSERT(this == cur.text());
406 getSelectionSpan(cur, beg, end);
407 recordUndoSelection(cur);
411 max_depth = pars_[beg - 1].getMaxDepthAfter();
413 for (par_type pit = beg; pit != end; ++pit) {
414 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
415 int const depth = pars_[pit].params().depth();
416 if (type == INC_DEPTH)
417 pars_[pit].params().depth(depth + 1);
419 pars_[pit].params().depth(depth - 1);
421 max_depth = pars_[pit].getMaxDepthAfter();
423 // this handles the counter labels, and also fixes up
424 // depth values for follow-on (child) paragraphs
429 // set font over selection
430 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
432 BOOST_ASSERT(this == cur.text());
433 // if there is no selection just set the current_font
434 if (!cur.selection()) {
435 // Determine basis font
437 par_type pit = cur.par();
438 if (cur.pos() < pars_[pit].beginOfBody())
439 layoutfont = getLabelFont(pit);
441 layoutfont = getLayoutFont(pit);
443 // Update current font
444 real_current_font.update(font,
445 cur.buffer().params().language,
448 // Reduce to implicit settings
449 current_font = real_current_font;
450 current_font.reduce(layoutfont);
451 // And resolve it completely
452 real_current_font.realize(layoutfont);
457 // Ok, we have a selection.
458 recordUndoSelection(cur);
460 par_type const beg = cur.selBegin().par();
461 par_type const end = cur.selEnd().par();
463 DocIterator pos = cur.selectionBegin();
464 DocIterator posend = cur.selectionEnd();
466 lyxerr[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
469 BufferParams const & params = cur.buffer().params();
471 // Don't use forwardChar here as posend might have
472 // pos() == lastpos() and forwardChar would miss it.
473 for (; pos != posend; pos.forwardPos()) {
474 if (pos.pos() != pos.lastpos()) {
475 LyXFont f = getFont(pos.par(), pos.pos());
476 f.update(font, params.language, toggleall);
477 setCharFont(pos.par(), pos.pos(), f);
481 redoParagraphs(beg, end + 1);
485 // the cursor set functions have a special mechanism. When they
486 // realize you left an empty paragraph, they will delete it.
488 void LyXText::cursorHome(LCursor & cur)
490 BOOST_ASSERT(this == cur.text());
491 setCursor(cur, cur.par(), cur.textRow().pos());
495 void LyXText::cursorEnd(LCursor & cur)
497 BOOST_ASSERT(this == cur.text());
498 // if not on the last row of the par, put the cursor before
500 pos_type const end = cur.textRow().endpos();
501 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
505 void LyXText::cursorTop(LCursor & cur)
507 BOOST_ASSERT(this == cur.text());
508 setCursor(cur, 0, 0);
512 void LyXText::cursorBottom(LCursor & cur)
514 BOOST_ASSERT(this == cur.text());
515 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
519 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
521 BOOST_ASSERT(this == cur.text());
522 // If the mask is completely neutral, tell user
523 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
524 // Could only happen with user style
525 cur.message(_("No font change defined. "
526 "Use Character under the Layout menu to define font change."));
530 // Try implicit word selection
531 // If there is a change in the language the implicit word selection
533 CursorSlice resetCursor = cur.top();
534 bool implicitSelection =
535 font.language() == ignore_language
536 && font.number() == LyXFont::IGNORE
537 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
540 setFont(cur, font, toggleall);
542 // Implicit selections are cleared afterwards
543 // and cursor is set to the original position.
544 if (implicitSelection) {
545 cur.clearSelection();
546 cur.top() = resetCursor;
552 string LyXText::getStringToIndex(LCursor & cur)
554 BOOST_ASSERT(this == cur.text());
555 // Try implicit word selection
556 // If there is a change in the language the implicit word selection
558 CursorSlice const reset_cursor = cur.top();
559 bool const implicitSelection =
560 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
563 if (!cur.selection())
564 cur.message(_("Nothing to index!"));
565 else if (cur.selBegin().par() != cur.selEnd().par())
566 cur.message(_("Cannot index more than one paragraph!"));
568 idxstring = cur.selectionAsString(false);
570 // Reset cursors to their original position.
571 cur.top() = reset_cursor;
574 // Clear the implicit selection.
575 if (implicitSelection)
576 cur.clearSelection();
582 void LyXText::setParagraph(LCursor & cur,
583 Spacing const & spacing, LyXAlignment align,
584 string const & labelwidthstring, bool noindent)
586 BOOST_ASSERT(cur.text());
587 // make sure that the depth behind the selection are restored, too
588 par_type undopit = undoSpan(cur.selEnd().par());
589 recUndo(cur.selBegin().par(), undopit - 1);
591 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
593 Paragraph & par = pars_[pit];
594 ParagraphParameters & params = par.params();
595 params.spacing(spacing);
597 // does the layout allow the new alignment?
598 LyXLayout_ptr const & layout = par.layout();
600 if (align == LYX_ALIGN_LAYOUT)
601 align = layout->align;
602 if (align & layout->alignpossible) {
603 if (align == layout->align)
604 params.align(LYX_ALIGN_LAYOUT);
608 par.setLabelWidthString(labelwidthstring);
609 params.noindent(noindent);
612 redoParagraphs(cur.selBegin().par(), undopit);
616 string expandLabel(LyXTextClass const & textclass,
617 LyXLayout_ptr const & layout, bool appendix)
619 string fmt = appendix ?
620 layout->labelstring_appendix() : layout->labelstring();
622 // handle 'inherited level parts' in 'fmt',
623 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
624 size_t const i = fmt.find('@', 0);
625 if (i != string::npos) {
626 size_t const j = fmt.find('@', i + 1);
627 if (j != string::npos) {
628 string parent(fmt, i + 1, j - i - 1);
629 string label = expandLabel(textclass, textclass[parent], appendix);
630 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
634 return textclass.counters().counterLabel(fmt);
640 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
642 int const cur_labeltype = pars[pit].layout()->labeltype;
644 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
647 int const cur_depth = pars[pit].getDepth();
649 par_type prev_pit = pit - 1;
651 int const prev_depth = pars[prev_pit].getDepth();
652 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
653 if (prev_depth == 0 && cur_depth > 0) {
654 if (prev_labeltype == cur_labeltype) {
655 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
658 } else if (prev_depth < cur_depth) {
659 if (prev_labeltype == cur_labeltype) {
660 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
663 } else if (prev_depth == cur_depth) {
664 if (prev_labeltype == cur_labeltype) {
665 pars[pit].itemdepth = pars[prev_pit].itemdepth;
669 if (prev_pit == first_pit)
677 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
678 par_type firstpit, Counters & counters)
683 int const cur_depth = pars[pit].getDepth();
684 par_type prev_pit = pit - 1;
686 int const prev_depth = pars[prev_pit].getDepth();
687 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
688 if (prev_depth <= cur_depth) {
689 if (prev_labeltype != LABEL_ENUMERATE) {
690 switch (pars[pit].itemdepth) {
692 counters.reset("enumi");
694 counters.reset("enumii");
696 counters.reset("enumiii");
698 counters.reset("enumiv");
704 if (prev_pit == firstpit)
714 // set the counter of a paragraph. This includes the labels
715 void LyXText::setCounter(Buffer const & buf, par_type pit)
717 BufferParams const & bufparams = buf.params();
718 LyXTextClass const & textclass = bufparams.getLyXTextClass();
719 LyXLayout_ptr const & layout = pars_[pit].layout();
720 par_type first_pit = 0;
721 Counters & counters = textclass.counters();
724 pars_[pit].itemdepth = 0;
726 if (pit == first_pit) {
727 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
729 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
730 if (!pars_[pit].params().appendix() &&
731 pars_[pit].params().startOfAppendix()) {
732 pars_[pit].params().appendix(true);
733 textclass.counters().reset();
736 // Maybe we have to increment the item depth.
737 incrementItemDepth(pars_, pit, first_pit);
740 // erase what was there before
741 pars_[pit].params().labelString(string());
743 if (layout->margintype == MARGIN_MANUAL) {
744 if (pars_[pit].params().labelWidthString().empty())
745 pars_[pit].setLabelWidthString(layout->labelstring());
747 pars_[pit].setLabelWidthString(string());
750 // is it a layout that has an automatic label?
751 if (layout->labeltype == LABEL_COUNTER) {
752 BufferParams const & bufparams = buf.params();
753 LyXTextClass const & textclass = bufparams.getLyXTextClass();
754 counters.step(layout->counter);
755 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
756 pars_[pit].params().labelString(label);
757 } else if (layout->labeltype == LABEL_ITEMIZE) {
758 // At some point of time we should do something more
759 // clever here, like:
760 // pars_[pit].params().labelString(
761 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
762 // for now, use a simple hardcoded label
764 switch (pars_[pit].itemdepth) {
779 pars_[pit].params().labelString(itemlabel);
780 } else if (layout->labeltype == LABEL_ENUMERATE) {
781 // Maybe we have to reset the enumeration counter.
782 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
785 // Yes I know this is a really, really! bad solution
787 string enumcounter = "enum";
789 switch (pars_[pit].itemdepth) {
801 // not a valid enumdepth...
805 counters.step(enumcounter);
807 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
808 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
809 counters.step("bibitem");
810 int number = counters.value("bibitem");
811 if (pars_[pit].bibitem()) {
812 pars_[pit].bibitem()->setCounter(number);
813 pars_[pit].params().labelString(layout->labelstring());
815 // In biblio should't be following counters but...
817 string s = buf.B_(layout->labelstring());
820 if (layout->labeltype == LABEL_SENSITIVE) {
821 par_type end = paragraphs().size();
822 par_type tmppit = pit;
825 while (tmppit != end) {
826 in = pars_[tmppit].inInset();
827 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
828 in->lyxCode() == InsetBase::WRAP_CODE) {
832 Paragraph const * owner = &ownerPar(buf, in);
834 for ( ; tmppit != end; ++tmppit)
835 if (&pars_[tmppit] == owner)
843 if (in->lyxCode() == InsetBase::FLOAT_CODE)
844 type = static_cast<InsetFloat*>(in)->params().type;
845 else if (in->lyxCode() == InsetBase::WRAP_CODE)
846 type = static_cast<InsetWrap*>(in)->params().type;
850 Floating const & fl = textclass.floats().getType(type);
852 counters.step(fl.type());
854 // Doesn't work... yet.
855 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
857 // par->SetLayout(0);
858 // s = layout->labelstring;
859 s = _("Senseless: ");
862 pars_[pit].params().labelString(s);
868 // Updates all counters.
869 void LyXText::updateCounters()
872 bv()->buffer()->params().getLyXTextClass().counters().reset();
874 bool update_pos = false;
876 par_type end = paragraphs().size();
877 for (par_type pit = 0; pit != end; ++pit) {
878 string const oldLabel = pars_[pit].params().labelString();
881 maxdepth = pars_[pit - 1].getMaxDepthAfter();
883 if (pars_[pit].params().depth() > maxdepth)
884 pars_[pit].params().depth(maxdepth);
886 // setCounter can potentially change the labelString.
887 setCounter(*bv()->buffer(), pit);
888 string const & newLabel = pars_[pit].params().labelString();
889 if (oldLabel != newLabel) {
890 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
891 // << newLabel << endl;
892 redoParagraphInternal(pit);
897 updateParPositions();
901 // this really should just inset the inset and not move the cursor.
902 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
904 BOOST_ASSERT(this == cur.text());
906 cur.paragraph().insertInset(cur.pos(), inset);
911 // needed to insert the selection
912 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
914 par_type pit = cur.par();
915 par_type endpit = cur.par() + 1;
916 pos_type pos = cur.pos();
919 // only to be sure, should not be neccessary
920 cur.clearSelection();
921 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
923 redoParagraphs(cur.par(), endpit);
925 setCursor(cur, cur.par(), pos);
930 // turn double CR to single CR, others are converted into one
931 // blank. Then insertStringAsLines is called
932 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
934 string linestr = str;
935 bool newline_inserted = false;
937 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
938 if (linestr[i] == '\n') {
939 if (newline_inserted) {
940 // we know that \r will be ignored by
941 // insertStringAsLines. Of course, it is a dirty
942 // trick, but it works...
943 linestr[i - 1] = '\r';
947 newline_inserted = true;
949 } else if (IsPrintable(linestr[i])) {
950 newline_inserted = false;
953 insertStringAsLines(cur, linestr);
957 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
958 bool setfont, bool boundary)
961 setCursorIntern(cur, par, pos, setfont, boundary);
962 return deleteEmptyParagraphMechanism(cur, old);
966 void LyXText::setCursor(CursorSlice & cur, par_type par,
967 pos_type pos, bool boundary)
969 BOOST_ASSERT(par != int(paragraphs().size()));
973 cur.boundary() = boundary;
975 // no rows, no fun...
976 if (paragraphs().begin()->rows.empty())
979 // now some strict checking
980 Paragraph & para = getPar(par);
981 Row const & row = *para.getRow(pos);
982 pos_type const end = row.endpos();
984 // None of these should happen, but we're scaredy-cats
986 lyxerr << "dont like -1" << endl;
990 if (pos > para.size()) {
991 lyxerr << "dont like 1, pos: " << pos
992 << " size: " << para.size()
993 << " row.pos():" << row.pos()
994 << " par: " << par << endl;
999 lyxerr << "dont like 2, pos: " << pos
1000 << " size: " << para.size()
1001 << " row.pos():" << row.pos()
1002 << " par: " << par << endl;
1003 // This shouldn't happen.
1004 BOOST_ASSERT(false);
1007 if (pos < row.pos()) {
1008 lyxerr << "dont like 3 please report pos:" << pos
1009 << " size: " << para.size()
1010 << " row.pos():" << row.pos()
1011 << " par: " << par << endl;
1012 BOOST_ASSERT(false);
1017 void LyXText::setCursorIntern(LCursor & cur,
1018 par_type par, pos_type pos, bool setfont, bool boundary)
1020 setCursor(cur.top(), par, pos, boundary);
1021 cur.x_target() = cursorX(cur.top());
1023 setCurrentFont(cur);
1027 void LyXText::setCurrentFont(LCursor & cur)
1029 BOOST_ASSERT(this == cur.text());
1030 pos_type pos = cur.pos();
1031 par_type pit = cur.par();
1033 if (cur.boundary() && pos > 0)
1037 if (pos == cur.lastpos())
1039 else // potentional bug... BUG (Lgb)
1040 if (pars_[pit].isSeparator(pos)) {
1041 if (pos > cur.textRow().pos() &&
1042 bidi.level(pos) % 2 ==
1043 bidi.level(pos - 1) % 2)
1045 else if (pos + 1 < cur.lastpos())
1050 BufferParams const & bufparams = cur.buffer().params();
1051 current_font = pars_[pit].getFontSettings(bufparams, pos);
1052 real_current_font = getFont(pit, pos);
1054 if (cur.pos() == cur.lastpos()
1055 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1056 && !cur.boundary()) {
1057 Language const * lang = pars_[pit].getParLanguage(bufparams);
1058 current_font.setLanguage(lang);
1059 current_font.setNumber(LyXFont::OFF);
1060 real_current_font.setLanguage(lang);
1061 real_current_font.setNumber(LyXFont::OFF);
1066 // x is an absolute screen coord
1067 // returns the column near the specified x-coordinate of the row
1068 // x is set to the real beginning of this column
1069 pos_type LyXText::getColumnNearX(par_type pit,
1070 Row const & row, int & x, bool & boundary) const
1073 RowMetrics const r = computeRowMetrics(pit, row);
1075 pos_type vc = row.pos();
1076 pos_type end = row.endpos();
1078 LyXLayout_ptr const & layout = pars_[pit].layout();
1080 bool left_side = false;
1082 pos_type body_pos = pars_[pit].beginOfBody();
1085 double last_tmpx = tmpx;
1088 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1091 // check for empty row
1093 x = int(tmpx) + xo_;
1097 while (vc < end && tmpx <= x) {
1098 c = bidi.vis2log(vc);
1100 if (body_pos > 0 && c == body_pos - 1) {
1101 tmpx += r.label_hfill +
1102 font_metrics::width(layout->labelsep, getLabelFont(pit));
1103 if (pars_[pit].isLineSeparator(body_pos - 1))
1104 tmpx -= singleWidth(pit, body_pos - 1);
1107 if (hfillExpansion(pars_[pit], row, c)) {
1108 tmpx += singleWidth(pit, c);
1112 tmpx += r.label_hfill;
1113 } else if (pars_[pit].isSeparator(c)) {
1114 tmpx += singleWidth(pit, c);
1116 tmpx += r.separator;
1118 tmpx += singleWidth(pit, c);
1123 if ((tmpx + last_tmpx) / 2 > x) {
1128 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1131 // This (rtl_support test) is not needed, but gives
1132 // some speedup if rtl_support == false
1133 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1135 // If lastrow is false, we don't need to compute
1136 // the value of rtl.
1137 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1139 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1140 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1142 else if (vc == row.pos()) {
1143 c = bidi.vis2log(vc);
1144 if (bidi.level(c) % 2 == 1)
1147 c = bidi.vis2log(vc - 1);
1148 bool const rtl = (bidi.level(c) % 2 == 1);
1149 if (left_side == rtl) {
1151 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1155 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1156 if (bidi.level(end -1) % 2 == 0)
1157 tmpx -= singleWidth(pit, end - 1);
1159 tmpx += singleWidth(pit, end - 1);
1163 x = int(tmpx) + xo_;
1164 return c - row.pos();
1168 // y is relative to this LyXText's top
1169 // this is only used in the two functions below
1170 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1172 BOOST_ASSERT(!paragraphs().empty());
1173 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1174 par_type const pend = paragraphs().size() - 1;
1176 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1179 RowList::iterator rit = pars_[pit].rows.end();
1180 RowList::iterator const rbegin = pars_[pit].rows.begin();
1183 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1189 // x,y are absolute coordinates
1190 // sets cursor only within this LyXText
1191 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1196 Row const & row = getRowNearY(y, pit);
1197 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1198 << row.pos() << endl;
1200 int xx = x + xo_; // getRowNearX get absolute x coords
1201 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1202 setCursor(cur, pit, pos, true, bound);
1206 // x,y are absolute screen coordinates
1207 // sets cursor recursively descending into nested editable insets
1208 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1211 Row const & row = getRowNearY(y - yo_, pit);
1214 int xx = x; // is modified by getColumnNearX
1215 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1218 cur.boundary() = bound;
1220 // try to descend into nested insets
1221 InsetBase * inset = checkInsetHit(x, y);
1222 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1226 // This should be just before or just behind the
1227 // cursor position set above.
1228 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1229 || inset == pars_[pit].getInset(pos));
1230 // Make sure the cursor points to the position before
1232 if (inset == pars_[pit].getInset(pos - 1))
1234 return inset->editXY(cur, x, y);
1238 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1240 if (cur.selection())
1242 if (cur.pos() == cur.lastpos())
1244 InsetBase * inset = cur.nextInset();
1245 if (!isHighlyEditableInset(inset))
1247 inset->edit(cur, front);
1252 void LyXText::cursorLeft(LCursor & cur)
1254 if (cur.pos() != 0) {
1255 bool boundary = cur.boundary();
1256 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1257 if (!checkAndActivateInset(cur, false)) {
1258 if (false && !boundary &&
1259 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1260 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1265 if (cur.par() != 0) {
1266 // steps into the paragraph above
1267 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1272 void LyXText::cursorRight(LCursor & cur)
1274 if (false && cur.boundary()) {
1275 setCursor(cur, cur.par(), cur.pos(), true, false);
1279 if (cur.pos() != cur.lastpos()) {
1280 if (!checkAndActivateInset(cur, true)) {
1281 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1282 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1284 setCursor(cur, cur.par(), cur.pos(), true, true);
1289 if (cur.par() != cur.lastpar())
1290 setCursor(cur, cur.par() + 1, 0);
1294 void LyXText::cursorUp(LCursor & cur)
1296 Row const & row = cur.textRow();
1297 int x = cur.x_target();
1298 int y = cursorY(cur.top()) - row.baseline() - 1;
1299 setCursorFromCoordinates(cur, x, y);
1301 if (!cur.selection()) {
1302 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1303 if (inset_hit && isHighlyEditableInset(inset_hit))
1304 inset_hit->editXY(cur, cur.x_target(), y);
1309 void LyXText::cursorDown(LCursor & cur)
1311 Row const & row = cur.textRow();
1312 int x = cur.x_target();
1313 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1314 setCursorFromCoordinates(cur, x, y);
1316 if (!cur.selection()) {
1317 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1318 if (inset_hit && isHighlyEditableInset(inset_hit))
1319 inset_hit->editXY(cur, cur.x_target(), y);
1324 void LyXText::cursorUpParagraph(LCursor & cur)
1327 setCursor(cur, cur.par(), 0);
1328 else if (cur.par() != 0)
1329 setCursor(cur, cur.par() - 1, 0);
1333 void LyXText::cursorDownParagraph(LCursor & cur)
1335 if (cur.par() != cur.lastpar())
1336 setCursor(cur, cur.par() + 1, 0);
1338 setCursor(cur, cur.par(), cur.lastpos());
1342 // fix the cursor `cur' after a characters has been deleted at `where'
1343 // position. Called by deleteEmptyParagraphMechanism
1344 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1346 // do notheing if cursor is not in the paragraph where the
1347 // deletion occured,
1348 if (cur.par() != where.par())
1351 // if cursor position is after the deletion place update it
1352 if (cur.pos() > where.pos())
1355 // check also if we don't want to set the cursor on a spot behind the
1356 // pagragraph because we erased the last character.
1357 if (cur.pos() > cur.lastpos())
1358 cur.pos() = cur.lastpos();
1362 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1364 BOOST_ASSERT(cur.size() == old.size());
1365 // Would be wrong to delete anything if we have a selection.
1366 if (cur.selection())
1369 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1370 Paragraph const & oldpar = pars_[old.par()];
1372 // We allow all kinds of "mumbo-jumbo" when freespacing.
1373 if (oldpar.isFreeSpacing())
1376 /* Ok I'll put some comments here about what is missing.
1377 I have fixed BackSpace (and thus Delete) to not delete
1378 double-spaces automagically. I have also changed Cut,
1379 Copy and Paste to hopefully do some sensible things.
1380 There are still some small problems that can lead to
1381 double spaces stored in the document file or space at
1382 the beginning of paragraphs(). This happens if you have
1383 the cursor between to spaces and then save. Or if you
1384 cut and paste and the selection have a space at the
1385 beginning and then save right after the paste. I am
1386 sure none of these are very hard to fix, but I will
1387 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1388 that I can get some feedback. (Lgb)
1391 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1392 // delete the LineSeparator.
1395 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1396 // delete the LineSeparator.
1399 // If the chars around the old cursor were spaces, delete one of them.
1400 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1402 // Only if the cursor has really moved.
1404 && old.pos() < oldpar.size()
1405 && oldpar.isLineSeparator(old.pos())
1406 && oldpar.isLineSeparator(old.pos() - 1)) {
1407 pars_[old.par()].erase(old.pos() - 1);
1408 #ifdef WITH_WARNINGS
1409 #warning This will not work anymore when we have multiple views of the same buffer
1410 // In this case, we will have to correct also the cursors held by
1411 // other bufferviews. It will probably be easier to do that in a more
1412 // automated way in CursorSlice code. (JMarc 26/09/2001)
1414 // correct all cursor parts
1415 fixCursorAfterDelete(cur.top(), old.top());
1416 #ifdef WITH_WARNINGS
1417 #warning DEPM, look here
1419 //fixCursorAfterDelete(cur.anchor(), old.top());
1424 // only do our magic if we changed paragraph
1425 if (old.par() == cur.par())
1428 // don't delete anything if this is the ONLY paragraph!
1429 if (pars_.size() == 1)
1432 // Do not delete empty paragraphs with keepempty set.
1433 if (oldpar.allowEmpty())
1436 // record if we have deleted a paragraph
1437 // we can't possibly have deleted a paragraph before this point
1438 bool deleted = false;
1440 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1441 // ok, we will delete something
1442 CursorSlice tmpcursor;
1446 bool selection_position_was_oldcursor_position =
1447 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1449 // This is a bit of a overkill. We change the old and the cur par
1450 // at max, certainly not everything in between...
1451 recUndo(old.par(), cur.par());
1454 pars_.erase(pars_.begin() + old.par());
1456 // Update cursor par offset if necessary.
1457 // Some 'iterator registration' would be nice that takes care of
1458 // such events. Maybe even signal/slot?
1459 if (cur.par() > old.par())
1461 #ifdef WITH_WARNINGS
1462 #warning DEPM, look here
1464 // if (cur.anchor().par() > old.par())
1465 // --cur.anchor().par();
1467 if (selection_position_was_oldcursor_position) {
1468 // correct selection
1476 if (pars_[old.par()].stripLeadingSpaces())
1483 ParagraphList & LyXText::paragraphs() const
1485 return const_cast<ParagraphList &>(pars_);
1489 void LyXText::recUndo(par_type first, par_type last) const
1491 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1495 void LyXText::recUndo(par_type par) const
1497 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1501 int defaultRowHeight()
1503 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);