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 BufferParams const & params = bv()->buffer()->params();
284 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
285 if (lyxlayout->is_environment) {
286 // move everything in a new environment inset
287 lyxerr << "setting layout " << layout << endl;
288 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
289 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
290 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
291 InsetBase * inset = new InsetEnvironment(params, layout);
292 insertInset(cur, inset);
293 //inset->edit(cur, true);
294 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
298 par_type start = cur.selBegin().par();
299 par_type end = cur.selEnd().par() + 1;
300 par_type endpit = setLayout(start, end, layout);
301 redoParagraphs(start, endpit);
309 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
311 if (!cur.selection()) {
315 beg = cur.selBegin().par();
316 end = cur.selEnd().par() + 1;
321 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
322 Paragraph const & par, int max_depth)
324 if (par.layout()->labeltype == LABEL_BIBLIO)
326 int const depth = par.params().depth();
327 if (type == LyXText::INC_DEPTH && depth < max_depth)
329 if (type == LyXText::DEC_DEPTH && depth > 0)
338 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
340 BOOST_ASSERT(this == cur.text());
342 getSelectionSpan(cur, beg, end);
345 max_depth = pars_[beg - 1].getMaxDepthAfter();
347 for (par_type pit = beg; pit != end; ++pit) {
348 if (::changeDepthAllowed(type, pars_[pit], max_depth))
350 max_depth = pars_[pit].getMaxDepthAfter();
356 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
358 BOOST_ASSERT(this == cur.text());
360 getSelectionSpan(cur, beg, end);
361 recordUndoSelection(cur);
365 max_depth = pars_[beg - 1].getMaxDepthAfter();
367 for (par_type pit = beg; pit != end; ++pit) {
368 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
369 int const depth = pars_[pit].params().depth();
370 if (type == INC_DEPTH)
371 pars_[pit].params().depth(depth + 1);
373 pars_[pit].params().depth(depth - 1);
375 max_depth = pars_[pit].getMaxDepthAfter();
377 // this handles the counter labels, and also fixes up
378 // depth values for follow-on (child) paragraphs
383 // set font over selection
384 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
386 BOOST_ASSERT(this == cur.text());
387 // if there is no selection just set the current_font
388 if (!cur.selection()) {
389 // Determine basis font
391 par_type pit = cur.par();
392 if (cur.pos() < pars_[pit].beginOfBody())
393 layoutfont = getLabelFont(pit);
395 layoutfont = getLayoutFont(pit);
397 // Update current font
398 real_current_font.update(font,
399 bv()->buffer()->params().language,
402 // Reduce to implicit settings
403 current_font = real_current_font;
404 current_font.reduce(layoutfont);
405 // And resolve it completely
406 real_current_font.realize(layoutfont);
411 // Ok, we have a selection.
412 recordUndoSelection(cur);
414 par_type const beg = cur.selBegin().par();
415 par_type const end = cur.selEnd().par();
417 DocIterator pos = cur.selectionBegin();
418 DocIterator posend = cur.selectionEnd();
420 lyxerr << "pos: " << pos << " posend: " << posend << endl;
422 BufferParams const & params = bv()->buffer()->params();
424 // Don't use forwardChar here as posend might have
425 // pos() == lastpos() and forwardChar would miss it.
426 for (; pos != posend; pos.forwardPos()) {
427 if (pos.pos() != pos.lastpos()) {
428 LyXFont f = getFont(pos.par(), pos.pos());
429 f.update(font, params.language, toggleall);
430 setCharFont(pos.par(), pos.pos(), f);
434 redoParagraphs(beg, end + 1);
438 // the cursor set functions have a special mechanism. When they
439 // realize you left an empty paragraph, they will delete it.
441 void LyXText::cursorHome(LCursor & cur)
443 BOOST_ASSERT(this == cur.text());
444 setCursor(cur, cur.par(), cur.textRow().pos());
448 void LyXText::cursorEnd(LCursor & cur)
450 BOOST_ASSERT(this == cur.text());
451 // if not on the last row of the par, put the cursor before
453 pos_type const end = cur.textRow().endpos();
454 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
458 void LyXText::cursorTop(LCursor & cur)
460 BOOST_ASSERT(this == cur.text());
461 setCursor(cur, 0, 0);
465 void LyXText::cursorBottom(LCursor & cur)
467 BOOST_ASSERT(this == cur.text());
468 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
472 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
474 BOOST_ASSERT(this == cur.text());
475 // If the mask is completely neutral, tell user
476 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
477 // Could only happen with user style
478 cur.message(_("No font change defined. "
479 "Use Character under the Layout menu to define font change."));
483 // Try implicit word selection
484 // If there is a change in the language the implicit word selection
486 CursorSlice resetCursor = cur.top();
487 bool implicitSelection =
488 font.language() == ignore_language
489 && font.number() == LyXFont::IGNORE
490 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
493 setFont(cur, font, toggleall);
495 // Implicit selections are cleared afterwards
496 // and cursor is set to the original position.
497 if (implicitSelection) {
498 cur.clearSelection();
499 cur.top() = resetCursor;
505 string LyXText::getStringToIndex(LCursor & cur)
507 BOOST_ASSERT(this == cur.text());
508 // Try implicit word selection
509 // If there is a change in the language the implicit word selection
511 CursorSlice const reset_cursor = cur.top();
512 bool const implicitSelection =
513 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
516 if (!cur.selection())
517 cur.message(_("Nothing to index!"));
518 else if (cur.selBegin().par() != cur.selEnd().par())
519 cur.message(_("Cannot index more than one paragraph!"));
521 idxstring = cur.selectionAsString(false);
523 // Reset cursors to their original position.
524 cur.top() = reset_cursor;
527 // Clear the implicit selection.
528 if (implicitSelection)
529 cur.clearSelection();
535 void LyXText::setParagraph(LCursor & cur,
536 Spacing const & spacing, LyXAlignment align,
537 string const & labelwidthstring, bool noindent)
539 BOOST_ASSERT(cur.text());
540 // make sure that the depth behind the selection are restored, too
541 par_type undopit = undoSpan(cur.selEnd().par());
542 recUndo(cur.selBegin().par(), undopit - 1);
544 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
546 Paragraph & par = pars_[pit];
547 ParagraphParameters & params = par.params();
548 params.spacing(spacing);
550 // does the layout allow the new alignment?
551 LyXLayout_ptr const & layout = par.layout();
553 if (align == LYX_ALIGN_LAYOUT)
554 align = layout->align;
555 if (align & layout->alignpossible) {
556 if (align == layout->align)
557 params.align(LYX_ALIGN_LAYOUT);
561 par.setLabelWidthString(labelwidthstring);
562 params.noindent(noindent);
565 redoParagraphs(cur.selBegin().par(), undopit);
569 string expandLabel(LyXTextClass const & textclass,
570 LyXLayout_ptr const & layout, bool appendix)
572 string fmt = appendix ?
573 layout->labelstring_appendix() : layout->labelstring();
575 // handle 'inherited level parts' in 'fmt',
576 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
577 size_t const i = fmt.find('@', 0);
578 if (i != string::npos) {
579 size_t const j = fmt.find('@', i + 1);
580 if (j != string::npos) {
581 string parent(fmt, i + 1, j - i - 1);
582 string label = expandLabel(textclass, textclass[parent], appendix);
583 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
587 return textclass.counters().counterLabel(fmt);
593 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
595 int const cur_labeltype = pars[pit].layout()->labeltype;
597 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
600 int const cur_depth = pars[pit].getDepth();
602 par_type prev_pit = pit - 1;
604 int const prev_depth = pars[prev_pit].getDepth();
605 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
606 if (prev_depth == 0 && cur_depth > 0) {
607 if (prev_labeltype == cur_labeltype) {
608 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
611 } else if (prev_depth < cur_depth) {
612 if (prev_labeltype == cur_labeltype) {
613 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
616 } else if (prev_depth == cur_depth) {
617 if (prev_labeltype == cur_labeltype) {
618 pars[pit].itemdepth = pars[prev_pit].itemdepth;
622 if (prev_pit == first_pit)
630 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
631 par_type firstpit, Counters & counters)
636 int const cur_depth = pars[pit].getDepth();
637 par_type prev_pit = pit - 1;
639 int const prev_depth = pars[prev_pit].getDepth();
640 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
641 if (prev_depth <= cur_depth) {
642 if (prev_labeltype != LABEL_ENUMERATE) {
643 switch (pars[pit].itemdepth) {
645 counters.reset("enumi");
647 counters.reset("enumii");
649 counters.reset("enumiii");
651 counters.reset("enumiv");
657 if (prev_pit == firstpit)
667 // set the counter of a paragraph. This includes the labels
668 void LyXText::setCounter(Buffer const & buf, par_type pit)
670 BufferParams const & bufparams = buf.params();
671 LyXTextClass const & textclass = bufparams.getLyXTextClass();
672 LyXLayout_ptr const & layout = pars_[pit].layout();
673 par_type first_pit = 0;
674 Counters & counters = textclass.counters();
677 pars_[pit].itemdepth = 0;
679 if (pit == first_pit) {
680 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
682 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
683 if (!pars_[pit].params().appendix() &&
684 pars_[pit].params().startOfAppendix()) {
685 pars_[pit].params().appendix(true);
686 textclass.counters().reset();
689 // Maybe we have to increment the item depth.
690 incrementItemDepth(pars_, pit, first_pit);
693 // erase what was there before
694 pars_[pit].params().labelString(string());
696 if (layout->margintype == MARGIN_MANUAL) {
697 if (pars_[pit].params().labelWidthString().empty())
698 pars_[pit].setLabelWidthString(layout->labelstring());
700 pars_[pit].setLabelWidthString(string());
703 // is it a layout that has an automatic label?
704 if (layout->labeltype == LABEL_COUNTER) {
705 BufferParams const & bufparams = buf.params();
706 LyXTextClass const & textclass = bufparams.getLyXTextClass();
707 counters.step(layout->counter);
708 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
709 pars_[pit].params().labelString(label);
710 } else if (layout->labeltype == LABEL_ITEMIZE) {
711 // At some point of time we should do something more
712 // clever here, like:
713 // pars_[pit].params().labelString(
714 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
715 // for now, use a simple hardcoded label
717 switch (pars_[pit].itemdepth) {
732 pars_[pit].params().labelString(itemlabel);
733 } else if (layout->labeltype == LABEL_ENUMERATE) {
734 // Maybe we have to reset the enumeration counter.
735 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
738 // Yes I know this is a really, really! bad solution
740 string enumcounter = "enum";
742 switch (pars_[pit].itemdepth) {
754 // not a valid enumdepth...
758 counters.step(enumcounter);
760 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
761 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
762 counters.step("bibitem");
763 int number = counters.value("bibitem");
764 if (pars_[pit].bibitem()) {
765 pars_[pit].bibitem()->setCounter(number);
766 pars_[pit].params().labelString(layout->labelstring());
768 // In biblio should't be following counters but...
770 string s = buf.B_(layout->labelstring());
773 if (layout->labeltype == LABEL_SENSITIVE) {
774 par_type end = paragraphs().size();
775 par_type tmppit = pit;
778 while (tmppit != end && pars_[tmppit].inInset()
779 // the single '=' is intended below
780 && (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 << "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);
865 // needed to insert the selection
866 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
868 par_type pit = cur.par();
869 par_type endpit = cur.par() + 1;
870 pos_type pos = cur.pos();
873 // only to be sure, should not be neccessary
874 cur.clearSelection();
875 bv()->buffer()->insertStringAsLines(pars_, pit, pos, current_font, str);
877 redoParagraphs(cur.par(), endpit);
879 setCursor(cur, cur.par(), pos);
884 // turn double CR to single CR, others are converted into one
885 // blank. Then insertStringAsLines is called
886 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
888 string linestr = str;
889 bool newline_inserted = false;
891 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
892 if (linestr[i] == '\n') {
893 if (newline_inserted) {
894 // we know that \r will be ignored by
895 // insertStringAsLines. Of course, it is a dirty
896 // trick, but it works...
897 linestr[i - 1] = '\r';
901 newline_inserted = true;
903 } else if (IsPrintable(linestr[i])) {
904 newline_inserted = false;
907 insertStringAsLines(cur, linestr);
911 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
912 bool setfont, bool boundary)
915 setCursorIntern(cur, par, pos, setfont, boundary);
916 return deleteEmptyParagraphMechanism(cur, old);
920 void LyXText::setCursor(CursorSlice & cur, par_type par,
921 pos_type pos, bool boundary)
923 BOOST_ASSERT(par != int(paragraphs().size()));
927 cur.boundary() = boundary;
929 // no rows, no fun...
930 if (paragraphs().begin()->rows.empty())
933 // now some strict checking
934 Paragraph & para = getPar(par);
935 Row const & row = *para.getRow(pos);
936 pos_type const end = row.endpos();
938 // None of these should happen, but we're scaredy-cats
940 lyxerr << "dont like -1" << endl;
944 if (pos > para.size()) {
945 lyxerr << "dont like 1, pos: " << pos
946 << " size: " << para.size()
947 << " row.pos():" << row.pos()
948 << " par: " << par << endl;
953 lyxerr << "dont like 2, pos: " << pos
954 << " size: " << para.size()
955 << " row.pos():" << row.pos()
956 << " par: " << par << endl;
957 // This shouldn't happen.
961 if (pos < row.pos()) {
962 lyxerr << "dont like 3 please report pos:" << pos
963 << " size: " << para.size()
964 << " row.pos():" << row.pos()
965 << " par: " << par << endl;
971 void LyXText::setCursorIntern(LCursor & cur,
972 par_type par, pos_type pos, bool setfont, bool boundary)
974 setCursor(cur.top(), par, pos, boundary);
975 cur.x_target() = cursorX(cur.top());
981 void LyXText::setCurrentFont(LCursor & cur)
983 BOOST_ASSERT(this == cur.text());
984 pos_type pos = cur.pos();
985 par_type pit = cur.par();
987 if (cur.boundary() && pos > 0)
991 if (pos == cur.lastpos())
993 else // potentional bug... BUG (Lgb)
994 if (pars_[pit].isSeparator(pos)) {
995 if (pos > cur.textRow().pos() &&
996 bidi.level(pos) % 2 ==
997 bidi.level(pos - 1) % 2)
999 else if (pos + 1 < cur.lastpos())
1004 BufferParams const & bufparams = bv()->buffer()->params();
1005 current_font = pars_[pit].getFontSettings(bufparams, pos);
1006 real_current_font = getFont(pit, pos);
1008 if (cur.pos() == cur.lastpos()
1009 && bidi.isBoundary(*bv()->buffer(), pars_[pit], cur.pos())
1010 && !cur.boundary()) {
1011 Language const * lang = pars_[pit].getParLanguage(bufparams);
1012 current_font.setLanguage(lang);
1013 current_font.setNumber(LyXFont::OFF);
1014 real_current_font.setLanguage(lang);
1015 real_current_font.setNumber(LyXFont::OFF);
1020 // x is an absolute screen coord
1021 // returns the column near the specified x-coordinate of the row
1022 // x is set to the real beginning of this column
1023 pos_type LyXText::getColumnNearX(par_type pit,
1024 Row const & row, int & x, bool & boundary) const
1027 RowMetrics const r = computeRowMetrics(pit, row);
1029 pos_type vc = row.pos();
1030 pos_type end = row.endpos();
1032 LyXLayout_ptr const & layout = pars_[pit].layout();
1034 bool left_side = false;
1036 pos_type body_pos = pars_[pit].beginOfBody();
1039 double last_tmpx = tmpx;
1042 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1045 // check for empty row
1047 x = int(tmpx) + xo_;
1051 while (vc < end && tmpx <= x) {
1052 c = bidi.vis2log(vc);
1054 if (body_pos > 0 && c == body_pos - 1) {
1055 tmpx += r.label_hfill +
1056 font_metrics::width(layout->labelsep, getLabelFont(pit));
1057 if (pars_[pit].isLineSeparator(body_pos - 1))
1058 tmpx -= singleWidth(pit, body_pos - 1);
1061 if (hfillExpansion(pars_[pit], row, c)) {
1062 tmpx += singleWidth(pit, c);
1066 tmpx += r.label_hfill;
1067 } else if (pars_[pit].isSeparator(c)) {
1068 tmpx += singleWidth(pit, c);
1070 tmpx += r.separator;
1072 tmpx += singleWidth(pit, c);
1077 if ((tmpx + last_tmpx) / 2 > x) {
1082 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1085 // This (rtl_support test) is not needed, but gives
1086 // some speedup if rtl_support == false
1087 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1089 // If lastrow is false, we don't need to compute
1090 // the value of rtl.
1091 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1093 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1094 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1096 else if (vc == row.pos()) {
1097 c = bidi.vis2log(vc);
1098 if (bidi.level(c) % 2 == 1)
1101 c = bidi.vis2log(vc - 1);
1102 bool const rtl = (bidi.level(c) % 2 == 1);
1103 if (left_side == rtl) {
1105 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1109 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1110 if (bidi.level(end -1) % 2 == 0)
1111 tmpx -= singleWidth(pit, end - 1);
1113 tmpx += singleWidth(pit, end - 1);
1117 x = int(tmpx) + xo_;
1118 return c - row.pos();
1122 // x,y are absolute coordinates
1123 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1128 Row const & row = *getRowNearY(y, pit);
1129 lyxerr << "setCursorFromCoordinates:: hit row at: " << row.pos() << endl;
1131 int xx = x + xo_; // getRowNearX get absolute x coords
1132 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1133 setCursor(cur, pit, pos, true, bound);
1137 // x,y are absolute screen coordinates
1138 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1141 Row const & row = *getRowNearY(y - yo_, pit);
1144 int xx = x; // is modified by getColumnNearX
1145 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1148 cur.boundary() = bound;
1150 // try to descend into nested insets
1151 InsetBase * inset = checkInsetHit(x, y);
1152 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1156 // This should be just before or just behind the
1157 // cursor position set above.
1158 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1159 || inset == pars_[pit].getInset(pos));
1160 // Make sure the cursor points to the position before
1162 if (inset == pars_[pit].getInset(pos - 1))
1164 return inset->editXY(cur, x, y);
1168 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1170 if (cur.selection())
1172 if (cur.pos() == cur.lastpos())
1174 InsetBase * inset = cur.nextInset();
1175 if (!isHighlyEditableInset(inset))
1177 inset->edit(cur, front);
1182 void LyXText::cursorLeft(LCursor & cur)
1184 if (cur.pos() != 0) {
1185 bool boundary = cur.boundary();
1186 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1187 if (!checkAndActivateInset(cur, false)) {
1188 if (false && !boundary &&
1189 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1190 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1195 if (cur.par() != 0) {
1196 // steps into the paragraph above
1197 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1202 void LyXText::cursorRight(LCursor & cur)
1204 if (false && cur.boundary()) {
1205 setCursor(cur, cur.par(), cur.pos(), true, false);
1209 if (cur.pos() != cur.lastpos()) {
1210 if (!checkAndActivateInset(cur, true)) {
1211 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1212 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1214 setCursor(cur, cur.par(), cur.pos(), true, true);
1219 if (cur.par() != cur.lastpar())
1220 setCursor(cur, cur.par() + 1, 0);
1224 void LyXText::cursorUp(LCursor & cur)
1226 Row const & row = cur.textRow();
1227 int x = cur.x_target();
1228 int y = cursorY(cur.top()) - row.baseline() - 1;
1229 setCursorFromCoordinates(cur, x, y);
1231 if (!cur.selection()) {
1232 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1233 if (inset_hit && isHighlyEditableInset(inset_hit))
1234 inset_hit->editXY(cur, cur.x_target(), y);
1239 void LyXText::cursorDown(LCursor & cur)
1241 Row const & row = cur.textRow();
1242 int x = cur.x_target();
1243 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1244 setCursorFromCoordinates(cur, x, y);
1246 if (!cur.selection()) {
1247 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1248 if (inset_hit && isHighlyEditableInset(inset_hit))
1249 inset_hit->editXY(cur, cur.x_target(), y);
1254 void LyXText::cursorUpParagraph(LCursor & cur)
1257 setCursor(cur, cur.par(), 0);
1258 else if (cur.par() != 0)
1259 setCursor(cur, cur.par() - 1, 0);
1263 void LyXText::cursorDownParagraph(LCursor & cur)
1265 if (cur.par() != cur.lastpar())
1266 setCursor(cur, cur.par() + 1, 0);
1268 setCursor(cur, cur.par(), cur.lastpos());
1272 // fix the cursor `cur' after a characters has been deleted at `where'
1273 // position. Called by deleteEmptyParagraphMechanism
1274 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1276 // do notheing if cursor is not in the paragraph where the
1277 // deletion occured,
1278 if (cur.par() != where.par())
1281 // if cursor position is after the deletion place update it
1282 if (cur.pos() > where.pos())
1285 // check also if we don't want to set the cursor on a spot behind the
1286 // pagragraph because we erased the last character.
1287 if (cur.pos() > cur.lastpos())
1288 cur.pos() = cur.lastpos();
1292 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1294 BOOST_ASSERT(cur.size() == old.size());
1295 // Would be wrong to delete anything if we have a selection.
1296 if (cur.selection())
1299 //lyxerr << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1300 Paragraph const & oldpar = pars_[old.par()];
1302 // We allow all kinds of "mumbo-jumbo" when freespacing.
1303 if (oldpar.isFreeSpacing())
1306 /* Ok I'll put some comments here about what is missing.
1307 I have fixed BackSpace (and thus Delete) to not delete
1308 double-spaces automagically. I have also changed Cut,
1309 Copy and Paste to hopefully do some sensible things.
1310 There are still some small problems that can lead to
1311 double spaces stored in the document file or space at
1312 the beginning of paragraphs(). This happens if you have
1313 the cursor between to spaces and then save. Or if you
1314 cut and paste and the selection have a space at the
1315 beginning and then save right after the paste. I am
1316 sure none of these are very hard to fix, but I will
1317 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1318 that I can get some feedback. (Lgb)
1321 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1322 // delete the LineSeparator.
1325 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1326 // delete the LineSeparator.
1329 // If the chars around the old cursor were spaces, delete one of them.
1330 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1332 // Only if the cursor has really moved.
1334 && old.pos() < oldpar.size()
1335 && oldpar.isLineSeparator(old.pos())
1336 && oldpar.isLineSeparator(old.pos() - 1)) {
1337 pars_[old.par()].erase(old.pos() - 1);
1338 #ifdef WITH_WARNINGS
1339 #warning This will not work anymore when we have multiple views of the same buffer
1340 // In this case, we will have to correct also the cursors held by
1341 // other bufferviews. It will probably be easier to do that in a more
1342 // automated way in CursorSlice code. (JMarc 26/09/2001)
1344 // correct all cursor parts
1345 fixCursorAfterDelete(cur.top(), old.top());
1346 #warning DEPM, look here
1347 //fixCursorAfterDelete(cur.anchor(), old.top());
1352 // only do our magic if we changed paragraph
1353 if (old.par() == cur.par())
1356 // don't delete anything if this is the ONLY paragraph!
1357 if (pars_.size() == 1)
1360 // Do not delete empty paragraphs with keepempty set.
1361 if (oldpar.allowEmpty())
1364 // record if we have deleted a paragraph
1365 // we can't possibly have deleted a paragraph before this point
1366 bool deleted = false;
1368 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1369 // ok, we will delete something
1370 CursorSlice tmpcursor;
1374 bool selection_position_was_oldcursor_position =
1375 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1377 // This is a bit of a overkill. We change the old and the cur par
1378 // at max, certainly not everything in between...
1379 recUndo(old.par(), cur.par());
1382 pars_.erase(pars_.begin() + old.par());
1384 // Update cursor par offset if necessary.
1385 // Some 'iterator registration' would be nice that takes care of
1386 // such events. Maybe even signal/slot?
1387 if (cur.par() > old.par())
1389 #warning DEPM, look here
1390 // if (cur.anchor().par() > old.par())
1391 // --cur.anchor().par();
1393 if (selection_position_was_oldcursor_position) {
1394 // correct selection
1402 if (pars_[old.par()].stripLeadingSpaces())
1409 ParagraphList & LyXText::paragraphs() const
1411 return const_cast<ParagraphList &>(pars_);
1415 void LyXText::recUndo(par_type first, par_type last) const
1417 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1421 void LyXText::recUndo(par_type par) const
1423 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1427 int defaultRowHeight()
1429 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);