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();
114 BufferParams const & params = bv()->buffer()->params();
115 pos_type const body_pos = pars_[pit].beginOfBody();
117 // We specialize the 95% common case:
118 if (!pars_[pit].getDepth()) {
119 LyXFont f = pars_[pit].getFontSettings(params, pos);
122 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
123 return f.realize(layout->reslabelfont);
125 return f.realize(layout->resfont);
128 // The uncommon case need not be optimized as much
131 layoutfont = layout->labelfont;
133 layoutfont = layout->font;
135 LyXFont font = pars_[pit].getFontSettings(params, pos);
136 font.realize(layoutfont);
141 // Realize with the fonts of lesser depth.
142 //font.realize(outerFont(pit, paragraphs()));
143 font.realize(defaultfont_);
149 LyXFont LyXText::getLayoutFont(par_type pit) const
151 LyXLayout_ptr const & layout = pars_[pit].layout();
153 if (!pars_[pit].getDepth())
154 return layout->resfont;
156 LyXFont font = layout->font;
157 // Realize with the fonts of lesser depth.
158 //font.realize(outerFont(pit, paragraphs()));
159 font.realize(defaultfont_);
165 LyXFont LyXText::getLabelFont(par_type pit) const
167 LyXLayout_ptr const & layout = pars_[pit].layout();
169 if (!pars_[pit].getDepth())
170 return layout->reslabelfont;
172 LyXFont font = layout->labelfont;
173 // Realize with the fonts of lesser depth.
174 font.realize(outerFont(pit, paragraphs()));
175 font.realize(defaultfont_);
181 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
184 LyXLayout_ptr const & layout = pars_[pit].layout();
186 // Get concrete layout font to reduce against
189 if (pos < pars_[pit].beginOfBody())
190 layoutfont = layout->labelfont;
192 layoutfont = layout->font;
194 // Realize against environment font information
195 if (pars_[pit].getDepth()) {
197 while (!layoutfont.resolved() &&
198 tp != par_type(paragraphs().size()) &&
199 pars_[tp].getDepth()) {
200 tp = outerHook(tp, paragraphs());
201 if (tp != par_type(paragraphs().size()))
202 layoutfont.realize(pars_[tp].layout()->font);
206 layoutfont.realize(defaultfont_);
208 // Now, reduce font against full layout font
209 font.reduce(layoutfont);
211 pars_[pit].setFont(pos, font);
216 // Asger is not sure we want to do this...
217 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
220 LyXLayout_ptr const & layout = par.layout();
221 pos_type const psize = par.size();
224 for (pos_type pos = 0; pos < psize; ++pos) {
225 if (pos < par.beginOfBody())
226 layoutfont = layout->labelfont;
228 layoutfont = layout->font;
230 LyXFont tmpfont = par.getFontSettings(params, pos);
231 tmpfont.reduce(layoutfont);
232 par.setFont(pos, tmpfont);
237 // return past-the-last paragraph influenced by a layout change on pit
238 par_type LyXText::undoSpan(par_type pit)
240 par_type end = paragraphs().size();
241 par_type nextpit = pit + 1;
244 //because of parindents
245 if (!pars_[pit].getDepth())
246 return boost::next(nextpit);
247 //because of depth constrains
248 for (; nextpit != end; ++pit, ++nextpit) {
249 if (!pars_[pit].getDepth())
256 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
258 BOOST_ASSERT(start != end);
259 par_type undopit = undoSpan(end - 1);
260 recUndo(start, undopit - 1);
262 BufferParams const & bufparams = bv()->buffer()->params();
263 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
265 for (par_type pit = start; pit != end; ++pit) {
266 pars_[pit].applyLayout(lyxlayout);
267 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
268 if (lyxlayout->margintype == MARGIN_MANUAL)
269 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
276 // set layout over selection and make a total rebreak of those paragraphs
277 void LyXText::setLayout(LCursor & cur, string const & layout)
279 BOOST_ASSERT(this == cur.text());
280 // special handling of new environment insets
281 BufferParams const & params = bv()->buffer()->params();
282 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
283 if (lyxlayout->is_environment) {
284 // move everything in a new environment inset
285 lyxerr << "setting layout " << layout << endl;
286 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
287 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
288 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
289 InsetBase * inset = new InsetEnvironment(params, layout);
290 insertInset(cur, inset);
291 //inset->edit(cur, true);
292 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
296 par_type start = cur.selBegin().par();
297 par_type end = cur.selEnd().par() + 1;
298 par_type endpit = setLayout(start, end, layout);
299 redoParagraphs(start, endpit);
307 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
309 if (!cur.selection()) {
313 beg = cur.selBegin().par();
314 end = cur.selEnd().par() + 1;
319 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
320 Paragraph const & par, int max_depth)
322 if (par.layout()->labeltype == LABEL_BIBLIO)
324 int const depth = par.params().depth();
325 if (type == LyXText::INC_DEPTH && depth < max_depth)
327 if (type == LyXText::DEC_DEPTH && depth > 0)
336 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
338 BOOST_ASSERT(this == cur.text());
340 getSelectionSpan(cur, beg, end);
343 max_depth = pars_[beg - 1].getMaxDepthAfter();
345 for (par_type pit = beg; pit != end; ++pit) {
346 if (::changeDepthAllowed(type, pars_[pit], max_depth))
348 max_depth = pars_[pit].getMaxDepthAfter();
354 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
356 BOOST_ASSERT(this == cur.text());
358 getSelectionSpan(cur, beg, end);
359 recordUndoSelection(cur);
363 max_depth = pars_[beg - 1].getMaxDepthAfter();
365 for (par_type pit = beg; pit != end; ++pit) {
366 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
367 int const depth = pars_[pit].params().depth();
368 if (type == INC_DEPTH)
369 pars_[pit].params().depth(depth + 1);
371 pars_[pit].params().depth(depth - 1);
373 max_depth = pars_[pit].getMaxDepthAfter();
375 // this handles the counter labels, and also fixes up
376 // depth values for follow-on (child) paragraphs
381 // set font over selection
382 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
384 BOOST_ASSERT(this == cur.text());
385 // if there is no selection just set the current_font
386 if (!cur.selection()) {
387 // Determine basis font
389 par_type pit = cur.par();
390 if (cur.pos() < pars_[pit].beginOfBody())
391 layoutfont = getLabelFont(pit);
393 layoutfont = getLayoutFont(pit);
395 // Update current font
396 real_current_font.update(font,
397 bv()->buffer()->params().language,
400 // Reduce to implicit settings
401 current_font = real_current_font;
402 current_font.reduce(layoutfont);
403 // And resolve it completely
404 real_current_font.realize(layoutfont);
409 // Ok, we have a selection.
410 recordUndoSelection(cur);
412 par_type const beg = cur.selBegin().par();
413 par_type const end = cur.selEnd().par();
415 DocIterator pos = cur.selectionBegin();
416 DocIterator posend = cur.selectionEnd();
418 BufferParams const & params = bv()->buffer()->params();
420 for (; pos != posend; pos.forwardChar()) {
421 LyXFont f = getFont(pos.par(), pos.pos());
422 f.update(font, params.language, toggleall);
423 setCharFont(pos.par(), pos.pos(), f);
426 redoParagraphs(beg, end + 1);
430 // the cursor set functions have a special mechanism. When they
431 // realize you left an empty paragraph, they will delete it.
433 void LyXText::cursorHome(LCursor & cur)
435 BOOST_ASSERT(this == cur.text());
436 setCursor(cur, cur.par(), cur.textRow().pos());
440 void LyXText::cursorEnd(LCursor & cur)
442 BOOST_ASSERT(this == cur.text());
443 // if not on the last row of the par, put the cursor before
445 pos_type const end = cur.textRow().endpos();
446 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
450 void LyXText::cursorTop(LCursor & cur)
452 BOOST_ASSERT(this == cur.text());
453 setCursor(cur, 0, 0);
457 void LyXText::cursorBottom(LCursor & cur)
459 BOOST_ASSERT(this == cur.text());
460 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
464 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
466 BOOST_ASSERT(this == cur.text());
467 // If the mask is completely neutral, tell user
468 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
469 // Could only happen with user style
470 cur.message(_("No font change defined. "
471 "Use Character under the Layout menu to define font change."));
475 // Try implicit word selection
476 // If there is a change in the language the implicit word selection
478 CursorSlice resetCursor = cur.top();
479 bool implicitSelection =
480 font.language() == ignore_language
481 && font.number() == LyXFont::IGNORE
482 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
485 setFont(cur, font, toggleall);
487 // Implicit selections are cleared afterwards
488 // and cursor is set to the original position.
489 if (implicitSelection) {
490 cur.clearSelection();
491 cur.top() = resetCursor;
497 string LyXText::getStringToIndex(LCursor & cur)
499 BOOST_ASSERT(this == cur.text());
500 // Try implicit word selection
501 // If there is a change in the language the implicit word selection
503 CursorSlice const reset_cursor = cur.top();
504 bool const implicitSelection =
505 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
508 if (!cur.selection())
509 cur.message(_("Nothing to index!"));
510 else if (cur.selBegin().par() != cur.selEnd().par())
511 cur.message(_("Cannot index more than one paragraph!"));
513 idxstring = cur.selectionAsString(false);
515 // Reset cursors to their original position.
516 cur.top() = reset_cursor;
519 // Clear the implicit selection.
520 if (implicitSelection)
521 cur.clearSelection();
527 void LyXText::setParagraph(LCursor & cur,
528 Spacing const & spacing, LyXAlignment align,
529 string const & labelwidthstring, bool noindent)
531 BOOST_ASSERT(cur.text());
532 // make sure that the depth behind the selection are restored, too
533 par_type undopit = undoSpan(cur.selEnd().par());
534 recUndo(cur.selBegin().par(), undopit - 1);
536 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
538 Paragraph & par = pars_[pit];
539 ParagraphParameters & params = par.params();
540 params.spacing(spacing);
542 // does the layout allow the new alignment?
543 LyXLayout_ptr const & layout = par.layout();
545 if (align == LYX_ALIGN_LAYOUT)
546 align = layout->align;
547 if (align & layout->alignpossible) {
548 if (align == layout->align)
549 params.align(LYX_ALIGN_LAYOUT);
553 par.setLabelWidthString(labelwidthstring);
554 params.noindent(noindent);
557 redoParagraphs(cur.selBegin().par(), undopit);
561 string expandLabel(LyXTextClass const & textclass,
562 LyXLayout_ptr const & layout, bool appendix)
564 string fmt = appendix ?
565 layout->labelstring_appendix() : layout->labelstring();
567 // handle 'inherited level parts' in 'fmt',
568 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
569 size_t const i = fmt.find('@', 0);
570 if (i != string::npos) {
571 size_t const j = fmt.find('@', i + 1);
572 if (j != string::npos) {
573 string parent(fmt, i + 1, j - i - 1);
574 string label = expandLabel(textclass, textclass[parent], appendix);
575 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
579 return textclass.counters().counterLabel(fmt);
585 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
587 int const cur_labeltype = pars[pit].layout()->labeltype;
589 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
592 int const cur_depth = pars[pit].getDepth();
594 par_type prev_pit = pit - 1;
596 int const prev_depth = pars[prev_pit].getDepth();
597 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
598 if (prev_depth == 0 && cur_depth > 0) {
599 if (prev_labeltype == cur_labeltype) {
600 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
603 } else if (prev_depth < cur_depth) {
604 if (prev_labeltype == cur_labeltype) {
605 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
608 } else if (prev_depth == cur_depth) {
609 if (prev_labeltype == cur_labeltype) {
610 pars[pit].itemdepth = pars[prev_pit].itemdepth;
614 if (prev_pit == first_pit)
622 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
623 par_type firstpit, Counters & counters)
628 int const cur_depth = pars[pit].getDepth();
629 par_type prev_pit = pit - 1;
631 int const prev_depth = pars[prev_pit].getDepth();
632 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
633 if (prev_depth <= cur_depth) {
634 if (prev_labeltype != LABEL_ENUMERATE) {
635 switch (pars[pit].itemdepth) {
637 counters.reset("enumi");
639 counters.reset("enumii");
641 counters.reset("enumiii");
643 counters.reset("enumiv");
649 if (prev_pit == firstpit)
659 // set the counter of a paragraph. This includes the labels
660 void LyXText::setCounter(Buffer const & buf, par_type pit)
662 BufferParams const & bufparams = buf.params();
663 LyXTextClass const & textclass = bufparams.getLyXTextClass();
664 LyXLayout_ptr const & layout = pars_[pit].layout();
665 par_type first_pit = 0;
666 Counters & counters = textclass.counters();
669 pars_[pit].itemdepth = 0;
671 if (pit == first_pit) {
672 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
674 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
675 if (!pars_[pit].params().appendix() &&
676 pars_[pit].params().startOfAppendix()) {
677 pars_[pit].params().appendix(true);
678 textclass.counters().reset();
681 // Maybe we have to increment the item depth.
682 incrementItemDepth(pars_, pit, first_pit);
685 // erase what was there before
686 pars_[pit].params().labelString(string());
688 if (layout->margintype == MARGIN_MANUAL) {
689 if (pars_[pit].params().labelWidthString().empty())
690 pars_[pit].setLabelWidthString(layout->labelstring());
692 pars_[pit].setLabelWidthString(string());
695 // is it a layout that has an automatic label?
696 if (layout->labeltype == LABEL_COUNTER) {
697 BufferParams const & bufparams = buf.params();
698 LyXTextClass const & textclass = bufparams.getLyXTextClass();
699 counters.step(layout->counter);
700 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
701 pars_[pit].params().labelString(label);
702 } else if (layout->labeltype == LABEL_ITEMIZE) {
703 // At some point of time we should do something more
704 // clever here, like:
705 // pars_[pit].params().labelString(
706 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
707 // for now, use a simple hardcoded label
709 switch (pars_[pit].itemdepth) {
724 pars_[pit].params().labelString(itemlabel);
725 } else if (layout->labeltype == LABEL_ENUMERATE) {
726 // Maybe we have to reset the enumeration counter.
727 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
730 // Yes I know this is a really, really! bad solution
732 string enumcounter = "enum";
734 switch (pars_[pit].itemdepth) {
746 // not a valid enumdepth...
750 counters.step(enumcounter);
752 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
753 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
754 counters.step("bibitem");
755 int number = counters.value("bibitem");
756 if (pars_[pit].bibitem()) {
757 pars_[pit].bibitem()->setCounter(number);
758 pars_[pit].params().labelString(layout->labelstring());
760 // In biblio should't be following counters but...
762 string s = buf.B_(layout->labelstring());
765 if (layout->labeltype == LABEL_SENSITIVE) {
766 par_type end = paragraphs().size();
767 par_type tmppit = pit;
770 while (tmppit != end && pars_[tmppit].inInset()
771 // the single '=' is intended below
772 && (in = pars_[tmppit].inInset()))
774 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
775 in->lyxCode() == InsetBase::WRAP_CODE) {
779 Paragraph const * owner = &ownerPar(buf, in);
781 for ( ; tmppit != end; ++tmppit)
782 if (&pars_[tmppit] == owner)
790 if (in->lyxCode() == InsetBase::FLOAT_CODE)
791 type = static_cast<InsetFloat*>(in)->params().type;
792 else if (in->lyxCode() == InsetBase::WRAP_CODE)
793 type = static_cast<InsetWrap*>(in)->params().type;
797 Floating const & fl = textclass.floats().getType(type);
799 counters.step(fl.type());
801 // Doesn't work... yet.
802 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
804 // par->SetLayout(0);
805 // s = layout->labelstring;
806 s = _("Senseless: ");
809 pars_[pit].params().labelString(s);
815 // Updates all counters.
816 void LyXText::updateCounters()
819 bv()->buffer()->params().getLyXTextClass().counters().reset();
821 bool update_pos = false;
823 par_type end = paragraphs().size();
824 for (par_type pit = 0; pit != end; ++pit) {
825 string const oldLabel = pars_[pit].params().labelString();
828 maxdepth = pars_[pit - 1].getMaxDepthAfter();
830 if (pars_[pit].params().depth() > maxdepth)
831 pars_[pit].params().depth(maxdepth);
833 // setCounter can potentially change the labelString.
834 setCounter(*bv()->buffer(), pit);
835 string const & newLabel = pars_[pit].params().labelString();
836 if (oldLabel != newLabel) {
837 //lyxerr << "changing labels: old: " << oldLabel << " new: "
838 // << newLabel << endl;
839 redoParagraphInternal(pit);
844 updateParPositions();
848 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
850 BOOST_ASSERT(this == cur.text());
852 cur.paragraph().insertInset(cur.pos(), inset);
857 // needed to insert the selection
858 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
860 par_type pit = cur.par();
861 par_type endpit = cur.par() + 1;
862 pos_type pos = cur.pos();
865 // only to be sure, should not be neccessary
866 cur.clearSelection();
867 bv()->buffer()->insertStringAsLines(pars_, pit, pos, current_font, str);
869 redoParagraphs(cur.par(), endpit);
871 setCursor(cur, cur.par(), pos);
876 // turn double CR to single CR, others are converted into one
877 // blank. Then insertStringAsLines is called
878 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
880 string linestr = str;
881 bool newline_inserted = false;
883 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
884 if (linestr[i] == '\n') {
885 if (newline_inserted) {
886 // we know that \r will be ignored by
887 // insertStringAsLines. Of course, it is a dirty
888 // trick, but it works...
889 linestr[i - 1] = '\r';
893 newline_inserted = true;
895 } else if (IsPrintable(linestr[i])) {
896 newline_inserted = false;
899 insertStringAsLines(cur, linestr);
903 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
904 bool setfont, bool boundary)
907 setCursorIntern(cur, par, pos, setfont, boundary);
908 return deleteEmptyParagraphMechanism(cur, old);
912 void LyXText::setCursor(CursorSlice & cur, par_type par,
913 pos_type pos, bool boundary)
915 BOOST_ASSERT(par != int(paragraphs().size()));
919 cur.boundary() = boundary;
921 // no rows, no fun...
922 if (paragraphs().begin()->rows.empty())
925 // now some strict checking
926 Paragraph & para = getPar(par);
927 Row const & row = *para.getRow(pos);
928 pos_type const end = row.endpos();
930 // None of these should happen, but we're scaredy-cats
932 lyxerr << "dont like -1" << endl;
936 if (pos > para.size()) {
937 lyxerr << "dont like 1, pos: " << pos
938 << " size: " << para.size()
939 << " row.pos():" << row.pos()
940 << " par: " << par << endl;
945 lyxerr << "dont like 2, pos: " << pos
946 << " size: " << para.size()
947 << " row.pos():" << row.pos()
948 << " par: " << par << endl;
949 // This shouldn't happen.
953 if (pos < row.pos()) {
954 lyxerr << "dont like 3 please report pos:" << pos
955 << " size: " << para.size()
956 << " row.pos():" << row.pos()
957 << " par: " << par << endl;
963 void LyXText::setCursorIntern(LCursor & cur,
964 par_type par, pos_type pos, bool setfont, bool boundary)
966 setCursor(cur.top(), par, pos, boundary);
967 cur.x_target() = cursorX(cur.top());
973 void LyXText::setCurrentFont(LCursor & cur)
975 BOOST_ASSERT(this == cur.text());
976 pos_type pos = cur.pos();
977 par_type pit = cur.par();
979 if (cur.boundary() && pos > 0)
983 if (pos == cur.lastpos())
985 else // potentional bug... BUG (Lgb)
986 if (pars_[pit].isSeparator(pos)) {
987 if (pos > cur.textRow().pos() &&
988 bidi.level(pos) % 2 ==
989 bidi.level(pos - 1) % 2)
991 else if (pos + 1 < cur.lastpos())
996 BufferParams const & bufparams = bv()->buffer()->params();
997 current_font = pars_[pit].getFontSettings(bufparams, pos);
998 real_current_font = getFont(pit, pos);
1000 if (cur.pos() == cur.lastpos()
1001 && bidi.isBoundary(*bv()->buffer(), pars_[pit], cur.pos())
1002 && !cur.boundary()) {
1003 Language const * lang = pars_[pit].getParLanguage(bufparams);
1004 current_font.setLanguage(lang);
1005 current_font.setNumber(LyXFont::OFF);
1006 real_current_font.setLanguage(lang);
1007 real_current_font.setNumber(LyXFont::OFF);
1012 // x is an absolute screen coord
1013 // returns the column near the specified x-coordinate of the row
1014 // x is set to the real beginning of this column
1015 pos_type LyXText::getColumnNearX(par_type pit,
1016 Row const & row, int & x, bool & boundary) const
1019 RowMetrics const r = computeRowMetrics(pit, row);
1021 pos_type vc = row.pos();
1022 pos_type end = row.endpos();
1024 LyXLayout_ptr const & layout = pars_[pit].layout();
1026 bool left_side = false;
1028 pos_type body_pos = pars_[pit].beginOfBody();
1031 double last_tmpx = tmpx;
1034 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1037 // check for empty row
1039 x = int(tmpx) + xo_;
1043 while (vc < end && tmpx <= x) {
1044 c = bidi.vis2log(vc);
1046 if (body_pos > 0 && c == body_pos - 1) {
1047 tmpx += r.label_hfill +
1048 font_metrics::width(layout->labelsep, getLabelFont(pit));
1049 if (pars_[pit].isLineSeparator(body_pos - 1))
1050 tmpx -= singleWidth(pit, body_pos - 1);
1053 if (hfillExpansion(pars_[pit], row, c)) {
1054 tmpx += singleWidth(pit, c);
1058 tmpx += r.label_hfill;
1059 } else if (pars_[pit].isSeparator(c)) {
1060 tmpx += singleWidth(pit, c);
1062 tmpx += r.separator;
1064 tmpx += singleWidth(pit, c);
1069 if ((tmpx + last_tmpx) / 2 > x) {
1074 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1077 // This (rtl_support test) is not needed, but gives
1078 // some speedup if rtl_support == false
1079 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1081 // If lastrow is false, we don't need to compute
1082 // the value of rtl.
1083 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1085 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1086 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1088 else if (vc == row.pos()) {
1089 c = bidi.vis2log(vc);
1090 if (bidi.level(c) % 2 == 1)
1093 c = bidi.vis2log(vc - 1);
1094 bool const rtl = (bidi.level(c) % 2 == 1);
1095 if (left_side == rtl) {
1097 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1101 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1102 if (bidi.level(end -1) % 2 == 0)
1103 tmpx -= singleWidth(pit, end - 1);
1105 tmpx += singleWidth(pit, end - 1);
1109 x = int(tmpx) + xo_;
1110 return c - row.pos();
1114 // x,y are absolute coordinates
1115 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1120 Row const & row = *getRowNearY(y, pit);
1121 lyxerr << "setCursorFromCoordinates:: hit row at: " << row.pos() << endl;
1123 int xx = x + xo_; // getRowNearX get absolute x coords
1124 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1125 setCursor(cur, pit, pos, true, bound);
1129 // x,y are absolute screen coordinates
1130 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1133 Row const & row = *getRowNearY(y - yo_, pit);
1136 int xx = x; // is modified by getColumnNearX
1137 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1140 cur.boundary() = bound;
1142 // try to descend into nested insets
1143 InsetBase * inset = checkInsetHit(x, y);
1147 // This should be just before or just behind the
1148 // cursor position set above.
1149 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1150 || inset == pars_[pit].getInset(pos));
1151 // Make sure the cursor points to the position before
1153 if (inset == pars_[pit].getInset(pos - 1))
1155 return inset->editXY(cur, x, y);
1159 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1161 if (cur.selection())
1163 if (cur.pos() == cur.lastpos())
1165 InsetBase * inset = cur.nextInset();
1166 if (!isHighlyEditableInset(inset))
1168 inset->edit(cur, front);
1173 void LyXText::cursorLeft(LCursor & cur)
1175 if (cur.pos() != 0) {
1176 bool boundary = cur.boundary();
1177 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1178 if (!checkAndActivateInset(cur, false)) {
1179 if (false && !boundary &&
1180 bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1181 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1186 if (cur.par() != 0) {
1187 // steps into the paragraph above
1188 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1193 void LyXText::cursorRight(LCursor & cur)
1195 if (false && cur.boundary()) {
1196 setCursor(cur, cur.par(), cur.pos(), true, false);
1200 if (cur.pos() != cur.lastpos()) {
1201 if (!checkAndActivateInset(cur, true)) {
1202 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1203 if (false && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1205 setCursor(cur, cur.par(), cur.pos(), true, true);
1210 if (cur.par() != cur.lastpar())
1211 setCursor(cur, cur.par() + 1, 0);
1215 void LyXText::cursorUp(LCursor & cur)
1217 Row const & row = cur.textRow();
1218 int x = cur.x_target();
1219 int y = cursorY(cur.top()) - row.baseline() - 1;
1220 setCursorFromCoordinates(cur, x, y);
1222 if (!cur.selection()) {
1223 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1224 if (inset_hit && isHighlyEditableInset(inset_hit))
1225 inset_hit->editXY(cur, cur.x_target(), y);
1230 void LyXText::cursorDown(LCursor & cur)
1232 Row const & row = cur.textRow();
1233 int x = cur.x_target();
1234 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1235 setCursorFromCoordinates(cur, x, y);
1237 if (!cur.selection()) {
1238 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1239 if (inset_hit && isHighlyEditableInset(inset_hit))
1240 inset_hit->editXY(cur, cur.x_target(), y);
1245 void LyXText::cursorUpParagraph(LCursor & cur)
1248 setCursor(cur, cur.par(), 0);
1249 else if (cur.par() != 0)
1250 setCursor(cur, cur.par() - 1, 0);
1254 void LyXText::cursorDownParagraph(LCursor & cur)
1256 if (cur.par() != cur.lastpar())
1257 setCursor(cur, cur.par() + 1, 0);
1259 setCursor(cur, cur.par(), cur.lastpos());
1263 // fix the cursor `cur' after a characters has been deleted at `where'
1264 // position. Called by deleteEmptyParagraphMechanism
1265 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1267 // do notheing if cursor is not in the paragraph where the
1268 // deletion occured,
1269 if (cur.par() != where.par())
1272 // if cursor position is after the deletion place update it
1273 if (cur.pos() > where.pos())
1276 // check also if we don't want to set the cursor on a spot behind the
1277 // pagragraph because we erased the last character.
1278 if (cur.pos() > cur.lastpos())
1279 cur.pos() = cur.lastpos();
1283 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1285 BOOST_ASSERT(cur.size() == old.size());
1286 // Would be wrong to delete anything if we have a selection.
1287 if (cur.selection())
1290 //lyxerr << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1291 Paragraph const & oldpar = pars_[old.par()];
1293 // We allow all kinds of "mumbo-jumbo" when freespacing.
1294 if (oldpar.isFreeSpacing())
1297 /* Ok I'll put some comments here about what is missing.
1298 I have fixed BackSpace (and thus Delete) to not delete
1299 double-spaces automagically. I have also changed Cut,
1300 Copy and Paste to hopefully do some sensible things.
1301 There are still some small problems that can lead to
1302 double spaces stored in the document file or space at
1303 the beginning of paragraphs(). This happens if you have
1304 the cursor between to spaces and then save. Or if you
1305 cut and paste and the selection have a space at the
1306 beginning and then save right after the paste. I am
1307 sure none of these are very hard to fix, but I will
1308 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1309 that I can get some feedback. (Lgb)
1312 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1313 // delete the LineSeparator.
1316 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1317 // delete the LineSeparator.
1320 // If the chars around the old cursor were spaces, delete one of them.
1321 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1323 // Only if the cursor has really moved.
1325 && old.pos() < oldpar.size()
1326 && oldpar.isLineSeparator(old.pos())
1327 && oldpar.isLineSeparator(old.pos() - 1)) {
1328 pars_[old.par()].erase(old.pos() - 1);
1329 #ifdef WITH_WARNINGS
1330 #warning This will not work anymore when we have multiple views of the same buffer
1331 // In this case, we will have to correct also the cursors held by
1332 // other bufferviews. It will probably be easier to do that in a more
1333 // automated way in CursorSlice code. (JMarc 26/09/2001)
1335 // correct all cursor parts
1336 fixCursorAfterDelete(cur.top(), old.top());
1337 fixCursorAfterDelete(cur.anchor(), old.top());
1342 // only do our magic if we changed paragraph
1343 if (old.par() == cur.par())
1346 // don't delete anything if this is the ONLY paragraph!
1347 if (pars_.size() == 1)
1350 // Do not delete empty paragraphs with keepempty set.
1351 if (oldpar.allowEmpty())
1354 // record if we have deleted a paragraph
1355 // we can't possibly have deleted a paragraph before this point
1356 bool deleted = false;
1358 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1359 // ok, we will delete something
1360 CursorSlice tmpcursor;
1364 bool selection_position_was_oldcursor_position =
1365 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1367 // This is a bit of a overkill. We change the old and the cur par
1368 // at max, certainly not everything in between...
1369 recUndo(old.par(), cur.par());
1372 pars_.erase(pars_.begin() + old.par());
1374 // Update cursor par offset if necessary.
1375 // Some 'iterator registration' would be nice that takes care of
1376 // such events. Maybe even signal/slot?
1377 if (cur.par() > old.par())
1379 if (cur.anchor().par() > old.par())
1380 --cur.anchor().par();
1382 if (selection_position_was_oldcursor_position) {
1383 // correct selection
1391 if (pars_[old.par()].stripLeadingSpaces())
1398 ParagraphList & LyXText::paragraphs() const
1400 return const_cast<ParagraphList &>(pars_);
1404 void LyXText::recUndo(par_type first, par_type last) const
1406 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1410 void LyXText::recUndo(par_type par) const
1412 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1416 int defaultRowHeight()
1418 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);