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"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
36 #include "FloatList.h"
37 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
50 #include "frontends/font_metrics.h"
51 #include "frontends/LyXView.h"
53 #include "insets/insetbibitem.h"
54 #include "insets/insetenv.h"
55 #include "insets/insetfloat.h"
56 #include "insets/insetwrap.h"
58 #include "support/lstrings.h"
59 #include "support/textutils.h"
60 #include "support/tostr.h"
61 #include "support/std_sstream.h"
65 using lyx::support::bformat;
68 using std::ostringstream;
72 LyXText::LyXText(BufferView * bv)
73 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
74 background_color_(LColor::background),
75 bv_owner(bv), xo_(0), yo_(0)
79 void LyXText::init(BufferView * bv)
83 maxwidth_ = bv->workWidth();
87 par_type const end = paragraphs().size();
88 for (par_type pit = 0; pit != end; ++pit)
89 pars_[pit].rows.clear();
91 current_font = getFont(0, 0);
92 redoParagraphs(0, end);
97 bool LyXText::isMainText() const
99 return &bv()->buffer()->text() == this;
103 // Gets the fully instantiated font at a given position in a paragraph
104 // Basically the same routine as Paragraph::getFont() in paragraph.C.
105 // The difference is that this one is used for displaying, and thus we
106 // are allowed to make cosmetic improvements. For instance make footnotes
108 LyXFont LyXText::getFont(par_type pit, pos_type pos) const
110 BOOST_ASSERT(pos >= 0);
112 LyXLayout_ptr const & layout = pars_[pit].layout();
116 BufferParams const & params = bv()->buffer()->params();
117 pos_type const body_pos = pars_[pit].beginOfBody();
119 // We specialize the 95% common case:
120 if (!pars_[pit].getDepth()) {
121 LyXFont f = pars_[pit].getFontSettings(params, pos);
124 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
125 return f.realize(layout->reslabelfont);
127 return f.realize(layout->resfont);
130 // The uncommon case need not be optimized as much
133 layoutfont = layout->labelfont;
135 layoutfont = layout->font;
137 LyXFont font = pars_[pit].getFontSettings(params, pos);
138 font.realize(layoutfont);
143 // Realize with the fonts of lesser depth.
144 //font.realize(outerFont(pit, paragraphs()));
145 font.realize(defaultfont_);
151 LyXFont LyXText::getLayoutFont(par_type pit) const
153 LyXLayout_ptr const & layout = pars_[pit].layout();
155 if (!pars_[pit].getDepth())
156 return layout->resfont;
158 LyXFont font = layout->font;
159 // Realize with the fonts of lesser depth.
160 //font.realize(outerFont(pit, paragraphs()));
161 font.realize(defaultfont_);
167 LyXFont LyXText::getLabelFont(par_type pit) const
169 LyXLayout_ptr const & layout = pars_[pit].layout();
171 if (!pars_[pit].getDepth())
172 return layout->reslabelfont;
174 LyXFont font = layout->labelfont;
175 // Realize with the fonts of lesser depth.
176 font.realize(outerFont(pit, paragraphs()));
177 font.realize(defaultfont_);
183 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
186 LyXLayout_ptr const & layout = pars_[pit].layout();
188 // Get concrete layout font to reduce against
191 if (pos < pars_[pit].beginOfBody())
192 layoutfont = layout->labelfont;
194 layoutfont = layout->font;
196 // Realize against environment font information
197 if (pars_[pit].getDepth()) {
199 while (!layoutfont.resolved() &&
200 tp != par_type(paragraphs().size()) &&
201 pars_[tp].getDepth()) {
202 tp = outerHook(tp, paragraphs());
203 if (tp != par_type(paragraphs().size()))
204 layoutfont.realize(pars_[tp].layout()->font);
208 layoutfont.realize(defaultfont_);
210 // Now, reduce font against full layout font
211 font.reduce(layoutfont);
213 pars_[pit].setFont(pos, font);
218 // Asger is not sure we want to do this...
219 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
222 LyXLayout_ptr const & layout = par.layout();
223 pos_type const psize = par.size();
226 for (pos_type pos = 0; pos < psize; ++pos) {
227 if (pos < par.beginOfBody())
228 layoutfont = layout->labelfont;
230 layoutfont = layout->font;
232 LyXFont tmpfont = par.getFontSettings(params, pos);
233 tmpfont.reduce(layoutfont);
234 par.setFont(pos, tmpfont);
239 // return past-the-last paragraph influenced by a layout change on pit
240 par_type LyXText::undoSpan(par_type pit)
242 par_type end = paragraphs().size();
243 par_type nextpit = pit + 1;
246 //because of parindents
247 if (!pars_[pit].getDepth())
248 return boost::next(nextpit);
249 //because of depth constrains
250 for (; nextpit != end; ++pit, ++nextpit) {
251 if (!pars_[pit].getDepth())
258 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
260 BOOST_ASSERT(start != end);
261 par_type undopit = undoSpan(end - 1);
262 recUndo(start, undopit - 1);
264 BufferParams const & bufparams = bv()->buffer()->params();
265 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
267 for (par_type pit = start; pit != end; ++pit) {
268 pars_[pit].applyLayout(lyxlayout);
269 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
270 if (lyxlayout->margintype == MARGIN_MANUAL)
271 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
278 // set layout over selection and make a total rebreak of those paragraphs
279 void LyXText::setLayout(LCursor & cur, string const & layout)
281 BOOST_ASSERT(this == cur.text());
282 // special handling of new environment insets
283 BufferView & bv = cur.bv();
284 BufferParams const & params = bv.buffer()->params();
285 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
286 if (lyxlayout->is_environment) {
287 // move everything in a new environment inset
288 lyxerr << "setting layout " << layout << endl;
289 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
290 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
291 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
292 InsetBase * inset = new InsetEnvironment(params, layout);
293 insertInset(cur, inset);
294 //inset->edit(cur, true);
295 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
299 par_type start = cur.selBegin().par();
300 par_type end = cur.selEnd().par() + 1;
301 par_type endpit = setLayout(start, end, layout);
302 redoParagraphs(start, endpit);
310 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
312 if (!cur.selection()) {
316 beg = cur.selBegin().par();
317 end = cur.selEnd().par() + 1;
322 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
323 Paragraph const & par, int max_depth)
325 if (par.layout()->labeltype == LABEL_BIBLIO)
327 int const depth = par.params().depth();
328 if (type == LyXText::INC_DEPTH && depth < max_depth)
330 if (type == LyXText::DEC_DEPTH && depth > 0)
339 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
341 BOOST_ASSERT(this == cur.text());
343 getSelectionSpan(cur, beg, end);
346 max_depth = pars_[beg - 1].getMaxDepthAfter();
348 for (par_type pit = beg; pit != end; ++pit) {
349 if (::changeDepthAllowed(type, pars_[pit], max_depth))
351 max_depth = pars_[pit].getMaxDepthAfter();
357 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
359 BOOST_ASSERT(this == cur.text());
361 getSelectionSpan(cur, beg, end);
362 recordUndoSelection(cur);
366 max_depth = pars_[beg - 1].getMaxDepthAfter();
368 for (par_type pit = beg; pit != end; ++pit) {
369 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
370 int const depth = pars_[pit].params().depth();
371 if (type == INC_DEPTH)
372 pars_[pit].params().depth(depth + 1);
374 pars_[pit].params().depth(depth - 1);
376 max_depth = pars_[pit].getMaxDepthAfter();
378 // this handles the counter labels, and also fixes up
379 // depth values for follow-on (child) paragraphs
384 // set font over selection
385 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
387 BOOST_ASSERT(this == cur.text());
388 // if there is no selection just set the current_font
389 if (!cur.selection()) {
390 // Determine basis font
392 par_type pit = cur.par();
393 if (cur.pos() < pars_[pit].beginOfBody())
394 layoutfont = getLabelFont(pit);
396 layoutfont = getLayoutFont(pit);
398 // Update current font
399 real_current_font.update(font,
400 cur.buffer().params().language,
403 // Reduce to implicit settings
404 current_font = real_current_font;
405 current_font.reduce(layoutfont);
406 // And resolve it completely
407 real_current_font.realize(layoutfont);
412 // Ok, we have a selection.
413 recordUndoSelection(cur);
415 par_type const beg = cur.selBegin().par();
416 par_type const end = cur.selEnd().par();
418 DocIterator pos = cur.selectionBegin();
419 DocIterator posend = cur.selectionEnd();
421 lyxerr << "pos: " << pos << " posend: " << posend << endl;
423 BufferParams const & params = cur.buffer().params();
425 // Don't use forwardChar here as posend might have
426 // pos() == lastpos() and forwardChar would miss it.
427 for (; pos != posend; pos.forwardPos()) {
428 if (pos.pos() != pos.lastpos()) {
429 LyXFont f = getFont(pos.par(), pos.pos());
430 f.update(font, params.language, toggleall);
431 setCharFont(pos.par(), pos.pos(), f);
435 redoParagraphs(beg, end + 1);
439 // the cursor set functions have a special mechanism. When they
440 // realize you left an empty paragraph, they will delete it.
442 void LyXText::cursorHome(LCursor & cur)
444 BOOST_ASSERT(this == cur.text());
445 setCursor(cur, cur.par(), cur.textRow().pos());
449 void LyXText::cursorEnd(LCursor & cur)
451 BOOST_ASSERT(this == cur.text());
452 // if not on the last row of the par, put the cursor before
454 pos_type const end = cur.textRow().endpos();
455 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
459 void LyXText::cursorTop(LCursor & cur)
461 BOOST_ASSERT(this == cur.text());
462 setCursor(cur, 0, 0);
466 void LyXText::cursorBottom(LCursor & cur)
468 BOOST_ASSERT(this == cur.text());
469 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
473 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
475 BOOST_ASSERT(this == cur.text());
476 // If the mask is completely neutral, tell user
477 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
478 // Could only happen with user style
479 cur.message(_("No font change defined. "
480 "Use Character under the Layout menu to define font change."));
484 // Try implicit word selection
485 // If there is a change in the language the implicit word selection
487 CursorSlice resetCursor = cur.top();
488 bool implicitSelection =
489 font.language() == ignore_language
490 && font.number() == LyXFont::IGNORE
491 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
494 setFont(cur, font, toggleall);
496 // Implicit selections are cleared afterwards
497 // and cursor is set to the original position.
498 if (implicitSelection) {
499 cur.clearSelection();
500 cur.top() = resetCursor;
506 string LyXText::getStringToIndex(LCursor & cur)
508 BOOST_ASSERT(this == cur.text());
509 // Try implicit word selection
510 // If there is a change in the language the implicit word selection
512 CursorSlice const reset_cursor = cur.top();
513 bool const implicitSelection =
514 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
517 if (!cur.selection())
518 cur.message(_("Nothing to index!"));
519 else if (cur.selBegin().par() != cur.selEnd().par())
520 cur.message(_("Cannot index more than one paragraph!"));
522 idxstring = cur.selectionAsString(false);
524 // Reset cursors to their original position.
525 cur.top() = reset_cursor;
528 // Clear the implicit selection.
529 if (implicitSelection)
530 cur.clearSelection();
536 void LyXText::setParagraph(LCursor & cur,
537 Spacing const & spacing, LyXAlignment align,
538 string const & labelwidthstring, bool noindent)
540 BOOST_ASSERT(cur.text());
541 // make sure that the depth behind the selection are restored, too
542 par_type undopit = undoSpan(cur.selEnd().par());
543 recUndo(cur.selBegin().par(), undopit - 1);
545 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
547 Paragraph & par = pars_[pit];
548 ParagraphParameters & params = par.params();
549 params.spacing(spacing);
551 // does the layout allow the new alignment?
552 LyXLayout_ptr const & layout = par.layout();
554 if (align == LYX_ALIGN_LAYOUT)
555 align = layout->align;
556 if (align & layout->alignpossible) {
557 if (align == layout->align)
558 params.align(LYX_ALIGN_LAYOUT);
562 par.setLabelWidthString(labelwidthstring);
563 params.noindent(noindent);
566 redoParagraphs(cur.selBegin().par(), undopit);
570 string expandLabel(LyXTextClass const & textclass,
571 LyXLayout_ptr const & layout, bool appendix)
573 string fmt = appendix ?
574 layout->labelstring_appendix() : layout->labelstring();
576 // handle 'inherited level parts' in 'fmt',
577 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
578 size_t const i = fmt.find('@', 0);
579 if (i != string::npos) {
580 size_t const j = fmt.find('@', i + 1);
581 if (j != string::npos) {
582 string parent(fmt, i + 1, j - i - 1);
583 string label = expandLabel(textclass, textclass[parent], appendix);
584 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
588 return textclass.counters().counterLabel(fmt);
594 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
596 int const cur_labeltype = pars[pit].layout()->labeltype;
598 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
601 int const cur_depth = pars[pit].getDepth();
603 par_type prev_pit = pit - 1;
605 int const prev_depth = pars[prev_pit].getDepth();
606 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
607 if (prev_depth == 0 && cur_depth > 0) {
608 if (prev_labeltype == cur_labeltype) {
609 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
612 } else if (prev_depth < cur_depth) {
613 if (prev_labeltype == cur_labeltype) {
614 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
617 } else if (prev_depth == cur_depth) {
618 if (prev_labeltype == cur_labeltype) {
619 pars[pit].itemdepth = pars[prev_pit].itemdepth;
623 if (prev_pit == first_pit)
631 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
632 par_type firstpit, Counters & counters)
637 int const cur_depth = pars[pit].getDepth();
638 par_type prev_pit = pit - 1;
640 int const prev_depth = pars[prev_pit].getDepth();
641 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
642 if (prev_depth <= cur_depth) {
643 if (prev_labeltype != LABEL_ENUMERATE) {
644 switch (pars[pit].itemdepth) {
646 counters.reset("enumi");
648 counters.reset("enumii");
650 counters.reset("enumiii");
652 counters.reset("enumiv");
658 if (prev_pit == firstpit)
668 // set the counter of a paragraph. This includes the labels
669 void LyXText::setCounter(Buffer const & buf, par_type pit)
671 BufferParams const & bufparams = buf.params();
672 LyXTextClass const & textclass = bufparams.getLyXTextClass();
673 LyXLayout_ptr const & layout = pars_[pit].layout();
674 par_type first_pit = 0;
675 Counters & counters = textclass.counters();
678 pars_[pit].itemdepth = 0;
680 if (pit == first_pit) {
681 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
683 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
684 if (!pars_[pit].params().appendix() &&
685 pars_[pit].params().startOfAppendix()) {
686 pars_[pit].params().appendix(true);
687 textclass.counters().reset();
690 // Maybe we have to increment the item depth.
691 incrementItemDepth(pars_, pit, first_pit);
694 // erase what was there before
695 pars_[pit].params().labelString(string());
697 if (layout->margintype == MARGIN_MANUAL) {
698 if (pars_[pit].params().labelWidthString().empty())
699 pars_[pit].setLabelWidthString(layout->labelstring());
701 pars_[pit].setLabelWidthString(string());
704 // is it a layout that has an automatic label?
705 if (layout->labeltype == LABEL_COUNTER) {
706 BufferParams const & bufparams = buf.params();
707 LyXTextClass const & textclass = bufparams.getLyXTextClass();
708 counters.step(layout->counter);
709 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
710 pars_[pit].params().labelString(label);
711 } else if (layout->labeltype == LABEL_ITEMIZE) {
712 // At some point of time we should do something more
713 // clever here, like:
714 // pars_[pit].params().labelString(
715 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
716 // for now, use a simple hardcoded label
718 switch (pars_[pit].itemdepth) {
733 pars_[pit].params().labelString(itemlabel);
734 } else if (layout->labeltype == LABEL_ENUMERATE) {
735 // Maybe we have to reset the enumeration counter.
736 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
739 // Yes I know this is a really, really! bad solution
741 string enumcounter = "enum";
743 switch (pars_[pit].itemdepth) {
755 // not a valid enumdepth...
759 counters.step(enumcounter);
761 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
762 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
763 counters.step("bibitem");
764 int number = counters.value("bibitem");
765 if (pars_[pit].bibitem()) {
766 pars_[pit].bibitem()->setCounter(number);
767 pars_[pit].params().labelString(layout->labelstring());
769 // In biblio should't be following counters but...
771 string s = buf.B_(layout->labelstring());
774 if (layout->labeltype == LABEL_SENSITIVE) {
775 par_type end = paragraphs().size();
776 par_type tmppit = pit;
779 while (tmppit != end) {
780 in = pars_[tmppit].inInset();
781 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
782 in->lyxCode() == InsetBase::WRAP_CODE) {
786 Paragraph const * owner = &ownerPar(buf, in);
788 for ( ; tmppit != end; ++tmppit)
789 if (&pars_[tmppit] == owner)
797 if (in->lyxCode() == InsetBase::FLOAT_CODE)
798 type = static_cast<InsetFloat*>(in)->params().type;
799 else if (in->lyxCode() == InsetBase::WRAP_CODE)
800 type = static_cast<InsetWrap*>(in)->params().type;
804 Floating const & fl = textclass.floats().getType(type);
806 counters.step(fl.type());
808 // Doesn't work... yet.
809 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
811 // par->SetLayout(0);
812 // s = layout->labelstring;
813 s = _("Senseless: ");
816 pars_[pit].params().labelString(s);
822 // Updates all counters.
823 void LyXText::updateCounters()
826 bv()->buffer()->params().getLyXTextClass().counters().reset();
828 bool update_pos = false;
830 par_type end = paragraphs().size();
831 for (par_type pit = 0; pit != end; ++pit) {
832 string const oldLabel = pars_[pit].params().labelString();
835 maxdepth = pars_[pit - 1].getMaxDepthAfter();
837 if (pars_[pit].params().depth() > maxdepth)
838 pars_[pit].params().depth(maxdepth);
840 // setCounter can potentially change the labelString.
841 setCounter(*bv()->buffer(), pit);
842 string const & newLabel = pars_[pit].params().labelString();
843 if (oldLabel != newLabel) {
844 //lyxerr << "changing labels: old: " << oldLabel << " new: "
845 // << newLabel << endl;
846 redoParagraphInternal(pit);
851 updateParPositions();
855 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
857 BOOST_ASSERT(this == cur.text());
859 cur.paragraph().insertInset(cur.pos(), inset);
864 // needed to insert the selection
865 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
867 par_type pit = cur.par();
868 par_type endpit = cur.par() + 1;
869 pos_type pos = cur.pos();
872 // only to be sure, should not be neccessary
873 cur.clearSelection();
874 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
876 redoParagraphs(cur.par(), endpit);
878 setCursor(cur, cur.par(), pos);
883 // turn double CR to single CR, others are converted into one
884 // blank. Then insertStringAsLines is called
885 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
887 string linestr = str;
888 bool newline_inserted = false;
890 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
891 if (linestr[i] == '\n') {
892 if (newline_inserted) {
893 // we know that \r will be ignored by
894 // insertStringAsLines. Of course, it is a dirty
895 // trick, but it works...
896 linestr[i - 1] = '\r';
900 newline_inserted = true;
902 } else if (IsPrintable(linestr[i])) {
903 newline_inserted = false;
906 insertStringAsLines(cur, linestr);
910 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
911 bool setfont, bool boundary)
914 setCursorIntern(cur, par, pos, setfont, boundary);
915 return deleteEmptyParagraphMechanism(cur, old);
919 void LyXText::setCursor(CursorSlice & cur, par_type par,
920 pos_type pos, bool boundary)
922 BOOST_ASSERT(par != int(paragraphs().size()));
926 cur.boundary() = boundary;
928 // no rows, no fun...
929 if (paragraphs().begin()->rows.empty())
932 // now some strict checking
933 Paragraph & para = getPar(par);
934 Row const & row = *para.getRow(pos);
935 pos_type const end = row.endpos();
937 // None of these should happen, but we're scaredy-cats
939 lyxerr << "dont like -1" << endl;
943 if (pos > para.size()) {
944 lyxerr << "dont like 1, pos: " << pos
945 << " size: " << para.size()
946 << " row.pos():" << row.pos()
947 << " par: " << par << endl;
952 lyxerr << "dont like 2, pos: " << pos
953 << " size: " << para.size()
954 << " row.pos():" << row.pos()
955 << " par: " << par << endl;
956 // This shouldn't happen.
960 if (pos < row.pos()) {
961 lyxerr << "dont like 3 please report pos:" << pos
962 << " size: " << para.size()
963 << " row.pos():" << row.pos()
964 << " par: " << par << endl;
970 void LyXText::setCursorIntern(LCursor & cur,
971 par_type par, pos_type pos, bool setfont, bool boundary)
973 setCursor(cur.top(), par, pos, boundary);
974 cur.x_target() = cursorX(cur.top());
980 void LyXText::setCurrentFont(LCursor & cur)
982 BOOST_ASSERT(this == cur.text());
983 pos_type pos = cur.pos();
984 par_type pit = cur.par();
986 if (cur.boundary() && pos > 0)
990 if (pos == cur.lastpos())
992 else // potentional bug... BUG (Lgb)
993 if (pars_[pit].isSeparator(pos)) {
994 if (pos > cur.textRow().pos() &&
995 bidi.level(pos) % 2 ==
996 bidi.level(pos - 1) % 2)
998 else if (pos + 1 < cur.lastpos())
1003 BufferParams const & bufparams = cur.buffer().params();
1004 current_font = pars_[pit].getFontSettings(bufparams, pos);
1005 real_current_font = getFont(pit, pos);
1007 if (cur.pos() == cur.lastpos()
1008 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1009 && !cur.boundary()) {
1010 Language const * lang = pars_[pit].getParLanguage(bufparams);
1011 current_font.setLanguage(lang);
1012 current_font.setNumber(LyXFont::OFF);
1013 real_current_font.setLanguage(lang);
1014 real_current_font.setNumber(LyXFont::OFF);
1019 // x is an absolute screen coord
1020 // returns the column near the specified x-coordinate of the row
1021 // x is set to the real beginning of this column
1022 pos_type LyXText::getColumnNearX(par_type pit,
1023 Row const & row, int & x, bool & boundary) const
1026 RowMetrics const r = computeRowMetrics(pit, row);
1028 pos_type vc = row.pos();
1029 pos_type end = row.endpos();
1031 LyXLayout_ptr const & layout = pars_[pit].layout();
1033 bool left_side = false;
1035 pos_type body_pos = pars_[pit].beginOfBody();
1038 double last_tmpx = tmpx;
1041 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1044 // check for empty row
1046 x = int(tmpx) + xo_;
1050 while (vc < end && tmpx <= x) {
1051 c = bidi.vis2log(vc);
1053 if (body_pos > 0 && c == body_pos - 1) {
1054 tmpx += r.label_hfill +
1055 font_metrics::width(layout->labelsep, getLabelFont(pit));
1056 if (pars_[pit].isLineSeparator(body_pos - 1))
1057 tmpx -= singleWidth(pit, body_pos - 1);
1060 if (hfillExpansion(pars_[pit], row, c)) {
1061 tmpx += singleWidth(pit, c);
1065 tmpx += r.label_hfill;
1066 } else if (pars_[pit].isSeparator(c)) {
1067 tmpx += singleWidth(pit, c);
1069 tmpx += r.separator;
1071 tmpx += singleWidth(pit, c);
1076 if ((tmpx + last_tmpx) / 2 > x) {
1081 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1084 // This (rtl_support test) is not needed, but gives
1085 // some speedup if rtl_support == false
1086 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1088 // If lastrow is false, we don't need to compute
1089 // the value of rtl.
1090 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1092 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1093 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1095 else if (vc == row.pos()) {
1096 c = bidi.vis2log(vc);
1097 if (bidi.level(c) % 2 == 1)
1100 c = bidi.vis2log(vc - 1);
1101 bool const rtl = (bidi.level(c) % 2 == 1);
1102 if (left_side == rtl) {
1104 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1108 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1109 if (bidi.level(end -1) % 2 == 0)
1110 tmpx -= singleWidth(pit, end - 1);
1112 tmpx += singleWidth(pit, end - 1);
1116 x = int(tmpx) + xo_;
1117 return c - row.pos();
1121 // x,y are absolute coordinates
1122 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1127 Row const & row = getRowNearY(y, pit);
1128 lyxerr << "setCursorFromCoordinates:: hit row at: " << row.pos() << endl;
1130 int xx = x + xo_; // getRowNearX get absolute x coords
1131 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1132 setCursor(cur, pit, pos, true, bound);
1136 // x,y are absolute screen coordinates
1137 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1140 Row const & row = getRowNearY(y - yo_, pit);
1143 int xx = x; // is modified by getColumnNearX
1144 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1147 cur.boundary() = bound;
1149 // try to descend into nested insets
1150 InsetBase * inset = checkInsetHit(x, y);
1151 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1155 // This should be just before or just behind the
1156 // cursor position set above.
1157 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1158 || inset == pars_[pit].getInset(pos));
1159 // Make sure the cursor points to the position before
1161 if (inset == pars_[pit].getInset(pos - 1))
1163 return inset->editXY(cur, x, y);
1167 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1169 if (cur.selection())
1171 if (cur.pos() == cur.lastpos())
1173 InsetBase * inset = cur.nextInset();
1174 if (!isHighlyEditableInset(inset))
1176 inset->edit(cur, front);
1181 void LyXText::cursorLeft(LCursor & cur)
1183 if (cur.pos() != 0) {
1184 bool boundary = cur.boundary();
1185 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1186 if (!checkAndActivateInset(cur, false)) {
1187 if (false && !boundary &&
1188 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1189 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1194 if (cur.par() != 0) {
1195 // steps into the paragraph above
1196 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1201 void LyXText::cursorRight(LCursor & cur)
1203 if (false && cur.boundary()) {
1204 setCursor(cur, cur.par(), cur.pos(), true, false);
1208 if (cur.pos() != cur.lastpos()) {
1209 if (!checkAndActivateInset(cur, true)) {
1210 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1211 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1213 setCursor(cur, cur.par(), cur.pos(), true, true);
1218 if (cur.par() != cur.lastpar())
1219 setCursor(cur, cur.par() + 1, 0);
1223 void LyXText::cursorUp(LCursor & cur)
1225 Row const & row = cur.textRow();
1226 int x = cur.x_target();
1227 int y = cursorY(cur.top()) - row.baseline() - 1;
1228 setCursorFromCoordinates(cur, x, y);
1230 if (!cur.selection()) {
1231 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1232 if (inset_hit && isHighlyEditableInset(inset_hit))
1233 inset_hit->editXY(cur, cur.x_target(), y);
1238 void LyXText::cursorDown(LCursor & cur)
1240 Row const & row = cur.textRow();
1241 int x = cur.x_target();
1242 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1243 setCursorFromCoordinates(cur, x, y);
1245 if (!cur.selection()) {
1246 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1247 if (inset_hit && isHighlyEditableInset(inset_hit))
1248 inset_hit->editXY(cur, cur.x_target(), y);
1253 void LyXText::cursorUpParagraph(LCursor & cur)
1256 setCursor(cur, cur.par(), 0);
1257 else if (cur.par() != 0)
1258 setCursor(cur, cur.par() - 1, 0);
1262 void LyXText::cursorDownParagraph(LCursor & cur)
1264 if (cur.par() != cur.lastpar())
1265 setCursor(cur, cur.par() + 1, 0);
1267 setCursor(cur, cur.par(), cur.lastpos());
1271 // fix the cursor `cur' after a characters has been deleted at `where'
1272 // position. Called by deleteEmptyParagraphMechanism
1273 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1275 // do notheing if cursor is not in the paragraph where the
1276 // deletion occured,
1277 if (cur.par() != where.par())
1280 // if cursor position is after the deletion place update it
1281 if (cur.pos() > where.pos())
1284 // check also if we don't want to set the cursor on a spot behind the
1285 // pagragraph because we erased the last character.
1286 if (cur.pos() > cur.lastpos())
1287 cur.pos() = cur.lastpos();
1291 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1293 BOOST_ASSERT(cur.size() == old.size());
1294 // Would be wrong to delete anything if we have a selection.
1295 if (cur.selection())
1298 //lyxerr << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1299 Paragraph const & oldpar = pars_[old.par()];
1301 // We allow all kinds of "mumbo-jumbo" when freespacing.
1302 if (oldpar.isFreeSpacing())
1305 /* Ok I'll put some comments here about what is missing.
1306 I have fixed BackSpace (and thus Delete) to not delete
1307 double-spaces automagically. I have also changed Cut,
1308 Copy and Paste to hopefully do some sensible things.
1309 There are still some small problems that can lead to
1310 double spaces stored in the document file or space at
1311 the beginning of paragraphs(). This happens if you have
1312 the cursor between to spaces and then save. Or if you
1313 cut and paste and the selection have a space at the
1314 beginning and then save right after the paste. I am
1315 sure none of these are very hard to fix, but I will
1316 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1317 that I can get some feedback. (Lgb)
1320 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1321 // delete the LineSeparator.
1324 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1325 // delete the LineSeparator.
1328 // If the chars around the old cursor were spaces, delete one of them.
1329 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1331 // Only if the cursor has really moved.
1333 && old.pos() < oldpar.size()
1334 && oldpar.isLineSeparator(old.pos())
1335 && oldpar.isLineSeparator(old.pos() - 1)) {
1336 pars_[old.par()].erase(old.pos() - 1);
1337 #ifdef WITH_WARNINGS
1338 #warning This will not work anymore when we have multiple views of the same buffer
1339 // In this case, we will have to correct also the cursors held by
1340 // other bufferviews. It will probably be easier to do that in a more
1341 // automated way in CursorSlice code. (JMarc 26/09/2001)
1343 // correct all cursor parts
1344 fixCursorAfterDelete(cur.top(), old.top());
1345 #warning DEPM, look here
1346 //fixCursorAfterDelete(cur.anchor(), old.top());
1351 // only do our magic if we changed paragraph
1352 if (old.par() == cur.par())
1355 // don't delete anything if this is the ONLY paragraph!
1356 if (pars_.size() == 1)
1359 // Do not delete empty paragraphs with keepempty set.
1360 if (oldpar.allowEmpty())
1363 // record if we have deleted a paragraph
1364 // we can't possibly have deleted a paragraph before this point
1365 bool deleted = false;
1367 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1368 // ok, we will delete something
1369 CursorSlice tmpcursor;
1373 bool selection_position_was_oldcursor_position =
1374 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1376 // This is a bit of a overkill. We change the old and the cur par
1377 // at max, certainly not everything in between...
1378 recUndo(old.par(), cur.par());
1381 pars_.erase(pars_.begin() + old.par());
1383 // Update cursor par offset if necessary.
1384 // Some 'iterator registration' would be nice that takes care of
1385 // such events. Maybe even signal/slot?
1386 if (cur.par() > old.par())
1388 #warning DEPM, look here
1389 // if (cur.anchor().par() > old.par())
1390 // --cur.anchor().par();
1392 if (selection_position_was_oldcursor_position) {
1393 // correct selection
1401 if (pars_[old.par()].stripLeadingSpaces())
1408 ParagraphList & LyXText::paragraphs() const
1410 return const_cast<ParagraphList &>(pars_);
1414 void LyXText::recUndo(par_type first, par_type last) const
1416 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1420 void LyXText::recUndo(par_type par) const
1422 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1426 int defaultRowHeight()
1428 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);