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[Debug::DEBUG] << "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[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
424 BufferParams const & params = cur.buffer().params();
426 // Don't use forwardChar here as posend might have
427 // pos() == lastpos() and forwardChar would miss it.
428 for (; pos != posend; pos.forwardPos()) {
429 if (pos.pos() != pos.lastpos()) {
430 LyXFont f = getFont(pos.par(), pos.pos());
431 f.update(font, params.language, toggleall);
432 setCharFont(pos.par(), pos.pos(), f);
436 redoParagraphs(beg, end + 1);
440 // the cursor set functions have a special mechanism. When they
441 // realize you left an empty paragraph, they will delete it.
443 void LyXText::cursorHome(LCursor & cur)
445 BOOST_ASSERT(this == cur.text());
446 setCursor(cur, cur.par(), cur.textRow().pos());
450 void LyXText::cursorEnd(LCursor & cur)
452 BOOST_ASSERT(this == cur.text());
453 // if not on the last row of the par, put the cursor before
455 pos_type const end = cur.textRow().endpos();
456 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
460 void LyXText::cursorTop(LCursor & cur)
462 BOOST_ASSERT(this == cur.text());
463 setCursor(cur, 0, 0);
467 void LyXText::cursorBottom(LCursor & cur)
469 BOOST_ASSERT(this == cur.text());
470 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
474 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
476 BOOST_ASSERT(this == cur.text());
477 // If the mask is completely neutral, tell user
478 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
479 // Could only happen with user style
480 cur.message(_("No font change defined. "
481 "Use Character under the Layout menu to define font change."));
485 // Try implicit word selection
486 // If there is a change in the language the implicit word selection
488 CursorSlice resetCursor = cur.top();
489 bool implicitSelection =
490 font.language() == ignore_language
491 && font.number() == LyXFont::IGNORE
492 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
495 setFont(cur, font, toggleall);
497 // Implicit selections are cleared afterwards
498 // and cursor is set to the original position.
499 if (implicitSelection) {
500 cur.clearSelection();
501 cur.top() = resetCursor;
507 string LyXText::getStringToIndex(LCursor & cur)
509 BOOST_ASSERT(this == cur.text());
510 // Try implicit word selection
511 // If there is a change in the language the implicit word selection
513 CursorSlice const reset_cursor = cur.top();
514 bool const implicitSelection =
515 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
518 if (!cur.selection())
519 cur.message(_("Nothing to index!"));
520 else if (cur.selBegin().par() != cur.selEnd().par())
521 cur.message(_("Cannot index more than one paragraph!"));
523 idxstring = cur.selectionAsString(false);
525 // Reset cursors to their original position.
526 cur.top() = reset_cursor;
529 // Clear the implicit selection.
530 if (implicitSelection)
531 cur.clearSelection();
537 void LyXText::setParagraph(LCursor & cur,
538 Spacing const & spacing, LyXAlignment align,
539 string const & labelwidthstring, bool noindent)
541 BOOST_ASSERT(cur.text());
542 // make sure that the depth behind the selection are restored, too
543 par_type undopit = undoSpan(cur.selEnd().par());
544 recUndo(cur.selBegin().par(), undopit - 1);
546 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
548 Paragraph & par = pars_[pit];
549 ParagraphParameters & params = par.params();
550 params.spacing(spacing);
552 // does the layout allow the new alignment?
553 LyXLayout_ptr const & layout = par.layout();
555 if (align == LYX_ALIGN_LAYOUT)
556 align = layout->align;
557 if (align & layout->alignpossible) {
558 if (align == layout->align)
559 params.align(LYX_ALIGN_LAYOUT);
563 par.setLabelWidthString(labelwidthstring);
564 params.noindent(noindent);
567 redoParagraphs(cur.selBegin().par(), undopit);
571 string expandLabel(LyXTextClass const & textclass,
572 LyXLayout_ptr const & layout, bool appendix)
574 string fmt = appendix ?
575 layout->labelstring_appendix() : layout->labelstring();
577 // handle 'inherited level parts' in 'fmt',
578 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
579 size_t const i = fmt.find('@', 0);
580 if (i != string::npos) {
581 size_t const j = fmt.find('@', i + 1);
582 if (j != string::npos) {
583 string parent(fmt, i + 1, j - i - 1);
584 string label = expandLabel(textclass, textclass[parent], appendix);
585 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
589 return textclass.counters().counterLabel(fmt);
595 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
597 int const cur_labeltype = pars[pit].layout()->labeltype;
599 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
602 int const cur_depth = pars[pit].getDepth();
604 par_type prev_pit = pit - 1;
606 int const prev_depth = pars[prev_pit].getDepth();
607 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
608 if (prev_depth == 0 && cur_depth > 0) {
609 if (prev_labeltype == cur_labeltype) {
610 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
613 } else if (prev_depth < cur_depth) {
614 if (prev_labeltype == cur_labeltype) {
615 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
618 } else if (prev_depth == cur_depth) {
619 if (prev_labeltype == cur_labeltype) {
620 pars[pit].itemdepth = pars[prev_pit].itemdepth;
624 if (prev_pit == first_pit)
632 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
633 par_type firstpit, Counters & counters)
638 int const cur_depth = pars[pit].getDepth();
639 par_type prev_pit = pit - 1;
641 int const prev_depth = pars[prev_pit].getDepth();
642 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
643 if (prev_depth <= cur_depth) {
644 if (prev_labeltype != LABEL_ENUMERATE) {
645 switch (pars[pit].itemdepth) {
647 counters.reset("enumi");
649 counters.reset("enumii");
651 counters.reset("enumiii");
653 counters.reset("enumiv");
659 if (prev_pit == firstpit)
669 // set the counter of a paragraph. This includes the labels
670 void LyXText::setCounter(Buffer const & buf, par_type pit)
672 BufferParams const & bufparams = buf.params();
673 LyXTextClass const & textclass = bufparams.getLyXTextClass();
674 LyXLayout_ptr const & layout = pars_[pit].layout();
675 par_type first_pit = 0;
676 Counters & counters = textclass.counters();
679 pars_[pit].itemdepth = 0;
681 if (pit == first_pit) {
682 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
684 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
685 if (!pars_[pit].params().appendix() &&
686 pars_[pit].params().startOfAppendix()) {
687 pars_[pit].params().appendix(true);
688 textclass.counters().reset();
691 // Maybe we have to increment the item depth.
692 incrementItemDepth(pars_, pit, first_pit);
695 // erase what was there before
696 pars_[pit].params().labelString(string());
698 if (layout->margintype == MARGIN_MANUAL) {
699 if (pars_[pit].params().labelWidthString().empty())
700 pars_[pit].setLabelWidthString(layout->labelstring());
702 pars_[pit].setLabelWidthString(string());
705 // is it a layout that has an automatic label?
706 if (layout->labeltype == LABEL_COUNTER) {
707 BufferParams const & bufparams = buf.params();
708 LyXTextClass const & textclass = bufparams.getLyXTextClass();
709 counters.step(layout->counter);
710 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
711 pars_[pit].params().labelString(label);
712 } else if (layout->labeltype == LABEL_ITEMIZE) {
713 // At some point of time we should do something more
714 // clever here, like:
715 // pars_[pit].params().labelString(
716 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
717 // for now, use a simple hardcoded label
719 switch (pars_[pit].itemdepth) {
734 pars_[pit].params().labelString(itemlabel);
735 } else if (layout->labeltype == LABEL_ENUMERATE) {
736 // Maybe we have to reset the enumeration counter.
737 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
740 // Yes I know this is a really, really! bad solution
742 string enumcounter = "enum";
744 switch (pars_[pit].itemdepth) {
756 // not a valid enumdepth...
760 counters.step(enumcounter);
762 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
763 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
764 counters.step("bibitem");
765 int number = counters.value("bibitem");
766 if (pars_[pit].bibitem()) {
767 pars_[pit].bibitem()->setCounter(number);
768 pars_[pit].params().labelString(layout->labelstring());
770 // In biblio should't be following counters but...
772 string s = buf.B_(layout->labelstring());
775 if (layout->labeltype == LABEL_SENSITIVE) {
776 par_type end = paragraphs().size();
777 par_type tmppit = pit;
780 while (tmppit != end) {
781 in = pars_[tmppit].inInset();
782 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
783 in->lyxCode() == InsetBase::WRAP_CODE) {
787 Paragraph const * owner = &ownerPar(buf, in);
789 for ( ; tmppit != end; ++tmppit)
790 if (&pars_[tmppit] == owner)
798 if (in->lyxCode() == InsetBase::FLOAT_CODE)
799 type = static_cast<InsetFloat*>(in)->params().type;
800 else if (in->lyxCode() == InsetBase::WRAP_CODE)
801 type = static_cast<InsetWrap*>(in)->params().type;
805 Floating const & fl = textclass.floats().getType(type);
807 counters.step(fl.type());
809 // Doesn't work... yet.
810 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
812 // par->SetLayout(0);
813 // s = layout->labelstring;
814 s = _("Senseless: ");
817 pars_[pit].params().labelString(s);
823 // Updates all counters.
824 void LyXText::updateCounters()
827 bv()->buffer()->params().getLyXTextClass().counters().reset();
829 bool update_pos = false;
831 par_type end = paragraphs().size();
832 for (par_type pit = 0; pit != end; ++pit) {
833 string const oldLabel = pars_[pit].params().labelString();
836 maxdepth = pars_[pit - 1].getMaxDepthAfter();
838 if (pars_[pit].params().depth() > maxdepth)
839 pars_[pit].params().depth(maxdepth);
841 // setCounter can potentially change the labelString.
842 setCounter(*bv()->buffer(), pit);
843 string const & newLabel = pars_[pit].params().labelString();
844 if (oldLabel != newLabel) {
845 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
846 // << newLabel << endl;
847 redoParagraphInternal(pit);
852 updateParPositions();
856 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
858 BOOST_ASSERT(this == cur.text());
860 cur.paragraph().insertInset(cur.pos(), inset);
862 setCursor(cur, cur.par(), cur.pos() + 1, false, cur.boundary());
866 // needed to insert the selection
867 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
869 par_type pit = cur.par();
870 par_type endpit = cur.par() + 1;
871 pos_type pos = cur.pos();
874 // only to be sure, should not be neccessary
875 cur.clearSelection();
876 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
878 redoParagraphs(cur.par(), endpit);
880 setCursor(cur, cur.par(), pos);
885 // turn double CR to single CR, others are converted into one
886 // blank. Then insertStringAsLines is called
887 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
889 string linestr = str;
890 bool newline_inserted = false;
892 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
893 if (linestr[i] == '\n') {
894 if (newline_inserted) {
895 // we know that \r will be ignored by
896 // insertStringAsLines. Of course, it is a dirty
897 // trick, but it works...
898 linestr[i - 1] = '\r';
902 newline_inserted = true;
904 } else if (IsPrintable(linestr[i])) {
905 newline_inserted = false;
908 insertStringAsLines(cur, linestr);
912 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
913 bool setfont, bool boundary)
916 setCursorIntern(cur, par, pos, setfont, boundary);
917 return deleteEmptyParagraphMechanism(cur, old);
921 void LyXText::setCursor(CursorSlice & cur, par_type par,
922 pos_type pos, bool boundary)
924 BOOST_ASSERT(par != int(paragraphs().size()));
928 cur.boundary() = boundary;
930 // no rows, no fun...
931 if (paragraphs().begin()->rows.empty())
934 // now some strict checking
935 Paragraph & para = getPar(par);
936 Row const & row = *para.getRow(pos);
937 pos_type const end = row.endpos();
939 // None of these should happen, but we're scaredy-cats
941 lyxerr << "dont like -1" << endl;
945 if (pos > para.size()) {
946 lyxerr << "dont like 1, pos: " << pos
947 << " size: " << para.size()
948 << " row.pos():" << row.pos()
949 << " par: " << par << endl;
954 lyxerr << "dont like 2, pos: " << pos
955 << " size: " << para.size()
956 << " row.pos():" << row.pos()
957 << " par: " << par << endl;
958 // This shouldn't happen.
962 if (pos < row.pos()) {
963 lyxerr << "dont like 3 please report pos:" << pos
964 << " size: " << para.size()
965 << " row.pos():" << row.pos()
966 << " par: " << par << endl;
972 void LyXText::setCursorIntern(LCursor & cur,
973 par_type par, pos_type pos, bool setfont, bool boundary)
975 setCursor(cur.top(), par, pos, boundary);
976 cur.x_target() = cursorX(cur.top());
982 void LyXText::setCurrentFont(LCursor & cur)
984 BOOST_ASSERT(this == cur.text());
985 pos_type pos = cur.pos();
986 par_type pit = cur.par();
988 if (cur.boundary() && pos > 0)
992 if (pos == cur.lastpos())
994 else // potentional bug... BUG (Lgb)
995 if (pars_[pit].isSeparator(pos)) {
996 if (pos > cur.textRow().pos() &&
997 bidi.level(pos) % 2 ==
998 bidi.level(pos - 1) % 2)
1000 else if (pos + 1 < cur.lastpos())
1005 BufferParams const & bufparams = cur.buffer().params();
1006 current_font = pars_[pit].getFontSettings(bufparams, pos);
1007 real_current_font = getFont(pit, pos);
1009 if (cur.pos() == cur.lastpos()
1010 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1011 && !cur.boundary()) {
1012 Language const * lang = pars_[pit].getParLanguage(bufparams);
1013 current_font.setLanguage(lang);
1014 current_font.setNumber(LyXFont::OFF);
1015 real_current_font.setLanguage(lang);
1016 real_current_font.setNumber(LyXFont::OFF);
1021 // x is an absolute screen coord
1022 // returns the column near the specified x-coordinate of the row
1023 // x is set to the real beginning of this column
1024 pos_type LyXText::getColumnNearX(par_type pit,
1025 Row const & row, int & x, bool & boundary) const
1028 RowMetrics const r = computeRowMetrics(pit, row);
1030 pos_type vc = row.pos();
1031 pos_type end = row.endpos();
1033 LyXLayout_ptr const & layout = pars_[pit].layout();
1035 bool left_side = false;
1037 pos_type body_pos = pars_[pit].beginOfBody();
1040 double last_tmpx = tmpx;
1043 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1046 // check for empty row
1048 x = int(tmpx) + xo_;
1052 while (vc < end && tmpx <= x) {
1053 c = bidi.vis2log(vc);
1055 if (body_pos > 0 && c == body_pos - 1) {
1056 tmpx += r.label_hfill +
1057 font_metrics::width(layout->labelsep, getLabelFont(pit));
1058 if (pars_[pit].isLineSeparator(body_pos - 1))
1059 tmpx -= singleWidth(pit, body_pos - 1);
1062 if (hfillExpansion(pars_[pit], row, c)) {
1063 tmpx += singleWidth(pit, c);
1067 tmpx += r.label_hfill;
1068 } else if (pars_[pit].isSeparator(c)) {
1069 tmpx += singleWidth(pit, c);
1071 tmpx += r.separator;
1073 tmpx += singleWidth(pit, c);
1078 if ((tmpx + last_tmpx) / 2 > x) {
1083 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1086 // This (rtl_support test) is not needed, but gives
1087 // some speedup if rtl_support == false
1088 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1090 // If lastrow is false, we don't need to compute
1091 // the value of rtl.
1092 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1094 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1095 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1097 else if (vc == row.pos()) {
1098 c = bidi.vis2log(vc);
1099 if (bidi.level(c) % 2 == 1)
1102 c = bidi.vis2log(vc - 1);
1103 bool const rtl = (bidi.level(c) % 2 == 1);
1104 if (left_side == rtl) {
1106 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1110 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1111 if (bidi.level(end -1) % 2 == 0)
1112 tmpx -= singleWidth(pit, end - 1);
1114 tmpx += singleWidth(pit, end - 1);
1118 x = int(tmpx) + xo_;
1119 return c - row.pos();
1123 // x,y are absolute coordinates
1124 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1129 Row const & row = getRowNearY(y, pit);
1130 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1131 << row.pos() << endl;
1133 int xx = x + xo_; // getRowNearX get absolute x coords
1134 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1135 setCursor(cur, pit, pos, true, bound);
1139 // x,y are absolute screen coordinates
1140 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1143 Row const & row = getRowNearY(y - yo_, pit);
1146 int xx = x; // is modified by getColumnNearX
1147 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1150 cur.boundary() = bound;
1152 // try to descend into nested insets
1153 InsetBase * inset = checkInsetHit(x, y);
1154 lyxerr[Debug::DEBUG] << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1158 // This should be just before or just behind the
1159 // cursor position set above.
1160 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1161 || inset == pars_[pit].getInset(pos));
1162 // Make sure the cursor points to the position before
1164 if (inset == pars_[pit].getInset(pos - 1))
1166 return inset->editXY(cur, x, y);
1170 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1172 if (cur.selection())
1174 if (cur.pos() == cur.lastpos())
1176 InsetBase * inset = cur.nextInset();
1177 if (!isHighlyEditableInset(inset))
1179 inset->edit(cur, front);
1184 void LyXText::cursorLeft(LCursor & cur)
1186 if (cur.pos() != 0) {
1187 bool boundary = cur.boundary();
1188 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1189 if (!checkAndActivateInset(cur, false)) {
1190 if (false && !boundary &&
1191 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1192 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1197 if (cur.par() != 0) {
1198 // steps into the paragraph above
1199 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1204 void LyXText::cursorRight(LCursor & cur)
1206 if (false && cur.boundary()) {
1207 setCursor(cur, cur.par(), cur.pos(), true, false);
1211 if (cur.pos() != cur.lastpos()) {
1212 if (!checkAndActivateInset(cur, true)) {
1213 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1214 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1216 setCursor(cur, cur.par(), cur.pos(), true, true);
1221 if (cur.par() != cur.lastpar())
1222 setCursor(cur, cur.par() + 1, 0);
1226 void LyXText::cursorUp(LCursor & cur)
1228 Row const & row = cur.textRow();
1229 int x = cur.x_target();
1230 int y = cursorY(cur.top()) - row.baseline() - 1;
1231 setCursorFromCoordinates(cur, x, y);
1233 if (!cur.selection()) {
1234 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1235 if (inset_hit && isHighlyEditableInset(inset_hit))
1236 inset_hit->editXY(cur, cur.x_target(), y);
1241 void LyXText::cursorDown(LCursor & cur)
1243 Row const & row = cur.textRow();
1244 int x = cur.x_target();
1245 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1246 setCursorFromCoordinates(cur, x, y);
1248 if (!cur.selection()) {
1249 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1250 if (inset_hit && isHighlyEditableInset(inset_hit))
1251 inset_hit->editXY(cur, cur.x_target(), y);
1256 void LyXText::cursorUpParagraph(LCursor & cur)
1259 setCursor(cur, cur.par(), 0);
1260 else if (cur.par() != 0)
1261 setCursor(cur, cur.par() - 1, 0);
1265 void LyXText::cursorDownParagraph(LCursor & cur)
1267 if (cur.par() != cur.lastpar())
1268 setCursor(cur, cur.par() + 1, 0);
1270 setCursor(cur, cur.par(), cur.lastpos());
1274 // fix the cursor `cur' after a characters has been deleted at `where'
1275 // position. Called by deleteEmptyParagraphMechanism
1276 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1278 // do notheing if cursor is not in the paragraph where the
1279 // deletion occured,
1280 if (cur.par() != where.par())
1283 // if cursor position is after the deletion place update it
1284 if (cur.pos() > where.pos())
1287 // check also if we don't want to set the cursor on a spot behind the
1288 // pagragraph because we erased the last character.
1289 if (cur.pos() > cur.lastpos())
1290 cur.pos() = cur.lastpos();
1294 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1296 BOOST_ASSERT(cur.size() == old.size());
1297 // Would be wrong to delete anything if we have a selection.
1298 if (cur.selection())
1301 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1302 Paragraph const & oldpar = pars_[old.par()];
1304 // We allow all kinds of "mumbo-jumbo" when freespacing.
1305 if (oldpar.isFreeSpacing())
1308 /* Ok I'll put some comments here about what is missing.
1309 I have fixed BackSpace (and thus Delete) to not delete
1310 double-spaces automagically. I have also changed Cut,
1311 Copy and Paste to hopefully do some sensible things.
1312 There are still some small problems that can lead to
1313 double spaces stored in the document file or space at
1314 the beginning of paragraphs(). This happens if you have
1315 the cursor between to spaces and then save. Or if you
1316 cut and paste and the selection have a space at the
1317 beginning and then save right after the paste. I am
1318 sure none of these are very hard to fix, but I will
1319 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1320 that I can get some feedback. (Lgb)
1323 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1324 // delete the LineSeparator.
1327 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1328 // delete the LineSeparator.
1331 // If the chars around the old cursor were spaces, delete one of them.
1332 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1334 // Only if the cursor has really moved.
1336 && old.pos() < oldpar.size()
1337 && oldpar.isLineSeparator(old.pos())
1338 && oldpar.isLineSeparator(old.pos() - 1)) {
1339 pars_[old.par()].erase(old.pos() - 1);
1340 #ifdef WITH_WARNINGS
1341 #warning This will not work anymore when we have multiple views of the same buffer
1342 // In this case, we will have to correct also the cursors held by
1343 // other bufferviews. It will probably be easier to do that in a more
1344 // automated way in CursorSlice code. (JMarc 26/09/2001)
1346 // correct all cursor parts
1347 fixCursorAfterDelete(cur.top(), old.top());
1348 #ifdef WITH_WARNINGS
1349 #warning DEPM, look here
1351 //fixCursorAfterDelete(cur.anchor(), old.top());
1356 // only do our magic if we changed paragraph
1357 if (old.par() == cur.par())
1360 // don't delete anything if this is the ONLY paragraph!
1361 if (pars_.size() == 1)
1364 // Do not delete empty paragraphs with keepempty set.
1365 if (oldpar.allowEmpty())
1368 // record if we have deleted a paragraph
1369 // we can't possibly have deleted a paragraph before this point
1370 bool deleted = false;
1372 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1373 // ok, we will delete something
1374 CursorSlice tmpcursor;
1378 bool selection_position_was_oldcursor_position =
1379 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1381 // This is a bit of a overkill. We change the old and the cur par
1382 // at max, certainly not everything in between...
1383 recUndo(old.par(), cur.par());
1386 pars_.erase(pars_.begin() + old.par());
1388 // Update cursor par offset if necessary.
1389 // Some 'iterator registration' would be nice that takes care of
1390 // such events. Maybe even signal/slot?
1391 if (cur.par() > old.par())
1393 #ifdef WITH_WARNINGS
1394 #warning DEPM, look here
1396 // if (cur.anchor().par() > old.par())
1397 // --cur.anchor().par();
1399 if (selection_position_was_oldcursor_position) {
1400 // correct selection
1408 if (pars_[old.par()].stripLeadingSpaces())
1415 ParagraphList & LyXText::paragraphs() const
1417 return const_cast<ParagraphList &>(pars_);
1421 void LyXText::recUndo(par_type first, par_type last) const
1423 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1427 void LyXText::recUndo(par_type par) const
1429 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1433 int defaultRowHeight()
1435 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);