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 BufferParams const & params = bv()->buffer()->params();
422 for (; pos != posend; pos.forwardChar()) {
423 LyXFont f = getFont(pos.par(), pos.pos());
424 f.update(font, params.language, toggleall);
425 setCharFont(pos.par(), pos.pos(), f);
428 redoParagraphs(beg, end + 1);
432 // the cursor set functions have a special mechanism. When they
433 // realize you left an empty paragraph, they will delete it.
435 void LyXText::cursorHome(LCursor & cur)
437 BOOST_ASSERT(this == cur.text());
438 setCursor(cur, cur.par(), cur.textRow().pos());
442 void LyXText::cursorEnd(LCursor & cur)
444 BOOST_ASSERT(this == cur.text());
445 // if not on the last row of the par, put the cursor before
447 pos_type const end = cur.textRow().endpos();
448 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
452 void LyXText::cursorTop(LCursor & cur)
454 BOOST_ASSERT(this == cur.text());
455 setCursor(cur, 0, 0);
459 void LyXText::cursorBottom(LCursor & cur)
461 BOOST_ASSERT(this == cur.text());
462 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
466 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
468 BOOST_ASSERT(this == cur.text());
469 // If the mask is completely neutral, tell user
470 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
471 // Could only happen with user style
472 cur.message(_("No font change defined. "
473 "Use Character under the Layout menu to define font change."));
477 // Try implicit word selection
478 // If there is a change in the language the implicit word selection
480 CursorSlice resetCursor = cur.top();
481 bool implicitSelection =
482 font.language() == ignore_language
483 && font.number() == LyXFont::IGNORE
484 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
487 setFont(cur, font, toggleall);
489 // Implicit selections are cleared afterwards
490 // and cursor is set to the original position.
491 if (implicitSelection) {
492 cur.clearSelection();
493 cur.top() = resetCursor;
499 string LyXText::getStringToIndex(LCursor & cur)
501 BOOST_ASSERT(this == cur.text());
502 // Try implicit word selection
503 // If there is a change in the language the implicit word selection
505 CursorSlice const reset_cursor = cur.top();
506 bool const implicitSelection =
507 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
510 if (!cur.selection())
511 cur.message(_("Nothing to index!"));
512 else if (cur.selBegin().par() != cur.selEnd().par())
513 cur.message(_("Cannot index more than one paragraph!"));
515 idxstring = cur.selectionAsString(false);
517 // Reset cursors to their original position.
518 cur.top() = reset_cursor;
521 // Clear the implicit selection.
522 if (implicitSelection)
523 cur.clearSelection();
529 void LyXText::setParagraph(LCursor & cur,
530 Spacing const & spacing, LyXAlignment align,
531 string const & labelwidthstring, bool noindent)
533 BOOST_ASSERT(cur.text());
534 // make sure that the depth behind the selection are restored, too
535 par_type undopit = undoSpan(cur.selEnd().par());
536 recUndo(cur.selBegin().par(), undopit - 1);
538 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
540 Paragraph & par = pars_[pit];
541 ParagraphParameters & params = par.params();
542 params.spacing(spacing);
544 // does the layout allow the new alignment?
545 LyXLayout_ptr const & layout = par.layout();
547 if (align == LYX_ALIGN_LAYOUT)
548 align = layout->align;
549 if (align & layout->alignpossible) {
550 if (align == layout->align)
551 params.align(LYX_ALIGN_LAYOUT);
555 par.setLabelWidthString(labelwidthstring);
556 params.noindent(noindent);
559 redoParagraphs(cur.selBegin().par(), undopit);
563 string expandLabel(LyXTextClass const & textclass,
564 LyXLayout_ptr const & layout, bool appendix)
566 string fmt = appendix ?
567 layout->labelstring_appendix() : layout->labelstring();
569 // handle 'inherited level parts' in 'fmt',
570 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
571 size_t const i = fmt.find('@', 0);
572 if (i != string::npos) {
573 size_t const j = fmt.find('@', i + 1);
574 if (j != string::npos) {
575 string parent(fmt, i + 1, j - i - 1);
576 string label = expandLabel(textclass, textclass[parent], appendix);
577 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
581 return textclass.counters().counterLabel(fmt);
587 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
589 int const cur_labeltype = pars[pit].layout()->labeltype;
591 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
594 int const cur_depth = pars[pit].getDepth();
596 par_type prev_pit = pit - 1;
598 int const prev_depth = pars[prev_pit].getDepth();
599 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
600 if (prev_depth == 0 && cur_depth > 0) {
601 if (prev_labeltype == cur_labeltype) {
602 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
605 } else if (prev_depth < cur_depth) {
606 if (prev_labeltype == cur_labeltype) {
607 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
610 } else if (prev_depth == cur_depth) {
611 if (prev_labeltype == cur_labeltype) {
612 pars[pit].itemdepth = pars[prev_pit].itemdepth;
616 if (prev_pit == first_pit)
624 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
625 par_type firstpit, Counters & counters)
630 int const cur_depth = pars[pit].getDepth();
631 par_type prev_pit = pit - 1;
633 int const prev_depth = pars[prev_pit].getDepth();
634 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
635 if (prev_depth <= cur_depth) {
636 if (prev_labeltype != LABEL_ENUMERATE) {
637 switch (pars[pit].itemdepth) {
639 counters.reset("enumi");
641 counters.reset("enumii");
643 counters.reset("enumiii");
645 counters.reset("enumiv");
651 if (prev_pit == firstpit)
661 // set the counter of a paragraph. This includes the labels
662 void LyXText::setCounter(Buffer const & buf, par_type pit)
664 BufferParams const & bufparams = buf.params();
665 LyXTextClass const & textclass = bufparams.getLyXTextClass();
666 LyXLayout_ptr const & layout = pars_[pit].layout();
667 par_type first_pit = 0;
668 Counters & counters = textclass.counters();
671 pars_[pit].itemdepth = 0;
673 if (pit == first_pit) {
674 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
676 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
677 if (!pars_[pit].params().appendix() &&
678 pars_[pit].params().startOfAppendix()) {
679 pars_[pit].params().appendix(true);
680 textclass.counters().reset();
683 // Maybe we have to increment the item depth.
684 incrementItemDepth(pars_, pit, first_pit);
687 // erase what was there before
688 pars_[pit].params().labelString(string());
690 if (layout->margintype == MARGIN_MANUAL) {
691 if (pars_[pit].params().labelWidthString().empty())
692 pars_[pit].setLabelWidthString(layout->labelstring());
694 pars_[pit].setLabelWidthString(string());
697 // is it a layout that has an automatic label?
698 if (layout->labeltype == LABEL_COUNTER) {
699 BufferParams const & bufparams = buf.params();
700 LyXTextClass const & textclass = bufparams.getLyXTextClass();
701 counters.step(layout->counter);
702 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
703 pars_[pit].params().labelString(label);
704 } else if (layout->labeltype == LABEL_ITEMIZE) {
705 // At some point of time we should do something more
706 // clever here, like:
707 // pars_[pit].params().labelString(
708 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
709 // for now, use a simple hardcoded label
711 switch (pars_[pit].itemdepth) {
726 pars_[pit].params().labelString(itemlabel);
727 } else if (layout->labeltype == LABEL_ENUMERATE) {
728 // Maybe we have to reset the enumeration counter.
729 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
732 // Yes I know this is a really, really! bad solution
734 string enumcounter = "enum";
736 switch (pars_[pit].itemdepth) {
748 // not a valid enumdepth...
752 counters.step(enumcounter);
754 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
755 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
756 counters.step("bibitem");
757 int number = counters.value("bibitem");
758 if (pars_[pit].bibitem()) {
759 pars_[pit].bibitem()->setCounter(number);
760 pars_[pit].params().labelString(layout->labelstring());
762 // In biblio should't be following counters but...
764 string s = buf.B_(layout->labelstring());
767 if (layout->labeltype == LABEL_SENSITIVE) {
768 par_type end = paragraphs().size();
769 par_type tmppit = pit;
772 while (tmppit != end && pars_[tmppit].inInset()
773 // the single '=' is intended below
774 && (in = pars_[tmppit].inInset()))
776 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
777 in->lyxCode() == InsetBase::WRAP_CODE) {
781 Paragraph const * owner = &ownerPar(buf, in);
783 for ( ; tmppit != end; ++tmppit)
784 if (&pars_[tmppit] == owner)
792 if (in->lyxCode() == InsetBase::FLOAT_CODE)
793 type = static_cast<InsetFloat*>(in)->params().type;
794 else if (in->lyxCode() == InsetBase::WRAP_CODE)
795 type = static_cast<InsetWrap*>(in)->params().type;
799 Floating const & fl = textclass.floats().getType(type);
801 counters.step(fl.type());
803 // Doesn't work... yet.
804 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
806 // par->SetLayout(0);
807 // s = layout->labelstring;
808 s = _("Senseless: ");
811 pars_[pit].params().labelString(s);
817 // Updates all counters.
818 void LyXText::updateCounters()
821 bv()->buffer()->params().getLyXTextClass().counters().reset();
823 bool update_pos = false;
825 par_type end = paragraphs().size();
826 for (par_type pit = 0; pit != end; ++pit) {
827 string const oldLabel = pars_[pit].params().labelString();
830 maxdepth = pars_[pit - 1].getMaxDepthAfter();
832 if (pars_[pit].params().depth() > maxdepth)
833 pars_[pit].params().depth(maxdepth);
835 // setCounter can potentially change the labelString.
836 setCounter(*bv()->buffer(), pit);
837 string const & newLabel = pars_[pit].params().labelString();
838 if (oldLabel != newLabel) {
839 //lyxerr << "changing labels: old: " << oldLabel << " new: "
840 // << newLabel << endl;
841 redoParagraphInternal(pit);
846 updateParPositions();
850 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
852 BOOST_ASSERT(this == cur.text());
854 cur.paragraph().insertInset(cur.pos(), inset);
859 // needed to insert the selection
860 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
862 par_type pit = cur.par();
863 par_type endpit = cur.par() + 1;
864 pos_type pos = cur.pos();
867 // only to be sure, should not be neccessary
868 cur.clearSelection();
869 bv()->buffer()->insertStringAsLines(pars_, pit, pos, current_font, str);
871 redoParagraphs(cur.par(), endpit);
873 setCursor(cur, cur.par(), pos);
878 // turn double CR to single CR, others are converted into one
879 // blank. Then insertStringAsLines is called
880 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
882 string linestr = str;
883 bool newline_inserted = false;
885 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
886 if (linestr[i] == '\n') {
887 if (newline_inserted) {
888 // we know that \r will be ignored by
889 // insertStringAsLines. Of course, it is a dirty
890 // trick, but it works...
891 linestr[i - 1] = '\r';
895 newline_inserted = true;
897 } else if (IsPrintable(linestr[i])) {
898 newline_inserted = false;
901 insertStringAsLines(cur, linestr);
905 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
906 bool setfont, bool boundary)
909 setCursorIntern(cur, par, pos, setfont, boundary);
910 return deleteEmptyParagraphMechanism(cur, old);
914 void LyXText::setCursor(CursorSlice & cur, par_type par,
915 pos_type pos, bool boundary)
917 BOOST_ASSERT(par != int(paragraphs().size()));
921 cur.boundary() = boundary;
923 // no rows, no fun...
924 if (paragraphs().begin()->rows.empty())
927 // now some strict checking
928 Paragraph & para = getPar(par);
929 Row const & row = *para.getRow(pos);
930 pos_type const end = row.endpos();
932 // None of these should happen, but we're scaredy-cats
934 lyxerr << "dont like -1" << endl;
938 if (pos > para.size()) {
939 lyxerr << "dont like 1, pos: " << pos
940 << " size: " << para.size()
941 << " row.pos():" << row.pos()
942 << " par: " << par << endl;
947 lyxerr << "dont like 2, pos: " << pos
948 << " size: " << para.size()
949 << " row.pos():" << row.pos()
950 << " par: " << par << endl;
951 // This shouldn't happen.
955 if (pos < row.pos()) {
956 lyxerr << "dont like 3 please report pos:" << pos
957 << " size: " << para.size()
958 << " row.pos():" << row.pos()
959 << " par: " << par << endl;
965 void LyXText::setCursorIntern(LCursor & cur,
966 par_type par, pos_type pos, bool setfont, bool boundary)
968 setCursor(cur.top(), par, pos, boundary);
969 cur.x_target() = cursorX(cur.top());
975 void LyXText::setCurrentFont(LCursor & cur)
977 BOOST_ASSERT(this == cur.text());
978 pos_type pos = cur.pos();
979 par_type pit = cur.par();
981 if (cur.boundary() && pos > 0)
985 if (pos == cur.lastpos())
987 else // potentional bug... BUG (Lgb)
988 if (pars_[pit].isSeparator(pos)) {
989 if (pos > cur.textRow().pos() &&
990 bidi.level(pos) % 2 ==
991 bidi.level(pos - 1) % 2)
993 else if (pos + 1 < cur.lastpos())
998 BufferParams const & bufparams = bv()->buffer()->params();
999 current_font = pars_[pit].getFontSettings(bufparams, pos);
1000 real_current_font = getFont(pit, pos);
1002 if (cur.pos() == cur.lastpos()
1003 && bidi.isBoundary(*bv()->buffer(), pars_[pit], cur.pos())
1004 && !cur.boundary()) {
1005 Language const * lang = pars_[pit].getParLanguage(bufparams);
1006 current_font.setLanguage(lang);
1007 current_font.setNumber(LyXFont::OFF);
1008 real_current_font.setLanguage(lang);
1009 real_current_font.setNumber(LyXFont::OFF);
1014 // x is an absolute screen coord
1015 // returns the column near the specified x-coordinate of the row
1016 // x is set to the real beginning of this column
1017 pos_type LyXText::getColumnNearX(par_type pit,
1018 Row const & row, int & x, bool & boundary) const
1021 RowMetrics const r = computeRowMetrics(pit, row);
1023 pos_type vc = row.pos();
1024 pos_type end = row.endpos();
1026 LyXLayout_ptr const & layout = pars_[pit].layout();
1028 bool left_side = false;
1030 pos_type body_pos = pars_[pit].beginOfBody();
1033 double last_tmpx = tmpx;
1036 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1039 // check for empty row
1041 x = int(tmpx) + xo_;
1045 while (vc < end && tmpx <= x) {
1046 c = bidi.vis2log(vc);
1048 if (body_pos > 0 && c == body_pos - 1) {
1049 tmpx += r.label_hfill +
1050 font_metrics::width(layout->labelsep, getLabelFont(pit));
1051 if (pars_[pit].isLineSeparator(body_pos - 1))
1052 tmpx -= singleWidth(pit, body_pos - 1);
1055 if (hfillExpansion(pars_[pit], row, c)) {
1056 tmpx += singleWidth(pit, c);
1060 tmpx += r.label_hfill;
1061 } else if (pars_[pit].isSeparator(c)) {
1062 tmpx += singleWidth(pit, c);
1064 tmpx += r.separator;
1066 tmpx += singleWidth(pit, c);
1071 if ((tmpx + last_tmpx) / 2 > x) {
1076 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1079 // This (rtl_support test) is not needed, but gives
1080 // some speedup if rtl_support == false
1081 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1083 // If lastrow is false, we don't need to compute
1084 // the value of rtl.
1085 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1087 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1088 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1090 else if (vc == row.pos()) {
1091 c = bidi.vis2log(vc);
1092 if (bidi.level(c) % 2 == 1)
1095 c = bidi.vis2log(vc - 1);
1096 bool const rtl = (bidi.level(c) % 2 == 1);
1097 if (left_side == rtl) {
1099 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1103 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1104 if (bidi.level(end -1) % 2 == 0)
1105 tmpx -= singleWidth(pit, end - 1);
1107 tmpx += singleWidth(pit, end - 1);
1111 x = int(tmpx) + xo_;
1112 return c - row.pos();
1116 // x,y are absolute coordinates
1117 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1122 Row const & row = *getRowNearY(y, pit);
1123 lyxerr << "setCursorFromCoordinates:: hit row at: " << row.pos() << endl;
1125 int xx = x + xo_; // getRowNearX get absolute x coords
1126 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1127 setCursor(cur, pit, pos, true, bound);
1131 // x,y are absolute screen coordinates
1132 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1135 Row const & row = *getRowNearY(y - yo_, pit);
1138 int xx = x; // is modified by getColumnNearX
1139 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1142 cur.boundary() = bound;
1144 // try to descend into nested insets
1145 InsetBase * inset = checkInsetHit(x, y);
1149 // This should be just before or just behind the
1150 // cursor position set above.
1151 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1152 || inset == pars_[pit].getInset(pos));
1153 // Make sure the cursor points to the position before
1155 if (inset == pars_[pit].getInset(pos - 1))
1157 return inset->editXY(cur, x, y);
1161 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1163 if (cur.selection())
1165 if (cur.pos() == cur.lastpos())
1167 InsetBase * inset = cur.nextInset();
1168 if (!isHighlyEditableInset(inset))
1170 inset->edit(cur, front);
1175 void LyXText::cursorLeft(LCursor & cur)
1177 if (cur.pos() != 0) {
1178 bool boundary = cur.boundary();
1179 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1180 if (!checkAndActivateInset(cur, false)) {
1181 if (false && !boundary &&
1182 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1183 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1188 if (cur.par() != 0) {
1189 // steps into the paragraph above
1190 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1195 void LyXText::cursorRight(LCursor & cur)
1197 if (false && cur.boundary()) {
1198 setCursor(cur, cur.par(), cur.pos(), true, false);
1202 if (cur.pos() != cur.lastpos()) {
1203 if (!checkAndActivateInset(cur, true)) {
1204 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1205 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1207 setCursor(cur, cur.par(), cur.pos(), true, true);
1212 if (cur.par() != cur.lastpar())
1213 setCursor(cur, cur.par() + 1, 0);
1217 void LyXText::cursorUp(LCursor & cur)
1219 Row const & row = cur.textRow();
1220 int x = cur.x_target();
1221 int y = cursorY(cur.top()) - row.baseline() - 1;
1222 setCursorFromCoordinates(cur, x, y);
1224 if (!cur.selection()) {
1225 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1226 if (inset_hit && isHighlyEditableInset(inset_hit))
1227 inset_hit->editXY(cur, cur.x_target(), y);
1232 void LyXText::cursorDown(LCursor & cur)
1234 Row const & row = cur.textRow();
1235 int x = cur.x_target();
1236 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1237 setCursorFromCoordinates(cur, x, y);
1239 if (!cur.selection()) {
1240 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1241 if (inset_hit && isHighlyEditableInset(inset_hit))
1242 inset_hit->editXY(cur, cur.x_target(), y);
1247 void LyXText::cursorUpParagraph(LCursor & cur)
1250 setCursor(cur, cur.par(), 0);
1251 else if (cur.par() != 0)
1252 setCursor(cur, cur.par() - 1, 0);
1256 void LyXText::cursorDownParagraph(LCursor & cur)
1258 if (cur.par() != cur.lastpar())
1259 setCursor(cur, cur.par() + 1, 0);
1261 setCursor(cur, cur.par(), cur.lastpos());
1265 // fix the cursor `cur' after a characters has been deleted at `where'
1266 // position. Called by deleteEmptyParagraphMechanism
1267 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1269 // do notheing if cursor is not in the paragraph where the
1270 // deletion occured,
1271 if (cur.par() != where.par())
1274 // if cursor position is after the deletion place update it
1275 if (cur.pos() > where.pos())
1278 // check also if we don't want to set the cursor on a spot behind the
1279 // pagragraph because we erased the last character.
1280 if (cur.pos() > cur.lastpos())
1281 cur.pos() = cur.lastpos();
1285 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1287 BOOST_ASSERT(cur.size() == old.size());
1288 // Would be wrong to delete anything if we have a selection.
1289 if (cur.selection())
1292 //lyxerr << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1293 Paragraph const & oldpar = pars_[old.par()];
1295 // We allow all kinds of "mumbo-jumbo" when freespacing.
1296 if (oldpar.isFreeSpacing())
1299 /* Ok I'll put some comments here about what is missing.
1300 I have fixed BackSpace (and thus Delete) to not delete
1301 double-spaces automagically. I have also changed Cut,
1302 Copy and Paste to hopefully do some sensible things.
1303 There are still some small problems that can lead to
1304 double spaces stored in the document file or space at
1305 the beginning of paragraphs(). This happens if you have
1306 the cursor between to spaces and then save. Or if you
1307 cut and paste and the selection have a space at the
1308 beginning and then save right after the paste. I am
1309 sure none of these are very hard to fix, but I will
1310 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1311 that I can get some feedback. (Lgb)
1314 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1315 // delete the LineSeparator.
1318 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1319 // delete the LineSeparator.
1322 // If the chars around the old cursor were spaces, delete one of them.
1323 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1325 // Only if the cursor has really moved.
1327 && old.pos() < oldpar.size()
1328 && oldpar.isLineSeparator(old.pos())
1329 && oldpar.isLineSeparator(old.pos() - 1)) {
1330 pars_[old.par()].erase(old.pos() - 1);
1331 #ifdef WITH_WARNINGS
1332 #warning This will not work anymore when we have multiple views of the same buffer
1333 // In this case, we will have to correct also the cursors held by
1334 // other bufferviews. It will probably be easier to do that in a more
1335 // automated way in CursorSlice code. (JMarc 26/09/2001)
1337 // correct all cursor parts
1338 fixCursorAfterDelete(cur.top(), old.top());
1339 fixCursorAfterDelete(cur.anchor(), old.top());
1344 // only do our magic if we changed paragraph
1345 if (old.par() == cur.par())
1348 // don't delete anything if this is the ONLY paragraph!
1349 if (pars_.size() == 1)
1352 // Do not delete empty paragraphs with keepempty set.
1353 if (oldpar.allowEmpty())
1356 // record if we have deleted a paragraph
1357 // we can't possibly have deleted a paragraph before this point
1358 bool deleted = false;
1360 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1361 // ok, we will delete something
1362 CursorSlice tmpcursor;
1366 bool selection_position_was_oldcursor_position =
1367 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1369 // This is a bit of a overkill. We change the old and the cur par
1370 // at max, certainly not everything in between...
1371 recUndo(old.par(), cur.par());
1374 pars_.erase(pars_.begin() + old.par());
1376 // Update cursor par offset if necessary.
1377 // Some 'iterator registration' would be nice that takes care of
1378 // such events. Maybe even signal/slot?
1379 if (cur.par() > old.par())
1381 if (cur.anchor().par() > old.par())
1382 --cur.anchor().par();
1384 if (selection_position_was_oldcursor_position) {
1385 // correct selection
1393 if (pars_[old.par()].stripLeadingSpaces())
1400 ParagraphList & LyXText::paragraphs() const
1402 return const_cast<ParagraphList &>(pars_);
1406 void LyXText::recUndo(par_type first, par_type last) const
1408 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1412 void LyXText::recUndo(par_type par) const
1414 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1418 int defaultRowHeight()
1420 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);