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"
30 #include "coordcache.h"
32 #include "CutAndPaste.h"
34 #include "dispatchresult.h"
35 #include "errorlist.h"
37 #include "FloatList.h"
38 #include "funcrequest.h"
44 #include "lyxrow_funcs.h"
45 #include "paragraph.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
51 #include "frontends/font_metrics.h"
52 #include "frontends/LyXView.h"
54 #include "insets/insetbibitem.h"
55 #include "insets/insetenv.h"
56 #include "insets/insetfloat.h"
57 #include "insets/insetwrap.h"
59 #include "support/lstrings.h"
60 #include "support/textutils.h"
61 #include "support/tostr.h"
67 using lyx::support::bformat;
70 using std::ostringstream;
74 LyXText::LyXText(BufferView * bv)
75 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
76 background_color_(LColor::background),
77 bv_owner(bv), xo_(0), yo_(0)
81 void LyXText::init(BufferView * bv)
85 maxwidth_ = bv->workWidth();
89 par_type const end = paragraphs().size();
90 for (par_type pit = 0; pit != end; ++pit)
91 pars_[pit].rows.clear();
93 current_font = getFont(0, 0);
94 redoParagraphs(0, end);
99 bool LyXText::isMainText() const
101 return &bv()->buffer()->text() == this;
105 // takes absolute x,y coordinates
106 InsetBase * LyXText::checkInsetHit(int x, int y) const
111 getParsInRange(paragraphs(),
113 bv()->top_y() - yo_ + bv()->workHeight(),
116 // convert to screen-absolute y coordinate
118 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
119 lyxerr << " pit: " << pit << " end: " << end << endl;
120 for (; pit != end; ++pit) {
121 InsetList::const_iterator iit = pars_[pit].insetlist.begin();
122 InsetList::const_iterator iend = pars_[pit].insetlist.end();
123 for (; iit != iend; ++iit) {
124 InsetBase * inset = iit->inset;
126 lyxerr << "examining inset " << inset << endl;
127 if (theCoords.insets_.has(inset))
129 << " xo: " << inset->xo() << "..." << inset->xo() + inset->width()
130 << " yo: " << inset->yo() - inset->ascent() << "..."
131 << inset->yo() + inset->descent() << endl;
133 lyxerr << " inset has no cached position";
135 if (inset->covers(x, y)) {
136 lyxerr << "Hit inset: " << inset << endl;
141 lyxerr << "No inset hit. " << endl;
147 // Gets the fully instantiated font at a given position in a paragraph
148 // Basically the same routine as Paragraph::getFont() in paragraph.C.
149 // The difference is that this one is used for displaying, and thus we
150 // are allowed to make cosmetic improvements. For instance make footnotes
152 LyXFont LyXText::getFont(par_type pit, pos_type pos) const
154 BOOST_ASSERT(pos >= 0);
156 LyXLayout_ptr const & layout = pars_[pit].layout();
160 BufferParams const & params = bv()->buffer()->params();
161 pos_type const body_pos = pars_[pit].beginOfBody();
163 // We specialize the 95% common case:
164 if (!pars_[pit].getDepth()) {
165 LyXFont f = pars_[pit].getFontSettings(params, pos);
168 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
169 return f.realize(layout->reslabelfont);
171 return f.realize(layout->resfont);
174 // The uncommon case need not be optimized as much
177 layoutfont = layout->labelfont;
179 layoutfont = layout->font;
181 LyXFont font = pars_[pit].getFontSettings(params, pos);
182 font.realize(layoutfont);
187 // Realize with the fonts of lesser depth.
188 //font.realize(outerFont(pit, paragraphs()));
189 font.realize(defaultfont_);
195 LyXFont LyXText::getLayoutFont(par_type pit) const
197 LyXLayout_ptr const & layout = pars_[pit].layout();
199 if (!pars_[pit].getDepth())
200 return layout->resfont;
202 LyXFont font = layout->font;
203 // Realize with the fonts of lesser depth.
204 //font.realize(outerFont(pit, paragraphs()));
205 font.realize(defaultfont_);
211 LyXFont LyXText::getLabelFont(par_type pit) const
213 LyXLayout_ptr const & layout = pars_[pit].layout();
215 if (!pars_[pit].getDepth())
216 return layout->reslabelfont;
218 LyXFont font = layout->labelfont;
219 // Realize with the fonts of lesser depth.
220 font.realize(outerFont(pit, paragraphs()));
221 font.realize(defaultfont_);
227 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
230 LyXLayout_ptr const & layout = pars_[pit].layout();
232 // Get concrete layout font to reduce against
235 if (pos < pars_[pit].beginOfBody())
236 layoutfont = layout->labelfont;
238 layoutfont = layout->font;
240 // Realize against environment font information
241 if (pars_[pit].getDepth()) {
243 while (!layoutfont.resolved() &&
244 tp != par_type(paragraphs().size()) &&
245 pars_[tp].getDepth()) {
246 tp = outerHook(tp, paragraphs());
247 if (tp != par_type(paragraphs().size()))
248 layoutfont.realize(pars_[tp].layout()->font);
252 layoutfont.realize(defaultfont_);
254 // Now, reduce font against full layout font
255 font.reduce(layoutfont);
257 pars_[pit].setFont(pos, font);
262 // Asger is not sure we want to do this...
263 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
266 LyXLayout_ptr const & layout = par.layout();
267 pos_type const psize = par.size();
270 for (pos_type pos = 0; pos < psize; ++pos) {
271 if (pos < par.beginOfBody())
272 layoutfont = layout->labelfont;
274 layoutfont = layout->font;
276 LyXFont tmpfont = par.getFontSettings(params, pos);
277 tmpfont.reduce(layoutfont);
278 par.setFont(pos, tmpfont);
283 // return past-the-last paragraph influenced by a layout change on pit
284 par_type LyXText::undoSpan(par_type pit)
286 par_type end = paragraphs().size();
287 par_type nextpit = pit + 1;
290 //because of parindents
291 if (!pars_[pit].getDepth())
292 return boost::next(nextpit);
293 //because of depth constrains
294 for (; nextpit != end; ++pit, ++nextpit) {
295 if (!pars_[pit].getDepth())
302 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
304 BOOST_ASSERT(start != end);
305 par_type undopit = undoSpan(end - 1);
306 recUndo(start, undopit - 1);
308 BufferParams const & bufparams = bv()->buffer()->params();
309 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
311 for (par_type pit = start; pit != end; ++pit) {
312 pars_[pit].applyLayout(lyxlayout);
313 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
314 if (lyxlayout->margintype == MARGIN_MANUAL)
315 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
322 // set layout over selection and make a total rebreak of those paragraphs
323 void LyXText::setLayout(LCursor & cur, string const & layout)
325 BOOST_ASSERT(this == cur.text());
326 // special handling of new environment insets
327 BufferView & bv = cur.bv();
328 BufferParams const & params = bv.buffer()->params();
329 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
330 if (lyxlayout->is_environment) {
331 // move everything in a new environment inset
332 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
333 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
334 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
335 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
336 InsetBase * inset = new InsetEnvironment(params, layout);
337 insertInset(cur, inset);
338 //inset->edit(cur, true);
339 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
343 par_type start = cur.selBegin().par();
344 par_type end = cur.selEnd().par() + 1;
345 par_type endpit = setLayout(start, end, layout);
346 redoParagraphs(start, endpit);
354 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
356 if (!cur.selection()) {
360 beg = cur.selBegin().par();
361 end = cur.selEnd().par() + 1;
366 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
367 Paragraph const & par, int max_depth)
369 if (par.layout()->labeltype == LABEL_BIBLIO)
371 int const depth = par.params().depth();
372 if (type == LyXText::INC_DEPTH && depth < max_depth)
374 if (type == LyXText::DEC_DEPTH && depth > 0)
383 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
385 BOOST_ASSERT(this == cur.text());
387 getSelectionSpan(cur, beg, end);
390 max_depth = pars_[beg - 1].getMaxDepthAfter();
392 for (par_type pit = beg; pit != end; ++pit) {
393 if (::changeDepthAllowed(type, pars_[pit], max_depth))
395 max_depth = pars_[pit].getMaxDepthAfter();
401 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
403 BOOST_ASSERT(this == cur.text());
405 getSelectionSpan(cur, beg, end);
406 recordUndoSelection(cur);
410 max_depth = pars_[beg - 1].getMaxDepthAfter();
412 for (par_type pit = beg; pit != end; ++pit) {
413 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
414 int const depth = pars_[pit].params().depth();
415 if (type == INC_DEPTH)
416 pars_[pit].params().depth(depth + 1);
418 pars_[pit].params().depth(depth - 1);
420 max_depth = pars_[pit].getMaxDepthAfter();
422 // this handles the counter labels, and also fixes up
423 // depth values for follow-on (child) paragraphs
428 // set font over selection
429 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
431 BOOST_ASSERT(this == cur.text());
432 // if there is no selection just set the current_font
433 if (!cur.selection()) {
434 // Determine basis font
436 par_type pit = cur.par();
437 if (cur.pos() < pars_[pit].beginOfBody())
438 layoutfont = getLabelFont(pit);
440 layoutfont = getLayoutFont(pit);
442 // Update current font
443 real_current_font.update(font,
444 cur.buffer().params().language,
447 // Reduce to implicit settings
448 current_font = real_current_font;
449 current_font.reduce(layoutfont);
450 // And resolve it completely
451 real_current_font.realize(layoutfont);
456 // Ok, we have a selection.
457 recordUndoSelection(cur);
459 par_type const beg = cur.selBegin().par();
460 par_type const end = cur.selEnd().par();
462 DocIterator pos = cur.selectionBegin();
463 DocIterator posend = cur.selectionEnd();
465 lyxerr[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
468 BufferParams const & params = cur.buffer().params();
470 // Don't use forwardChar here as posend might have
471 // pos() == lastpos() and forwardChar would miss it.
472 for (; pos != posend; pos.forwardPos()) {
473 if (pos.pos() != pos.lastpos()) {
474 LyXFont f = getFont(pos.par(), pos.pos());
475 f.update(font, params.language, toggleall);
476 setCharFont(pos.par(), pos.pos(), f);
480 redoParagraphs(beg, end + 1);
484 // the cursor set functions have a special mechanism. When they
485 // realize you left an empty paragraph, they will delete it.
487 void LyXText::cursorHome(LCursor & cur)
489 BOOST_ASSERT(this == cur.text());
490 setCursor(cur, cur.par(), cur.textRow().pos());
494 void LyXText::cursorEnd(LCursor & cur)
496 BOOST_ASSERT(this == cur.text());
497 // if not on the last row of the par, put the cursor before
499 pos_type const end = cur.textRow().endpos();
500 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
504 void LyXText::cursorTop(LCursor & cur)
506 BOOST_ASSERT(this == cur.text());
507 setCursor(cur, 0, 0);
511 void LyXText::cursorBottom(LCursor & cur)
513 BOOST_ASSERT(this == cur.text());
514 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
518 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
520 BOOST_ASSERT(this == cur.text());
521 // If the mask is completely neutral, tell user
522 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
523 // Could only happen with user style
524 cur.message(_("No font change defined. "
525 "Use Character under the Layout menu to define font change."));
529 // Try implicit word selection
530 // If there is a change in the language the implicit word selection
532 CursorSlice resetCursor = cur.top();
533 bool implicitSelection =
534 font.language() == ignore_language
535 && font.number() == LyXFont::IGNORE
536 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
539 setFont(cur, font, toggleall);
541 // Implicit selections are cleared afterwards
542 // and cursor is set to the original position.
543 if (implicitSelection) {
544 cur.clearSelection();
545 cur.top() = resetCursor;
551 string LyXText::getStringToIndex(LCursor & cur)
553 BOOST_ASSERT(this == cur.text());
554 // Try implicit word selection
555 // If there is a change in the language the implicit word selection
557 CursorSlice const reset_cursor = cur.top();
558 bool const implicitSelection =
559 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
562 if (!cur.selection())
563 cur.message(_("Nothing to index!"));
564 else if (cur.selBegin().par() != cur.selEnd().par())
565 cur.message(_("Cannot index more than one paragraph!"));
567 idxstring = cur.selectionAsString(false);
569 // Reset cursors to their original position.
570 cur.top() = reset_cursor;
573 // Clear the implicit selection.
574 if (implicitSelection)
575 cur.clearSelection();
581 void LyXText::setParagraph(LCursor & cur,
582 Spacing const & spacing, LyXAlignment align,
583 string const & labelwidthstring, bool noindent)
585 BOOST_ASSERT(cur.text());
586 // make sure that the depth behind the selection are restored, too
587 par_type undopit = undoSpan(cur.selEnd().par());
588 recUndo(cur.selBegin().par(), undopit - 1);
590 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
592 Paragraph & par = pars_[pit];
593 ParagraphParameters & params = par.params();
594 params.spacing(spacing);
596 // does the layout allow the new alignment?
597 LyXLayout_ptr const & layout = par.layout();
599 if (align == LYX_ALIGN_LAYOUT)
600 align = layout->align;
601 if (align & layout->alignpossible) {
602 if (align == layout->align)
603 params.align(LYX_ALIGN_LAYOUT);
607 par.setLabelWidthString(labelwidthstring);
608 params.noindent(noindent);
611 redoParagraphs(cur.selBegin().par(), undopit);
615 string expandLabel(LyXTextClass const & textclass,
616 LyXLayout_ptr const & layout, bool appendix)
618 string fmt = appendix ?
619 layout->labelstring_appendix() : layout->labelstring();
621 // handle 'inherited level parts' in 'fmt',
622 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
623 size_t const i = fmt.find('@', 0);
624 if (i != string::npos) {
625 size_t const j = fmt.find('@', i + 1);
626 if (j != string::npos) {
627 string parent(fmt, i + 1, j - i - 1);
628 string label = expandLabel(textclass, textclass[parent], appendix);
629 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
633 return textclass.counters().counterLabel(fmt);
639 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
641 int const cur_labeltype = pars[pit].layout()->labeltype;
643 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
646 int const cur_depth = pars[pit].getDepth();
648 par_type prev_pit = pit - 1;
650 int const prev_depth = pars[prev_pit].getDepth();
651 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
652 if (prev_depth == 0 && cur_depth > 0) {
653 if (prev_labeltype == cur_labeltype) {
654 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
657 } else if (prev_depth < cur_depth) {
658 if (prev_labeltype == cur_labeltype) {
659 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
662 } else if (prev_depth == cur_depth) {
663 if (prev_labeltype == cur_labeltype) {
664 pars[pit].itemdepth = pars[prev_pit].itemdepth;
668 if (prev_pit == first_pit)
676 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
677 par_type firstpit, Counters & counters)
682 int const cur_depth = pars[pit].getDepth();
683 par_type prev_pit = pit - 1;
685 int const prev_depth = pars[prev_pit].getDepth();
686 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
687 if (prev_depth <= cur_depth) {
688 if (prev_labeltype != LABEL_ENUMERATE) {
689 switch (pars[pit].itemdepth) {
691 counters.reset("enumi");
693 counters.reset("enumii");
695 counters.reset("enumiii");
697 counters.reset("enumiv");
703 if (prev_pit == firstpit)
713 // set the counter of a paragraph. This includes the labels
714 void LyXText::setCounter(Buffer const & buf, par_type pit)
716 BufferParams const & bufparams = buf.params();
717 LyXTextClass const & textclass = bufparams.getLyXTextClass();
718 LyXLayout_ptr const & layout = pars_[pit].layout();
719 par_type first_pit = 0;
720 Counters & counters = textclass.counters();
723 pars_[pit].itemdepth = 0;
725 if (pit == first_pit) {
726 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
728 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
729 if (!pars_[pit].params().appendix() &&
730 pars_[pit].params().startOfAppendix()) {
731 pars_[pit].params().appendix(true);
732 textclass.counters().reset();
735 // Maybe we have to increment the item depth.
736 incrementItemDepth(pars_, pit, first_pit);
739 // erase what was there before
740 pars_[pit].params().labelString(string());
742 if (layout->margintype == MARGIN_MANUAL) {
743 if (pars_[pit].params().labelWidthString().empty())
744 pars_[pit].setLabelWidthString(layout->labelstring());
746 pars_[pit].setLabelWidthString(string());
749 // is it a layout that has an automatic label?
750 if (layout->labeltype == LABEL_COUNTER) {
751 BufferParams const & bufparams = buf.params();
752 LyXTextClass const & textclass = bufparams.getLyXTextClass();
753 counters.step(layout->counter);
754 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
755 pars_[pit].params().labelString(label);
756 } else if (layout->labeltype == LABEL_ITEMIZE) {
757 // At some point of time we should do something more
758 // clever here, like:
759 // pars_[pit].params().labelString(
760 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
761 // for now, use a simple hardcoded label
763 switch (pars_[pit].itemdepth) {
778 pars_[pit].params().labelString(itemlabel);
779 } else if (layout->labeltype == LABEL_ENUMERATE) {
780 // Maybe we have to reset the enumeration counter.
781 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
784 // Yes I know this is a really, really! bad solution
786 string enumcounter = "enum";
788 switch (pars_[pit].itemdepth) {
800 // not a valid enumdepth...
804 counters.step(enumcounter);
806 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
807 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
808 counters.step("bibitem");
809 int number = counters.value("bibitem");
810 if (pars_[pit].bibitem()) {
811 pars_[pit].bibitem()->setCounter(number);
812 pars_[pit].params().labelString(layout->labelstring());
814 // In biblio should't be following counters but...
816 string s = buf.B_(layout->labelstring());
819 if (layout->labeltype == LABEL_SENSITIVE) {
820 par_type end = paragraphs().size();
821 par_type tmppit = pit;
824 while (tmppit != end) {
825 in = pars_[tmppit].inInset();
826 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
827 in->lyxCode() == InsetBase::WRAP_CODE) {
831 Paragraph const * owner = &ownerPar(buf, in);
833 for ( ; tmppit != end; ++tmppit)
834 if (&pars_[tmppit] == owner)
842 if (in->lyxCode() == InsetBase::FLOAT_CODE)
843 type = static_cast<InsetFloat*>(in)->params().type;
844 else if (in->lyxCode() == InsetBase::WRAP_CODE)
845 type = static_cast<InsetWrap*>(in)->params().type;
849 Floating const & fl = textclass.floats().getType(type);
851 counters.step(fl.type());
853 // Doesn't work... yet.
854 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
856 // par->SetLayout(0);
857 // s = layout->labelstring;
858 s = _("Senseless: ");
861 pars_[pit].params().labelString(s);
867 // Updates all counters.
868 void LyXText::updateCounters()
871 bv()->buffer()->params().getLyXTextClass().counters().reset();
873 bool update_pos = false;
875 par_type end = paragraphs().size();
876 for (par_type pit = 0; pit != end; ++pit) {
877 string const oldLabel = pars_[pit].params().labelString();
880 maxdepth = pars_[pit - 1].getMaxDepthAfter();
882 if (pars_[pit].params().depth() > maxdepth)
883 pars_[pit].params().depth(maxdepth);
885 // setCounter can potentially change the labelString.
886 setCounter(*bv()->buffer(), pit);
887 string const & newLabel = pars_[pit].params().labelString();
888 if (oldLabel != newLabel) {
889 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
890 // << newLabel << endl;
891 redoParagraphInternal(pit);
896 updateParPositions();
900 // this really should just inset the inset and not move the cursor.
901 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
903 BOOST_ASSERT(this == cur.text());
905 cur.paragraph().insertInset(cur.pos(), inset);
910 // needed to insert the selection
911 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
913 par_type pit = cur.par();
914 par_type endpit = cur.par() + 1;
915 pos_type pos = cur.pos();
918 // only to be sure, should not be neccessary
919 cur.clearSelection();
920 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
922 redoParagraphs(cur.par(), endpit);
924 setCursor(cur, cur.par(), pos);
929 // turn double CR to single CR, others are converted into one
930 // blank. Then insertStringAsLines is called
931 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
933 string linestr = str;
934 bool newline_inserted = false;
936 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
937 if (linestr[i] == '\n') {
938 if (newline_inserted) {
939 // we know that \r will be ignored by
940 // insertStringAsLines. Of course, it is a dirty
941 // trick, but it works...
942 linestr[i - 1] = '\r';
946 newline_inserted = true;
948 } else if (IsPrintable(linestr[i])) {
949 newline_inserted = false;
952 insertStringAsLines(cur, linestr);
956 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
957 bool setfont, bool boundary)
960 setCursorIntern(cur, par, pos, setfont, boundary);
961 return deleteEmptyParagraphMechanism(cur, old);
965 void LyXText::setCursor(CursorSlice & cur, par_type par,
966 pos_type pos, bool boundary)
968 BOOST_ASSERT(par != int(paragraphs().size()));
972 cur.boundary() = boundary;
974 // no rows, no fun...
975 if (paragraphs().begin()->rows.empty())
978 // now some strict checking
979 Paragraph & para = getPar(par);
980 Row const & row = *para.getRow(pos);
981 pos_type const end = row.endpos();
983 // None of these should happen, but we're scaredy-cats
985 lyxerr << "dont like -1" << endl;
989 if (pos > para.size()) {
990 lyxerr << "dont like 1, pos: " << pos
991 << " size: " << para.size()
992 << " row.pos():" << row.pos()
993 << " par: " << par << endl;
998 lyxerr << "dont like 2, pos: " << pos
999 << " size: " << para.size()
1000 << " row.pos():" << row.pos()
1001 << " par: " << par << endl;
1002 // This shouldn't happen.
1003 BOOST_ASSERT(false);
1006 if (pos < row.pos()) {
1007 lyxerr << "dont like 3 please report pos:" << pos
1008 << " size: " << para.size()
1009 << " row.pos():" << row.pos()
1010 << " par: " << par << endl;
1011 BOOST_ASSERT(false);
1016 void LyXText::setCursorIntern(LCursor & cur,
1017 par_type par, pos_type pos, bool setfont, bool boundary)
1019 setCursor(cur.top(), par, pos, boundary);
1020 cur.x_target() = cursorX(cur.top());
1022 setCurrentFont(cur);
1026 void LyXText::setCurrentFont(LCursor & cur)
1028 BOOST_ASSERT(this == cur.text());
1029 pos_type pos = cur.pos();
1030 par_type pit = cur.par();
1032 if (cur.boundary() && pos > 0)
1036 if (pos == cur.lastpos())
1038 else // potentional bug... BUG (Lgb)
1039 if (pars_[pit].isSeparator(pos)) {
1040 if (pos > cur.textRow().pos() &&
1041 bidi.level(pos) % 2 ==
1042 bidi.level(pos - 1) % 2)
1044 else if (pos + 1 < cur.lastpos())
1049 BufferParams const & bufparams = cur.buffer().params();
1050 current_font = pars_[pit].getFontSettings(bufparams, pos);
1051 real_current_font = getFont(pit, pos);
1053 if (cur.pos() == cur.lastpos()
1054 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1055 && !cur.boundary()) {
1056 Language const * lang = pars_[pit].getParLanguage(bufparams);
1057 current_font.setLanguage(lang);
1058 current_font.setNumber(LyXFont::OFF);
1059 real_current_font.setLanguage(lang);
1060 real_current_font.setNumber(LyXFont::OFF);
1065 // x is an absolute screen coord
1066 // returns the column near the specified x-coordinate of the row
1067 // x is set to the real beginning of this column
1068 pos_type LyXText::getColumnNearX(par_type pit,
1069 Row const & row, int & x, bool & boundary) const
1072 RowMetrics const r = computeRowMetrics(pit, row);
1074 pos_type vc = row.pos();
1075 pos_type end = row.endpos();
1077 LyXLayout_ptr const & layout = pars_[pit].layout();
1079 bool left_side = false;
1081 pos_type body_pos = pars_[pit].beginOfBody();
1084 double last_tmpx = tmpx;
1087 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1090 // check for empty row
1092 x = int(tmpx) + xo_;
1096 while (vc < end && tmpx <= x) {
1097 c = bidi.vis2log(vc);
1099 if (body_pos > 0 && c == body_pos - 1) {
1100 tmpx += r.label_hfill +
1101 font_metrics::width(layout->labelsep, getLabelFont(pit));
1102 if (pars_[pit].isLineSeparator(body_pos - 1))
1103 tmpx -= singleWidth(pit, body_pos - 1);
1106 if (hfillExpansion(pars_[pit], row, c)) {
1107 tmpx += singleWidth(pit, c);
1111 tmpx += r.label_hfill;
1112 } else if (pars_[pit].isSeparator(c)) {
1113 tmpx += singleWidth(pit, c);
1115 tmpx += r.separator;
1117 tmpx += singleWidth(pit, c);
1122 if ((tmpx + last_tmpx) / 2 > x) {
1127 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1130 // This (rtl_support test) is not needed, but gives
1131 // some speedup if rtl_support == false
1132 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1134 // If lastrow is false, we don't need to compute
1135 // the value of rtl.
1136 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1138 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1139 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1141 else if (vc == row.pos()) {
1142 c = bidi.vis2log(vc);
1143 if (bidi.level(c) % 2 == 1)
1146 c = bidi.vis2log(vc - 1);
1147 bool const rtl = (bidi.level(c) % 2 == 1);
1148 if (left_side == rtl) {
1150 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1154 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1155 if (bidi.level(end -1) % 2 == 0)
1156 tmpx -= singleWidth(pit, end - 1);
1158 tmpx += singleWidth(pit, end - 1);
1162 x = int(tmpx) + xo_;
1163 return c - row.pos();
1167 // y is relative to this LyXText's top
1168 // this is only used in the two functions below
1169 Row const & LyXText::getRowNearY(int y, par_type & pit) const
1171 BOOST_ASSERT(!paragraphs().empty());
1172 BOOST_ASSERT(!paragraphs().begin()->rows.empty());
1173 par_type const pend = paragraphs().size() - 1;
1175 while (int(pars_[pit].y + pars_[pit].height) < y && pit != pend)
1178 RowList::iterator rit = pars_[pit].rows.end();
1179 RowList::iterator const rbegin = pars_[pit].rows.begin();
1182 } while (rit != rbegin && int(pars_[pit].y + rit->y_offset()) > y);
1188 // x,y are absolute coordinates
1189 // sets cursor only within this LyXText
1190 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1195 Row const & row = getRowNearY(y, pit);
1196 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1197 << row.pos() << endl;
1199 int xx = x + xo_; // getRowNearX get absolute x coords
1200 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1201 setCursor(cur, pit, pos, true, bound);
1205 // x,y are absolute screen coordinates
1206 // sets cursor recursively descending into nested editable insets
1207 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1210 Row const & row = getRowNearY(y - yo_, pit);
1213 int xx = x; // is modified by getColumnNearX
1214 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1217 cur.boundary() = bound;
1219 // try to descend into nested insets
1220 InsetBase * inset = checkInsetHit(x, y);
1221 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1225 // This should be just before or just behind the
1226 // cursor position set above.
1227 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1228 || inset == pars_[pit].getInset(pos));
1229 // Make sure the cursor points to the position before
1231 if (inset == pars_[pit].getInset(pos - 1))
1233 return inset->editXY(cur, x, y);
1237 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1239 if (cur.selection())
1241 if (cur.pos() == cur.lastpos())
1243 InsetBase * inset = cur.nextInset();
1244 if (!isHighlyEditableInset(inset))
1246 inset->edit(cur, front);
1251 void LyXText::cursorLeft(LCursor & cur)
1253 if (cur.pos() != 0) {
1254 bool boundary = cur.boundary();
1255 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1256 if (!checkAndActivateInset(cur, false)) {
1257 if (false && !boundary &&
1258 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1259 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1264 if (cur.par() != 0) {
1265 // steps into the paragraph above
1266 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1271 void LyXText::cursorRight(LCursor & cur)
1273 if (false && cur.boundary()) {
1274 setCursor(cur, cur.par(), cur.pos(), true, false);
1278 if (cur.pos() != cur.lastpos()) {
1279 if (!checkAndActivateInset(cur, true)) {
1280 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1281 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1283 setCursor(cur, cur.par(), cur.pos(), true, true);
1288 if (cur.par() != cur.lastpar())
1289 setCursor(cur, cur.par() + 1, 0);
1293 void LyXText::cursorUp(LCursor & cur)
1295 Row const & row = cur.textRow();
1296 int x = cur.x_target();
1297 int y = cursorY(cur.top()) - row.baseline() - 1;
1298 setCursorFromCoordinates(cur, x, y);
1300 if (!cur.selection()) {
1301 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1302 if (inset_hit && isHighlyEditableInset(inset_hit))
1303 inset_hit->editXY(cur, cur.x_target(), y);
1308 void LyXText::cursorDown(LCursor & cur)
1310 Row const & row = cur.textRow();
1311 int x = cur.x_target();
1312 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1313 setCursorFromCoordinates(cur, x, y);
1315 if (!cur.selection()) {
1316 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1317 if (inset_hit && isHighlyEditableInset(inset_hit))
1318 inset_hit->editXY(cur, cur.x_target(), y);
1323 void LyXText::cursorUpParagraph(LCursor & cur)
1326 setCursor(cur, cur.par(), 0);
1327 else if (cur.par() != 0)
1328 setCursor(cur, cur.par() - 1, 0);
1332 void LyXText::cursorDownParagraph(LCursor & cur)
1334 if (cur.par() != cur.lastpar())
1335 setCursor(cur, cur.par() + 1, 0);
1337 setCursor(cur, cur.par(), cur.lastpos());
1341 // fix the cursor `cur' after a characters has been deleted at `where'
1342 // position. Called by deleteEmptyParagraphMechanism
1343 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1345 // do notheing if cursor is not in the paragraph where the
1346 // deletion occured,
1347 if (cur.par() != where.par())
1350 // if cursor position is after the deletion place update it
1351 if (cur.pos() > where.pos())
1354 // check also if we don't want to set the cursor on a spot behind the
1355 // pagragraph because we erased the last character.
1356 if (cur.pos() > cur.lastpos())
1357 cur.pos() = cur.lastpos();
1361 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1363 BOOST_ASSERT(cur.size() == old.size());
1364 // Would be wrong to delete anything if we have a selection.
1365 if (cur.selection())
1368 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1369 Paragraph const & oldpar = pars_[old.par()];
1371 // We allow all kinds of "mumbo-jumbo" when freespacing.
1372 if (oldpar.isFreeSpacing())
1375 /* Ok I'll put some comments here about what is missing.
1376 I have fixed BackSpace (and thus Delete) to not delete
1377 double-spaces automagically. I have also changed Cut,
1378 Copy and Paste to hopefully do some sensible things.
1379 There are still some small problems that can lead to
1380 double spaces stored in the document file or space at
1381 the beginning of paragraphs(). This happens if you have
1382 the cursor between to spaces and then save. Or if you
1383 cut and paste and the selection have a space at the
1384 beginning and then save right after the paste. I am
1385 sure none of these are very hard to fix, but I will
1386 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1387 that I can get some feedback. (Lgb)
1390 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1391 // delete the LineSeparator.
1394 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1395 // delete the LineSeparator.
1398 // If the chars around the old cursor were spaces, delete one of them.
1399 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1401 // Only if the cursor has really moved.
1403 && old.pos() < oldpar.size()
1404 && oldpar.isLineSeparator(old.pos())
1405 && oldpar.isLineSeparator(old.pos() - 1)) {
1406 pars_[old.par()].erase(old.pos() - 1);
1407 #ifdef WITH_WARNINGS
1408 #warning This will not work anymore when we have multiple views of the same buffer
1409 // In this case, we will have to correct also the cursors held by
1410 // other bufferviews. It will probably be easier to do that in a more
1411 // automated way in CursorSlice code. (JMarc 26/09/2001)
1413 // correct all cursor parts
1414 fixCursorAfterDelete(cur.top(), old.top());
1415 #ifdef WITH_WARNINGS
1416 #warning DEPM, look here
1418 //fixCursorAfterDelete(cur.anchor(), old.top());
1423 // only do our magic if we changed paragraph
1424 if (old.par() == cur.par())
1427 // don't delete anything if this is the ONLY paragraph!
1428 if (pars_.size() == 1)
1431 // Do not delete empty paragraphs with keepempty set.
1432 if (oldpar.allowEmpty())
1435 // record if we have deleted a paragraph
1436 // we can't possibly have deleted a paragraph before this point
1437 bool deleted = false;
1439 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1440 // ok, we will delete something
1441 CursorSlice tmpcursor;
1445 bool selection_position_was_oldcursor_position =
1446 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1448 // This is a bit of a overkill. We change the old and the cur par
1449 // at max, certainly not everything in between...
1450 recUndo(old.par(), cur.par());
1453 pars_.erase(pars_.begin() + old.par());
1455 // Update cursor par offset if necessary.
1456 // Some 'iterator registration' would be nice that takes care of
1457 // such events. Maybe even signal/slot?
1458 if (cur.par() > old.par())
1460 #ifdef WITH_WARNINGS
1461 #warning DEPM, look here
1463 // if (cur.anchor().par() > old.par())
1464 // --cur.anchor().par();
1466 if (selection_position_was_oldcursor_position) {
1467 // correct selection
1475 if (pars_[old.par()].stripLeadingSpaces())
1482 ParagraphList & LyXText::paragraphs() const
1484 return const_cast<ParagraphList &>(pars_);
1488 void LyXText::recUndo(par_type first, par_type last) const
1490 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1494 void LyXText::recUndo(par_type par) const
1496 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1500 int defaultRowHeight()
1502 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);