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 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
117 lyxerr << " pit: " << pit << " end: " << end << endl;
118 for (; pit != end; ++pit) {
119 InsetList::const_iterator iit = pars_[pit].insetlist.begin();
120 InsetList::const_iterator iend = pars_[pit].insetlist.end();
121 for (; iit != iend; ++iit) {
122 InsetBase * inset = iit->inset;
124 lyxerr << "examining inset " << inset << endl;
125 if (theCoords.insets_.has(inset))
127 << " xo: " << inset->xo() << "..." << inset->xo() + inset->width()
128 << " yo: " << inset->yo() - inset->ascent() << "..."
129 << inset->yo() + inset->descent() << endl;
131 lyxerr << " inset has no cached position";
133 if (inset->covers(x, y)) {
134 lyxerr << "Hit inset: " << inset << endl;
139 lyxerr << "No inset hit. " << endl;
145 // Gets the fully instantiated font at a given position in a paragraph
146 // Basically the same routine as Paragraph::getFont() in paragraph.C.
147 // The difference is that this one is used for displaying, and thus we
148 // are allowed to make cosmetic improvements. For instance make footnotes
150 LyXFont LyXText::getFont(par_type pit, pos_type pos) const
152 BOOST_ASSERT(pos >= 0);
154 LyXLayout_ptr const & layout = pars_[pit].layout();
158 BufferParams const & params = bv()->buffer()->params();
159 pos_type const body_pos = pars_[pit].beginOfBody();
161 // We specialize the 95% common case:
162 if (!pars_[pit].getDepth()) {
163 LyXFont f = pars_[pit].getFontSettings(params, pos);
166 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
167 return f.realize(layout->reslabelfont);
169 return f.realize(layout->resfont);
172 // The uncommon case need not be optimized as much
175 layoutfont = layout->labelfont;
177 layoutfont = layout->font;
179 LyXFont font = pars_[pit].getFontSettings(params, pos);
180 font.realize(layoutfont);
185 // Realize with the fonts of lesser depth.
186 //font.realize(outerFont(pit, paragraphs()));
187 font.realize(defaultfont_);
193 LyXFont LyXText::getLayoutFont(par_type pit) const
195 LyXLayout_ptr const & layout = pars_[pit].layout();
197 if (!pars_[pit].getDepth())
198 return layout->resfont;
200 LyXFont font = layout->font;
201 // Realize with the fonts of lesser depth.
202 //font.realize(outerFont(pit, paragraphs()));
203 font.realize(defaultfont_);
209 LyXFont LyXText::getLabelFont(par_type pit) const
211 LyXLayout_ptr const & layout = pars_[pit].layout();
213 if (!pars_[pit].getDepth())
214 return layout->reslabelfont;
216 LyXFont font = layout->labelfont;
217 // Realize with the fonts of lesser depth.
218 font.realize(outerFont(pit, paragraphs()));
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(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 pos = cur.selectionBegin();
461 DocIterator posend = cur.selectionEnd();
463 lyxerr[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
466 BufferParams const & params = cur.buffer().params();
468 // Don't use forwardChar here as posend might have
469 // pos() == lastpos() and forwardChar would miss it.
470 for (; pos != posend; pos.forwardPos()) {
471 if (pos.pos() != pos.lastpos()) {
472 LyXFont f = getFont(pos.par(), pos.pos());
473 f.update(font, params.language, toggleall);
474 setCharFont(pos.par(), pos.pos(), f);
478 redoParagraphs(beg, end + 1);
482 // the cursor set functions have a special mechanism. When they
483 // realize you left an empty paragraph, they will delete it.
485 void LyXText::cursorHome(LCursor & cur)
487 BOOST_ASSERT(this == cur.text());
488 setCursor(cur, cur.par(), cur.textRow().pos());
492 void LyXText::cursorEnd(LCursor & cur)
494 BOOST_ASSERT(this == cur.text());
495 // if not on the last row of the par, put the cursor before
497 pos_type const end = cur.textRow().endpos();
498 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
502 void LyXText::cursorTop(LCursor & cur)
504 BOOST_ASSERT(this == cur.text());
505 setCursor(cur, 0, 0);
509 void LyXText::cursorBottom(LCursor & cur)
511 BOOST_ASSERT(this == cur.text());
512 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
516 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
518 BOOST_ASSERT(this == cur.text());
519 // If the mask is completely neutral, tell user
520 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
521 // Could only happen with user style
522 cur.message(_("No font change defined. "
523 "Use Character under the Layout menu to define font change."));
527 // Try implicit word selection
528 // If there is a change in the language the implicit word selection
530 CursorSlice resetCursor = cur.top();
531 bool implicitSelection =
532 font.language() == ignore_language
533 && font.number() == LyXFont::IGNORE
534 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
537 setFont(cur, font, toggleall);
539 // Implicit selections are cleared afterwards
540 // and cursor is set to the original position.
541 if (implicitSelection) {
542 cur.clearSelection();
543 cur.top() = resetCursor;
549 string LyXText::getStringToIndex(LCursor & cur)
551 BOOST_ASSERT(this == cur.text());
552 // Try implicit word selection
553 // If there is a change in the language the implicit word selection
555 CursorSlice const reset_cursor = cur.top();
556 bool const implicitSelection =
557 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
560 if (!cur.selection())
561 cur.message(_("Nothing to index!"));
562 else if (cur.selBegin().par() != cur.selEnd().par())
563 cur.message(_("Cannot index more than one paragraph!"));
565 idxstring = cur.selectionAsString(false);
567 // Reset cursors to their original position.
568 cur.top() = reset_cursor;
571 // Clear the implicit selection.
572 if (implicitSelection)
573 cur.clearSelection();
579 void LyXText::setParagraph(LCursor & cur,
580 Spacing const & spacing, LyXAlignment align,
581 string const & labelwidthstring, bool noindent)
583 BOOST_ASSERT(cur.text());
584 // make sure that the depth behind the selection are restored, too
585 par_type undopit = undoSpan(cur.selEnd().par());
586 recUndo(cur.selBegin().par(), undopit - 1);
588 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
590 Paragraph & par = pars_[pit];
591 ParagraphParameters & params = par.params();
592 params.spacing(spacing);
594 // does the layout allow the new alignment?
595 LyXLayout_ptr const & layout = par.layout();
597 if (align == LYX_ALIGN_LAYOUT)
598 align = layout->align;
599 if (align & layout->alignpossible) {
600 if (align == layout->align)
601 params.align(LYX_ALIGN_LAYOUT);
605 par.setLabelWidthString(labelwidthstring);
606 params.noindent(noindent);
609 redoParagraphs(cur.selBegin().par(), undopit);
613 string expandLabel(LyXTextClass const & textclass,
614 LyXLayout_ptr const & layout, bool appendix)
616 string fmt = appendix ?
617 layout->labelstring_appendix() : layout->labelstring();
619 // handle 'inherited level parts' in 'fmt',
620 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
621 size_t const i = fmt.find('@', 0);
622 if (i != string::npos) {
623 size_t const j = fmt.find('@', i + 1);
624 if (j != string::npos) {
625 string parent(fmt, i + 1, j - i - 1);
626 string label = expandLabel(textclass, textclass[parent], appendix);
627 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
631 return textclass.counters().counterLabel(fmt);
637 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
639 int const cur_labeltype = pars[pit].layout()->labeltype;
641 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
644 int const cur_depth = pars[pit].getDepth();
646 par_type prev_pit = pit - 1;
648 int const prev_depth = pars[prev_pit].getDepth();
649 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
650 if (prev_depth == 0 && cur_depth > 0) {
651 if (prev_labeltype == cur_labeltype) {
652 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
655 } else if (prev_depth < cur_depth) {
656 if (prev_labeltype == cur_labeltype) {
657 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
660 } else if (prev_depth == cur_depth) {
661 if (prev_labeltype == cur_labeltype) {
662 pars[pit].itemdepth = pars[prev_pit].itemdepth;
666 if (prev_pit == first_pit)
674 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
675 par_type firstpit, Counters & counters)
680 int const cur_depth = pars[pit].getDepth();
681 par_type prev_pit = pit - 1;
683 int const prev_depth = pars[prev_pit].getDepth();
684 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
685 if (prev_depth <= cur_depth) {
686 if (prev_labeltype != LABEL_ENUMERATE) {
687 switch (pars[pit].itemdepth) {
689 counters.reset("enumi");
691 counters.reset("enumii");
693 counters.reset("enumiii");
695 counters.reset("enumiv");
701 if (prev_pit == firstpit)
711 // set the counter of a paragraph. This includes the labels
712 void LyXText::setCounter(Buffer const & buf, par_type pit)
714 BufferParams const & bufparams = buf.params();
715 LyXTextClass const & textclass = bufparams.getLyXTextClass();
716 LyXLayout_ptr const & layout = pars_[pit].layout();
717 par_type first_pit = 0;
718 Counters & counters = textclass.counters();
721 pars_[pit].itemdepth = 0;
723 if (pit == first_pit) {
724 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
726 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
727 if (!pars_[pit].params().appendix() &&
728 pars_[pit].params().startOfAppendix()) {
729 pars_[pit].params().appendix(true);
730 textclass.counters().reset();
733 // Maybe we have to increment the item depth.
734 incrementItemDepth(pars_, pit, first_pit);
737 // erase what was there before
738 pars_[pit].params().labelString(string());
740 if (layout->margintype == MARGIN_MANUAL) {
741 if (pars_[pit].params().labelWidthString().empty())
742 pars_[pit].setLabelWidthString(layout->labelstring());
744 pars_[pit].setLabelWidthString(string());
747 // is it a layout that has an automatic label?
748 if (layout->labeltype == LABEL_COUNTER) {
749 BufferParams const & bufparams = buf.params();
750 LyXTextClass const & textclass = bufparams.getLyXTextClass();
751 counters.step(layout->counter);
752 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
753 pars_[pit].params().labelString(label);
754 } else if (layout->labeltype == LABEL_ITEMIZE) {
755 // At some point of time we should do something more
756 // clever here, like:
757 // pars_[pit].params().labelString(
758 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
759 // for now, use a simple hardcoded label
761 switch (pars_[pit].itemdepth) {
776 pars_[pit].params().labelString(itemlabel);
777 } else if (layout->labeltype == LABEL_ENUMERATE) {
778 // Maybe we have to reset the enumeration counter.
779 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
782 // Yes I know this is a really, really! bad solution
784 string enumcounter = "enum";
786 switch (pars_[pit].itemdepth) {
798 // not a valid enumdepth...
802 counters.step(enumcounter);
804 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
805 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
806 counters.step("bibitem");
807 int number = counters.value("bibitem");
808 if (pars_[pit].bibitem()) {
809 pars_[pit].bibitem()->setCounter(number);
810 pars_[pit].params().labelString(layout->labelstring());
812 // In biblio should't be following counters but...
814 string s = buf.B_(layout->labelstring());
817 if (layout->labeltype == LABEL_SENSITIVE) {
818 par_type end = paragraphs().size();
819 par_type tmppit = pit;
822 while (tmppit != end) {
823 in = pars_[tmppit].inInset();
824 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
825 in->lyxCode() == InsetBase::WRAP_CODE) {
829 Paragraph const * owner = &ownerPar(buf, in);
831 for ( ; tmppit != end; ++tmppit)
832 if (&pars_[tmppit] == owner)
840 if (in->lyxCode() == InsetBase::FLOAT_CODE)
841 type = static_cast<InsetFloat*>(in)->params().type;
842 else if (in->lyxCode() == InsetBase::WRAP_CODE)
843 type = static_cast<InsetWrap*>(in)->params().type;
847 Floating const & fl = textclass.floats().getType(type);
849 counters.step(fl.type());
851 // Doesn't work... yet.
852 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
854 // par->SetLayout(0);
855 // s = layout->labelstring;
856 s = _("Senseless: ");
859 pars_[pit].params().labelString(s);
865 // Updates all counters.
866 void LyXText::updateCounters()
869 bv()->buffer()->params().getLyXTextClass().counters().reset();
871 bool update_pos = false;
873 par_type end = paragraphs().size();
874 for (par_type pit = 0; pit != end; ++pit) {
875 string const oldLabel = pars_[pit].params().labelString();
878 maxdepth = pars_[pit - 1].getMaxDepthAfter();
880 if (pars_[pit].params().depth() > maxdepth)
881 pars_[pit].params().depth(maxdepth);
883 // setCounter can potentially change the labelString.
884 setCounter(*bv()->buffer(), pit);
885 string const & newLabel = pars_[pit].params().labelString();
886 if (oldLabel != newLabel) {
887 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
888 // << newLabel << endl;
889 redoParagraphInternal(pit);
894 updateParPositions();
898 // this really should just inset the inset and not move the cursor.
899 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
901 BOOST_ASSERT(this == cur.text());
903 cur.paragraph().insertInset(cur.pos(), inset);
908 // needed to insert the selection
909 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
911 par_type pit = cur.par();
912 par_type endpit = cur.par() + 1;
913 pos_type pos = cur.pos();
916 // only to be sure, should not be neccessary
917 cur.clearSelection();
918 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
920 redoParagraphs(cur.par(), endpit);
922 setCursor(cur, cur.par(), pos);
927 // turn double CR to single CR, others are converted into one
928 // blank. Then insertStringAsLines is called
929 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
931 string linestr = str;
932 bool newline_inserted = false;
934 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
935 if (linestr[i] == '\n') {
936 if (newline_inserted) {
937 // we know that \r will be ignored by
938 // insertStringAsLines. Of course, it is a dirty
939 // trick, but it works...
940 linestr[i - 1] = '\r';
944 newline_inserted = true;
946 } else if (IsPrintable(linestr[i])) {
947 newline_inserted = false;
950 insertStringAsLines(cur, linestr);
954 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
955 bool setfont, bool boundary)
958 setCursorIntern(cur, par, pos, setfont, boundary);
959 return deleteEmptyParagraphMechanism(cur, old);
963 void LyXText::setCursor(CursorSlice & cur, par_type par,
964 pos_type pos, bool boundary)
966 BOOST_ASSERT(par != int(paragraphs().size()));
970 cur.boundary() = boundary;
972 // no rows, no fun...
973 if (paragraphs().begin()->rows.empty())
976 // now some strict checking
977 Paragraph & para = getPar(par);
978 Row const & row = *para.getRow(pos);
979 pos_type const end = row.endpos();
981 // None of these should happen, but we're scaredy-cats
983 lyxerr << "dont like -1" << endl;
987 if (pos > para.size()) {
988 lyxerr << "dont like 1, pos: " << pos
989 << " size: " << para.size()
990 << " row.pos():" << row.pos()
991 << " par: " << par << endl;
996 lyxerr << "dont like 2, pos: " << pos
997 << " size: " << para.size()
998 << " row.pos():" << row.pos()
999 << " par: " << par << endl;
1000 // This shouldn't happen.
1001 BOOST_ASSERT(false);
1004 if (pos < row.pos()) {
1005 lyxerr << "dont like 3 please report pos:" << pos
1006 << " size: " << para.size()
1007 << " row.pos():" << row.pos()
1008 << " par: " << par << endl;
1009 BOOST_ASSERT(false);
1014 void LyXText::setCursorIntern(LCursor & cur,
1015 par_type par, pos_type pos, bool setfont, bool boundary)
1017 setCursor(cur.top(), par, pos, boundary);
1018 cur.x_target() = cursorX(cur.top());
1020 setCurrentFont(cur);
1024 void LyXText::setCurrentFont(LCursor & cur)
1026 BOOST_ASSERT(this == cur.text());
1027 pos_type pos = cur.pos();
1028 par_type pit = cur.par();
1030 if (cur.boundary() && pos > 0)
1034 if (pos == cur.lastpos())
1036 else // potentional bug... BUG (Lgb)
1037 if (pars_[pit].isSeparator(pos)) {
1038 if (pos > cur.textRow().pos() &&
1039 bidi.level(pos) % 2 ==
1040 bidi.level(pos - 1) % 2)
1042 else if (pos + 1 < cur.lastpos())
1047 BufferParams const & bufparams = cur.buffer().params();
1048 current_font = pars_[pit].getFontSettings(bufparams, pos);
1049 real_current_font = getFont(pit, pos);
1051 if (cur.pos() == cur.lastpos()
1052 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1053 && !cur.boundary()) {
1054 Language const * lang = pars_[pit].getParLanguage(bufparams);
1055 current_font.setLanguage(lang);
1056 current_font.setNumber(LyXFont::OFF);
1057 real_current_font.setLanguage(lang);
1058 real_current_font.setNumber(LyXFont::OFF);
1063 // x is an absolute screen coord
1064 // returns the column near the specified x-coordinate of the row
1065 // x is set to the real beginning of this column
1066 pos_type LyXText::getColumnNearX(par_type pit,
1067 Row const & row, int & x, bool & boundary) const
1070 RowMetrics const r = computeRowMetrics(pit, row);
1072 pos_type vc = row.pos();
1073 pos_type end = row.endpos();
1075 LyXLayout_ptr const & layout = pars_[pit].layout();
1077 bool left_side = false;
1079 pos_type body_pos = pars_[pit].beginOfBody();
1082 double last_tmpx = tmpx;
1085 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1088 // check for empty row
1090 x = int(tmpx) + xo_;
1094 while (vc < end && tmpx <= x) {
1095 c = bidi.vis2log(vc);
1097 if (body_pos > 0 && c == body_pos - 1) {
1098 tmpx += r.label_hfill +
1099 font_metrics::width(layout->labelsep, getLabelFont(pit));
1100 if (pars_[pit].isLineSeparator(body_pos - 1))
1101 tmpx -= singleWidth(pit, body_pos - 1);
1104 if (hfillExpansion(pars_[pit], row, c)) {
1105 tmpx += singleWidth(pit, c);
1109 tmpx += r.label_hfill;
1110 } else if (pars_[pit].isSeparator(c)) {
1111 tmpx += singleWidth(pit, c);
1113 tmpx += r.separator;
1115 tmpx += singleWidth(pit, c);
1120 if ((tmpx + last_tmpx) / 2 > x) {
1125 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1128 // This (rtl_support test) is not needed, but gives
1129 // some speedup if rtl_support == false
1130 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1132 // If lastrow is false, we don't need to compute
1133 // the value of rtl.
1134 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1136 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1137 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1139 else if (vc == row.pos()) {
1140 c = bidi.vis2log(vc);
1141 if (bidi.level(c) % 2 == 1)
1144 c = bidi.vis2log(vc - 1);
1145 bool const rtl = (bidi.level(c) % 2 == 1);
1146 if (left_side == rtl) {
1148 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1152 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1153 if (bidi.level(end -1) % 2 == 0)
1154 tmpx -= singleWidth(pit, end - 1);
1156 tmpx += singleWidth(pit, end - 1);
1160 x = int(tmpx) + xo_;
1161 return c - row.pos();
1165 // x,y are absolute coordinates
1166 // sets cursor only within this LyXText
1167 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1172 Row const & row = getRowNearY(y, pit);
1173 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1174 << row.pos() << endl;
1176 int xx = x + xo_; // getRowNearX get absolute x coords
1177 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1178 setCursor(cur, pit, pos, true, bound);
1182 // x,y are absolute screen coordinates
1183 // sets cursor recursively descending into nested editable insets
1184 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1187 Row const & row = getRowNearY(y - yo_, pit);
1190 int xx = x; // is modified by getColumnNearX
1191 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1194 cur.boundary() = bound;
1196 // try to descend into nested insets
1197 InsetBase * inset = checkInsetHit(x, y);
1198 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1202 // This should be just before or just behind the
1203 // cursor position set above.
1204 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1205 || inset == pars_[pit].getInset(pos));
1206 // Make sure the cursor points to the position before
1208 if (inset == pars_[pit].getInset(pos - 1))
1210 return inset->editXY(cur, x, y);
1214 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1216 if (cur.selection())
1218 if (cur.pos() == cur.lastpos())
1220 InsetBase * inset = cur.nextInset();
1221 if (!isHighlyEditableInset(inset))
1223 inset->edit(cur, front);
1228 void LyXText::cursorLeft(LCursor & cur)
1230 if (cur.pos() != 0) {
1231 bool boundary = cur.boundary();
1232 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1233 if (!checkAndActivateInset(cur, false)) {
1234 if (false && !boundary &&
1235 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1236 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1241 if (cur.par() != 0) {
1242 // steps into the paragraph above
1243 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1248 void LyXText::cursorRight(LCursor & cur)
1250 if (false && cur.boundary()) {
1251 setCursor(cur, cur.par(), cur.pos(), true, false);
1255 if (cur.pos() != cur.lastpos()) {
1256 if (!checkAndActivateInset(cur, true)) {
1257 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1258 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1260 setCursor(cur, cur.par(), cur.pos(), true, true);
1265 if (cur.par() != cur.lastpar())
1266 setCursor(cur, cur.par() + 1, 0);
1270 void LyXText::cursorUp(LCursor & cur)
1272 Row const & row = cur.textRow();
1273 int x = cur.x_target();
1274 int y = cursorY(cur.top()) - row.baseline() - 1;
1275 setCursorFromCoordinates(cur, x, y);
1277 if (!cur.selection()) {
1278 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1279 if (inset_hit && isHighlyEditableInset(inset_hit))
1280 inset_hit->editXY(cur, cur.x_target(), y);
1285 void LyXText::cursorDown(LCursor & cur)
1287 Row const & row = cur.textRow();
1288 int x = cur.x_target();
1289 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1290 setCursorFromCoordinates(cur, x, y);
1292 if (!cur.selection()) {
1293 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1294 if (inset_hit && isHighlyEditableInset(inset_hit))
1295 inset_hit->editXY(cur, cur.x_target(), y);
1300 void LyXText::cursorUpParagraph(LCursor & cur)
1303 setCursor(cur, cur.par(), 0);
1304 else if (cur.par() != 0)
1305 setCursor(cur, cur.par() - 1, 0);
1309 void LyXText::cursorDownParagraph(LCursor & cur)
1311 if (cur.par() != cur.lastpar())
1312 setCursor(cur, cur.par() + 1, 0);
1314 setCursor(cur, cur.par(), cur.lastpos());
1318 // fix the cursor `cur' after a characters has been deleted at `where'
1319 // position. Called by deleteEmptyParagraphMechanism
1320 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1322 // do notheing if cursor is not in the paragraph where the
1323 // deletion occured,
1324 if (cur.par() != where.par())
1327 // if cursor position is after the deletion place update it
1328 if (cur.pos() > where.pos())
1331 // check also if we don't want to set the cursor on a spot behind the
1332 // pagragraph because we erased the last character.
1333 if (cur.pos() > cur.lastpos())
1334 cur.pos() = cur.lastpos();
1338 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1340 BOOST_ASSERT(cur.size() == old.size());
1341 // Would be wrong to delete anything if we have a selection.
1342 if (cur.selection())
1345 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1346 Paragraph const & oldpar = pars_[old.par()];
1348 // We allow all kinds of "mumbo-jumbo" when freespacing.
1349 if (oldpar.isFreeSpacing())
1352 /* Ok I'll put some comments here about what is missing.
1353 I have fixed BackSpace (and thus Delete) to not delete
1354 double-spaces automagically. I have also changed Cut,
1355 Copy and Paste to hopefully do some sensible things.
1356 There are still some small problems that can lead to
1357 double spaces stored in the document file or space at
1358 the beginning of paragraphs(). This happens if you have
1359 the cursor between to spaces and then save. Or if you
1360 cut and paste and the selection have a space at the
1361 beginning and then save right after the paste. I am
1362 sure none of these are very hard to fix, but I will
1363 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1364 that I can get some feedback. (Lgb)
1367 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1368 // delete the LineSeparator.
1371 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1372 // delete the LineSeparator.
1375 // If the chars around the old cursor were spaces, delete one of them.
1376 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1378 // Only if the cursor has really moved.
1380 && old.pos() < oldpar.size()
1381 && oldpar.isLineSeparator(old.pos())
1382 && oldpar.isLineSeparator(old.pos() - 1)) {
1383 pars_[old.par()].erase(old.pos() - 1);
1384 #ifdef WITH_WARNINGS
1385 #warning This will not work anymore when we have multiple views of the same buffer
1386 // In this case, we will have to correct also the cursors held by
1387 // other bufferviews. It will probably be easier to do that in a more
1388 // automated way in CursorSlice code. (JMarc 26/09/2001)
1390 // correct all cursor parts
1391 fixCursorAfterDelete(cur.top(), old.top());
1392 #ifdef WITH_WARNINGS
1393 #warning DEPM, look here
1395 //fixCursorAfterDelete(cur.anchor(), old.top());
1400 // only do our magic if we changed paragraph
1401 if (old.par() == cur.par())
1404 // don't delete anything if this is the ONLY paragraph!
1405 if (pars_.size() == 1)
1408 // Do not delete empty paragraphs with keepempty set.
1409 if (oldpar.allowEmpty())
1412 // record if we have deleted a paragraph
1413 // we can't possibly have deleted a paragraph before this point
1414 bool deleted = false;
1416 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1417 // ok, we will delete something
1418 CursorSlice tmpcursor;
1422 bool selection_position_was_oldcursor_position =
1423 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1425 // This is a bit of a overkill. We change the old and the cur par
1426 // at max, certainly not everything in between...
1427 recUndo(old.par(), cur.par());
1430 pars_.erase(pars_.begin() + old.par());
1432 // Update cursor par offset if necessary.
1433 // Some 'iterator registration' would be nice that takes care of
1434 // such events. Maybe even signal/slot?
1435 if (cur.par() > old.par())
1437 #ifdef WITH_WARNINGS
1438 #warning DEPM, look here
1440 // if (cur.anchor().par() > old.par())
1441 // --cur.anchor().par();
1443 if (selection_position_was_oldcursor_position) {
1444 // correct selection
1452 if (pars_[old.par()].stripLeadingSpaces())
1459 ParagraphList & LyXText::paragraphs() const
1461 return const_cast<ParagraphList &>(pars_);
1465 void LyXText::recUndo(par_type first, par_type last) const
1467 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1471 void LyXText::recUndo(par_type par) const
1473 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1477 int defaultRowHeight()
1479 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);