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 : maxwidth_(bv ? bv->workWidth() : 100),
76 background_color_(LColor::background),
81 void LyXText::init(BufferView * bv)
85 maxwidth_ = bv->workWidth();
90 pit_type const end = paragraphs().size();
91 for (pit_type pit = 0; pit != end; ++pit)
92 pars_[pit].rows().clear();
94 current_font = getFont(pars_[0], 0);
99 bool LyXText::isMainText() const
101 return &bv()->buffer()->text() == this;
105 //takes screen x,y coordinates
106 InsetBase * LyXText::checkInsetHit(int x, int y) const
108 pit_type pit = getPitNearY(y);
109 BOOST_ASSERT(pit != -1);
111 Paragraph const & par = pars_[pit];
113 lyxerr << "checkInsetHit: x: " << x << " y: " << y << endl;
114 lyxerr << " pit: " << pit << endl;
115 InsetList::const_iterator iit = par.insetlist.begin();
116 InsetList::const_iterator iend = par.insetlist.end();
117 for (; iit != iend; ++iit) {
118 InsetBase * inset = iit->inset;
120 lyxerr << "examining inset " << inset << endl;
121 if (theCoords.insets_.has(inset))
123 << " xo: " << inset->xo() << "..."
124 << inset->xo() + inset->width()
125 << " yo: " << inset->yo() - inset->ascent()
127 << inset->yo() + inset->descent() << endl;
129 lyxerr << " inset has no cached position" << endl;
131 if (inset->covers(x, y)) {
132 lyxerr << "Hit inset: " << inset << endl;
136 lyxerr << "No inset hit. " << endl;
142 // Gets the fully instantiated font at a given position in a paragraph
143 // Basically the same routine as Paragraph::getFont() in paragraph.C.
144 // The difference is that this one is used for displaying, and thus we
145 // are allowed to make cosmetic improvements. For instance make footnotes
147 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
149 BOOST_ASSERT(pos >= 0);
151 LyXLayout_ptr const & layout = par.layout();
155 BufferParams const & params = bv()->buffer()->params();
156 pos_type const body_pos = par.beginOfBody();
158 // We specialize the 95% common case:
159 if (!par.getDepth()) {
160 LyXFont f = par.getFontSettings(params, pos);
163 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
164 return f.realize(layout->reslabelfont);
166 return f.realize(layout->resfont);
169 // The uncommon case need not be optimized as much
172 layoutfont = layout->labelfont;
174 layoutfont = layout->font;
176 LyXFont font = par.getFontSettings(params, pos);
177 font.realize(layoutfont);
182 // Realize with the fonts of lesser depth.
183 font.realize(defaultfont_);
189 LyXFont LyXText::getLayoutFont(pit_type const pit) const
191 LyXLayout_ptr const & layout = pars_[pit].layout();
193 if (!pars_[pit].getDepth())
194 return layout->resfont;
196 LyXFont font = layout->font;
197 // Realize with the fonts of lesser depth.
198 //font.realize(outerFont(pit, paragraphs()));
199 font.realize(defaultfont_);
205 LyXFont LyXText::getLabelFont(Paragraph const & par) const
207 LyXLayout_ptr const & layout = par.layout();
210 return layout->reslabelfont;
212 LyXFont font = layout->labelfont;
213 // Realize with the fonts of lesser depth.
214 font.realize(defaultfont_);
220 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
223 LyXLayout_ptr const & layout = pars_[pit].layout();
225 // Get concrete layout font to reduce against
228 if (pos < pars_[pit].beginOfBody())
229 layoutfont = layout->labelfont;
231 layoutfont = layout->font;
233 // Realize against environment font information
234 if (pars_[pit].getDepth()) {
236 while (!layoutfont.resolved() &&
237 tp != pit_type(paragraphs().size()) &&
238 pars_[tp].getDepth()) {
239 tp = outerHook(tp, paragraphs());
240 if (tp != pit_type(paragraphs().size()))
241 layoutfont.realize(pars_[tp].layout()->font);
245 layoutfont.realize(defaultfont_);
247 // Now, reduce font against full layout font
248 font.reduce(layoutfont);
250 pars_[pit].setFont(pos, font);
255 // Asger is not sure we want to do this...
256 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
259 LyXLayout_ptr const & layout = par.layout();
260 pos_type const psize = par.size();
263 for (pos_type pos = 0; pos < psize; ++pos) {
264 if (pos < par.beginOfBody())
265 layoutfont = layout->labelfont;
267 layoutfont = layout->font;
269 LyXFont tmpfont = par.getFontSettings(params, pos);
270 tmpfont.reduce(layoutfont);
271 par.setFont(pos, tmpfont);
276 // return past-the-last paragraph influenced by a layout change on pit
277 pit_type LyXText::undoSpan(pit_type pit)
279 pit_type end = paragraphs().size();
280 pit_type nextpit = pit + 1;
283 //because of parindents
284 if (!pars_[pit].getDepth())
285 return boost::next(nextpit);
286 //because of depth constrains
287 for (; nextpit != end; ++pit, ++nextpit) {
288 if (!pars_[pit].getDepth())
295 pit_type LyXText::setLayout(pit_type start, pit_type end, string const & layout)
297 BOOST_ASSERT(start != end);
298 pit_type undopit = undoSpan(end - 1);
299 recUndo(start, undopit - 1);
301 BufferParams const & bufparams = bv()->buffer()->params();
302 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
304 for (pit_type pit = start; pit != end; ++pit) {
305 pars_[pit].applyLayout(lyxlayout);
306 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
307 if (lyxlayout->margintype == MARGIN_MANUAL)
308 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
315 // set layout over selection and make a total rebreak of those paragraphs
316 void LyXText::setLayout(LCursor & cur, string const & layout)
318 BOOST_ASSERT(this == cur.text());
319 // special handling of new environment insets
320 BufferView & bv = cur.bv();
321 BufferParams const & params = bv.buffer()->params();
322 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
323 if (lyxlayout->is_environment) {
324 // move everything in a new environment inset
325 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
326 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
327 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
328 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
329 InsetBase * inset = new InsetEnvironment(params, layout);
330 insertInset(cur, inset);
331 //inset->edit(cur, true);
332 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
336 pit_type start = cur.selBegin().pit();
337 pit_type end = cur.selEnd().pit() + 1;
338 pit_type endpit = setLayout(start, end, layout);
346 void getSelectionSpan(LCursor & cur, pit_type & beg, pit_type & end)
348 if (!cur.selection()) {
352 beg = cur.selBegin().pit();
353 end = cur.selEnd().pit() + 1;
358 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
359 Paragraph const & par, int max_depth)
361 if (par.layout()->labeltype == LABEL_BIBLIO)
363 int const depth = par.params().depth();
364 if (type == LyXText::INC_DEPTH && depth < max_depth)
366 if (type == LyXText::DEC_DEPTH && depth > 0)
375 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
377 BOOST_ASSERT(this == cur.text());
379 getSelectionSpan(cur, beg, end);
382 max_depth = pars_[beg - 1].getMaxDepthAfter();
384 for (pit_type pit = beg; pit != end; ++pit) {
385 if (::changeDepthAllowed(type, pars_[pit], max_depth))
387 max_depth = pars_[pit].getMaxDepthAfter();
393 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
395 BOOST_ASSERT(this == cur.text());
397 getSelectionSpan(cur, beg, end);
398 recordUndoSelection(cur);
402 max_depth = pars_[beg - 1].getMaxDepthAfter();
404 for (pit_type pit = beg; pit != end; ++pit) {
405 Paragraph & par = pars_[pit];
406 if (::changeDepthAllowed(type, par, max_depth)) {
407 int const depth = par.params().depth();
408 if (type == INC_DEPTH)
409 par.params().depth(depth + 1);
411 par.params().depth(depth - 1);
413 max_depth = par.getMaxDepthAfter();
415 // this handles the counter labels, and also fixes up
416 // depth values for follow-on (child) paragraphs
421 // set font over selection
422 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
424 BOOST_ASSERT(this == cur.text());
425 // if there is no selection just set the current_font
426 if (!cur.selection()) {
427 // Determine basis font
429 pit_type pit = cur.pit();
430 if (cur.pos() < pars_[pit].beginOfBody())
431 layoutfont = getLabelFont(pars_[pit]);
433 layoutfont = getLayoutFont(pit);
435 // Update current font
436 real_current_font.update(font,
437 cur.buffer().params().language,
440 // Reduce to implicit settings
441 current_font = real_current_font;
442 current_font.reduce(layoutfont);
443 // And resolve it completely
444 real_current_font.realize(layoutfont);
449 // Ok, we have a selection.
450 recordUndoSelection(cur);
452 DocIterator dit = cur.selectionBegin();
453 DocIterator ditend = cur.selectionEnd();
455 BufferParams const & params = cur.buffer().params();
457 // Don't use forwardChar here as ditend might have
458 // pos() == lastpos() and forwardChar would miss it.
459 for (; dit != ditend; dit.forwardPos()) {
460 if (dit.pos() != dit.lastpos()) {
461 LyXFont f = getFont(dit.paragraph(), dit.pos());
462 f.update(font, params.language, toggleall);
463 setCharFont(dit.pit(), dit.pos(), f);
469 // the cursor set functions have a special mechanism. When they
470 // realize you left an empty paragraph, they will delete it.
472 void LyXText::cursorHome(LCursor & cur)
474 BOOST_ASSERT(this == cur.text());
475 setCursor(cur, cur.pit(), cur.textRow().pos());
479 void LyXText::cursorEnd(LCursor & cur)
481 BOOST_ASSERT(this == cur.text());
482 // if not on the last row of the par, put the cursor before
484 // FIXME: does this final space exist?
485 pos_type const end = cur.textRow().endpos();
486 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
490 void LyXText::cursorTop(LCursor & cur)
492 BOOST_ASSERT(this == cur.text());
493 setCursor(cur, 0, 0);
497 void LyXText::cursorBottom(LCursor & cur)
499 BOOST_ASSERT(this == cur.text());
500 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
504 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
506 BOOST_ASSERT(this == cur.text());
507 // If the mask is completely neutral, tell user
508 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
509 // Could only happen with user style
510 cur.message(_("No font change defined. "
511 "Use Character under the Layout menu to define font change."));
515 // Try implicit word selection
516 // If there is a change in the language the implicit word selection
518 CursorSlice resetCursor = cur.top();
519 bool implicitSelection =
520 font.language() == ignore_language
521 && font.number() == LyXFont::IGNORE
522 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
525 setFont(cur, font, toggleall);
527 // Implicit selections are cleared afterwards
528 // and cursor is set to the original position.
529 if (implicitSelection) {
530 cur.clearSelection();
531 cur.top() = resetCursor;
537 string LyXText::getStringToIndex(LCursor & cur)
539 BOOST_ASSERT(this == cur.text());
540 // Try implicit word selection
541 // If there is a change in the language the implicit word selection
543 CursorSlice const reset_cursor = cur.top();
544 bool const implicitSelection =
545 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
548 if (!cur.selection())
549 cur.message(_("Nothing to index!"));
550 else if (cur.selBegin().pit() != cur.selEnd().pit())
551 cur.message(_("Cannot index more than one paragraph!"));
553 idxstring = cur.selectionAsString(false);
555 // Reset cursors to their original position.
556 cur.top() = reset_cursor;
559 // Clear the implicit selection.
560 if (implicitSelection)
561 cur.clearSelection();
567 void LyXText::setParagraph(LCursor & cur,
568 Spacing const & spacing, LyXAlignment align,
569 string const & labelwidthstring, bool noindent)
571 BOOST_ASSERT(cur.text());
572 // make sure that the depth behind the selection are restored, too
573 pit_type undopit = undoSpan(cur.selEnd().pit());
574 recUndo(cur.selBegin().pit(), undopit - 1);
576 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
578 Paragraph & par = pars_[pit];
579 ParagraphParameters & params = par.params();
580 params.spacing(spacing);
582 // does the layout allow the new alignment?
583 LyXLayout_ptr const & layout = par.layout();
585 if (align == LYX_ALIGN_LAYOUT)
586 align = layout->align;
587 if (align & layout->alignpossible) {
588 if (align == layout->align)
589 params.align(LYX_ALIGN_LAYOUT);
593 par.setLabelWidthString(labelwidthstring);
594 params.noindent(noindent);
599 string expandLabel(LyXTextClass const & textclass,
600 LyXLayout_ptr const & layout, bool appendix)
602 string fmt = appendix ?
603 layout->labelstring_appendix() : layout->labelstring();
605 // handle 'inherited level parts' in 'fmt',
606 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
607 size_t const i = fmt.find('@', 0);
608 if (i != string::npos) {
609 size_t const j = fmt.find('@', i + 1);
610 if (j != string::npos) {
611 string parent(fmt, i + 1, j - i - 1);
612 string label = expandLabel(textclass, textclass[parent], appendix);
613 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
617 return textclass.counters().counterLabel(fmt);
623 void incrementItemDepth(ParagraphList & pars, pit_type pit, pit_type first_pit)
625 int const cur_labeltype = pars[pit].layout()->labeltype;
627 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
630 int const cur_depth = pars[pit].getDepth();
632 pit_type prev_pit = pit - 1;
634 int const prev_depth = pars[prev_pit].getDepth();
635 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
636 if (prev_depth == 0 && cur_depth > 0) {
637 if (prev_labeltype == cur_labeltype) {
638 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
641 } else if (prev_depth < cur_depth) {
642 if (prev_labeltype == cur_labeltype) {
643 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
646 } else if (prev_depth == cur_depth) {
647 if (prev_labeltype == cur_labeltype) {
648 pars[pit].itemdepth = pars[prev_pit].itemdepth;
652 if (prev_pit == first_pit)
660 void resetEnumCounterIfNeeded(ParagraphList & pars, pit_type pit,
661 pit_type firstpit, Counters & counters)
666 int const cur_depth = pars[pit].getDepth();
667 pit_type prev_pit = pit - 1;
669 int const prev_depth = pars[prev_pit].getDepth();
670 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
671 if (prev_depth <= cur_depth) {
672 if (prev_labeltype != LABEL_ENUMERATE) {
673 switch (pars[pit].itemdepth) {
675 counters.reset("enumi");
677 counters.reset("enumii");
679 counters.reset("enumiii");
681 counters.reset("enumiv");
687 if (prev_pit == firstpit)
697 // set the counter of a paragraph. This includes the labels
698 void LyXText::setCounter(Buffer const & buf, pit_type pit)
700 Paragraph & par = pars_[pit];
701 BufferParams const & bufparams = buf.params();
702 LyXTextClass const & textclass = bufparams.getLyXTextClass();
703 LyXLayout_ptr const & layout = par.layout();
704 Counters & counters = textclass.counters();
710 par.params().appendix(par.params().startOfAppendix());
712 par.params().appendix(pars_[pit - 1].params().appendix());
713 if (!par.params().appendix() &&
714 par.params().startOfAppendix()) {
715 par.params().appendix(true);
716 textclass.counters().reset();
719 // Maybe we have to increment the item depth.
720 incrementItemDepth(pars_, pit, 0);
723 // erase what was there before
724 par.params().labelString(string());
726 if (layout->margintype == MARGIN_MANUAL) {
727 if (par.params().labelWidthString().empty())
728 par.setLabelWidthString(layout->labelstring());
730 par.setLabelWidthString(string());
733 // is it a layout that has an automatic label?
734 if (layout->labeltype == LABEL_COUNTER) {
735 BufferParams const & bufparams = buf.params();
736 LyXTextClass const & textclass = bufparams.getLyXTextClass();
737 counters.step(layout->counter);
738 string label = expandLabel(textclass, layout, par.params().appendix());
739 par.params().labelString(label);
740 } else if (layout->labeltype == LABEL_ITEMIZE) {
741 // At some point of time we should do something more
742 // clever here, like:
743 // par.params().labelString(
744 // bufparams.user_defined_bullet(par.itemdepth).getText());
745 // for now, use a simple hardcoded label
747 switch (par.itemdepth) {
762 par.params().labelString(itemlabel);
763 } else if (layout->labeltype == LABEL_ENUMERATE) {
764 // Maybe we have to reset the enumeration counter.
765 resetEnumCounterIfNeeded(pars_, pit, 0, counters);
768 // Yes I know this is a really, really! bad solution
770 string enumcounter = "enum";
772 switch (par.itemdepth) {
784 // not a valid enumdepth...
788 counters.step(enumcounter);
790 par.params().labelString(counters.enumLabel(enumcounter));
791 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
792 counters.step("bibitem");
793 int number = counters.value("bibitem");
795 par.bibitem()->setCounter(number);
796 par.params().labelString(layout->labelstring());
798 // In biblio should't be following counters but...
800 string s = buf.B_(layout->labelstring());
803 if (layout->labeltype == LABEL_SENSITIVE) {
804 pit_type end = paragraphs().size();
805 pit_type tmppit = pit;
808 while (tmppit != end) {
809 in = pars_[tmppit].inInset();
810 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
811 in->lyxCode() == InsetBase::WRAP_CODE) {
816 #warning replace this code by something that works
817 // This code does not work because we have currently no way to move up
818 // in the hierarchy of insets (JMarc 16/08/2004)
821 /* I think this code is supposed to be useful when one has a caption
822 * in a minipage in a figure inset. We need to go up to be able to see
823 * that the caption should use "Figure" as label
826 Paragraph const * owner = &ownerPar(buf, in);
828 for ( ; tmppit != end; ++tmppit)
829 if (&pars_[tmppit] == owner)
840 if (in->lyxCode() == InsetBase::FLOAT_CODE)
841 type = static_cast<InsetFloat*>(in)->params().type;
842 else if (in->lyxCode() == InsetBase::WRAP_CODE)
843 type = static_cast<InsetWrap*>(in)->params().type;
847 Floating const & fl = textclass.floats().getType(type);
849 counters.step(fl.type());
851 // Doesn't work... yet.
852 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
854 // par->SetLayout(0);
855 // s = layout->labelstring;
856 s = _("Senseless: ");
859 par.params().labelString(s);
865 // Updates all counters.
866 void LyXText::updateCounters()
869 bv()->buffer()->params().getLyXTextClass().counters().reset();
871 bool update_pos = false;
873 pit_type end = paragraphs().size();
874 for (pit_type pit = 0; pit != end; ++pit) {
875 string const oldLabel = pars_[pit].params().labelString();
878 maxdepth = pars_[pit - 1].getMaxDepthAfter();
880 if (pars_[pit].params().depth() > maxdepth)
881 pars_[pit].params().depth(maxdepth);
883 // setCounter can potentially change the labelString.
884 setCounter(*bv()->buffer(), pit);
885 string const & newLabel = pars_[pit].params().labelString();
886 if (oldLabel != newLabel) {
887 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
888 // << newLabel << endl;
895 // this really should just insert the inset and not move the cursor.
896 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
898 BOOST_ASSERT(this == cur.text());
900 cur.paragraph().insertInset(cur.pos(), inset);
904 // needed to insert the selection
905 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
907 pit_type pit = cur.pit();
908 pos_type pos = cur.pos();
911 // only to be sure, should not be neccessary
912 cur.clearSelection();
913 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
916 setCursor(cur, cur.pit(), pos);
921 // turn double CR to single CR, others are converted into one
922 // blank. Then insertStringAsLines is called
923 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
925 string linestr = str;
926 bool newline_inserted = false;
928 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
929 if (linestr[i] == '\n') {
930 if (newline_inserted) {
931 // we know that \r will be ignored by
932 // insertStringAsLines. Of course, it is a dirty
933 // trick, but it works...
934 linestr[i - 1] = '\r';
938 newline_inserted = true;
940 } else if (IsPrintable(linestr[i])) {
941 newline_inserted = false;
944 insertStringAsLines(cur, linestr);
948 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
949 bool setfont, bool boundary)
952 setCursorIntern(cur, par, pos, setfont, boundary);
953 return deleteEmptyParagraphMechanism(cur, old);
957 void LyXText::setCursor(CursorSlice & cur, pit_type par,
958 pos_type pos, bool boundary)
960 BOOST_ASSERT(par != int(paragraphs().size()));
963 cur.boundary() = boundary;
965 // now some strict checking
966 Paragraph & para = getPar(par);
968 // None of these should happen, but we're scaredy-cats
970 lyxerr << "dont like -1" << endl;
974 if (pos > para.size()) {
975 lyxerr << "dont like 1, pos: " << pos
976 << " size: " << para.size()
977 << " par: " << par << endl;
983 void LyXText::setCursorIntern(LCursor & cur,
984 pit_type par, pos_type pos, bool setfont, bool boundary)
986 setCursor(cur.top(), par, pos, boundary);
993 void LyXText::setCurrentFont(LCursor & cur)
995 BOOST_ASSERT(this == cur.text());
996 pos_type pos = cur.pos();
997 Paragraph & par = cur.paragraph();
999 if (cur.boundary() && pos > 0)
1003 if (pos == cur.lastpos())
1005 else // potentional bug... BUG (Lgb)
1006 if (par.isSeparator(pos)) {
1007 if (pos > cur.textRow().pos() &&
1008 bidi.level(pos) % 2 ==
1009 bidi.level(pos - 1) % 2)
1011 else if (pos + 1 < cur.lastpos())
1016 BufferParams const & bufparams = cur.buffer().params();
1017 current_font = par.getFontSettings(bufparams, pos);
1018 real_current_font = getFont(par, pos);
1020 if (cur.pos() == cur.lastpos()
1021 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1022 && !cur.boundary()) {
1023 Language const * lang = par.getParLanguage(bufparams);
1024 current_font.setLanguage(lang);
1025 current_font.setNumber(LyXFont::OFF);
1026 real_current_font.setLanguage(lang);
1027 real_current_font.setNumber(LyXFont::OFF);
1032 // x is an absolute screen coord
1033 // returns the column near the specified x-coordinate of the row
1034 // x is set to the real beginning of this column
1035 pos_type LyXText::getColumnNearX(pit_type const pit,
1036 Row const & row, int & x, bool & boundary) const
1038 int const xo = theCoords.get(this, pit).x_;
1040 RowMetrics const r = computeRowMetrics(pit, row);
1041 Paragraph const & par = pars_[pit];
1043 pos_type vc = row.pos();
1044 pos_type end = row.endpos();
1046 LyXLayout_ptr const & layout = par.layout();
1048 bool left_side = false;
1050 pos_type body_pos = par.beginOfBody();
1053 double last_tmpx = tmpx;
1056 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1059 // check for empty row
1065 while (vc < end && tmpx <= x) {
1066 c = bidi.vis2log(vc);
1068 if (body_pos > 0 && c == body_pos - 1) {
1069 tmpx += r.label_hfill +
1070 font_metrics::width(layout->labelsep, getLabelFont(par));
1071 if (par.isLineSeparator(body_pos - 1))
1072 tmpx -= singleWidth(par, body_pos - 1);
1075 if (hfillExpansion(par, row, c)) {
1076 tmpx += singleWidth(par, c);
1080 tmpx += r.label_hfill;
1081 } else if (par.isSeparator(c)) {
1082 tmpx += singleWidth(par, c);
1084 tmpx += r.separator;
1086 tmpx += singleWidth(par, c);
1091 if ((tmpx + last_tmpx) / 2 > x) {
1096 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1099 // This (rtl_support test) is not needed, but gives
1100 // some speedup if rtl_support == false
1101 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1103 // If lastrow is false, we don't need to compute
1104 // the value of rtl.
1105 bool const rtl = lastrow ? isRTL(par) : false;
1107 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1108 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1110 else if (vc == row.pos()) {
1111 c = bidi.vis2log(vc);
1112 if (bidi.level(c) % 2 == 1)
1115 c = bidi.vis2log(vc - 1);
1116 bool const rtl = (bidi.level(c) % 2 == 1);
1117 if (left_side == rtl) {
1119 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1123 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1124 if (bidi.level(end -1) % 2 == 0)
1125 tmpx -= singleWidth(par, end - 1);
1127 tmpx += singleWidth(par, end - 1);
1132 return c - row.pos();
1136 // y is screen coordinate
1137 pit_type LyXText::getPitNearY(int y) const
1139 BOOST_ASSERT(!paragraphs().empty());
1140 BOOST_ASSERT(theCoords.pars_.find(this) != theCoords.pars_.end());
1141 CoordCache::InnerParPosCache const & cc = theCoords.pars_[this];
1142 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
1143 << cc.size() << endl;
1145 // look for highest numbered paragraph with y coordinate less than given y
1148 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
1149 CoordCache::InnerParPosCache::const_iterator et = cc.end();
1150 for (; it != et; ++it) {
1151 lyxerr << " examining: pit: " << it->first << " y: "
1152 << it->second.y_ << endl;
1153 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
1159 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
1164 Row const & LyXText::getRowNearY(int y, pit_type pit) const
1166 Paragraph const & par = pars_[pit];
1167 int yy = theCoords.get(this, pit).y_ - par.ascent();
1168 BOOST_ASSERT(!par.rows().empty());
1169 RowList::const_iterator rit = par.rows().begin();
1170 RowList::const_iterator const rlast = boost::prior(par.rows().end());
1171 for (; rit != rlast; yy += rit->height(), ++rit)
1172 if (yy + rit->height() > y)
1177 // x,y are absolute screen coordinates
1178 // sets cursor recursively descending into nested editable insets
1179 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1181 pit_type pit = getPitNearY(y);
1182 BOOST_ASSERT(pit != -1);
1183 Row const & row = getRowNearY(y, pit);
1186 int xx = x; // is modified by getColumnNearX
1187 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1190 cur.boundary() = bound;
1193 // try to descend into nested insets
1194 InsetBase * inset = checkInsetHit(x, y);
1195 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1197 //either we deconst editXY or better we move current_font
1198 //and real_current_font to LCursor
1199 const_cast<LyXText *>(this)->setCurrentFont(cur);
1203 // This should be just before or just behind the
1204 // cursor position set above.
1205 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1206 || inset == pars_[pit].getInset(pos));
1207 // Make sure the cursor points to the position before
1209 if (inset == pars_[pit].getInset(pos - 1))
1211 inset = inset->editXY(cur, x, y);
1212 if (cur.top().text() == this)
1213 const_cast<LyXText *>(this)->setCurrentFont(cur);
1218 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1220 if (cur.selection())
1222 if (cur.pos() == cur.lastpos())
1224 InsetBase * inset = cur.nextInset();
1225 if (!isHighlyEditableInset(inset))
1227 inset->edit(cur, front);
1232 void LyXText::cursorLeft(LCursor & cur)
1234 if (cur.pos() != 0) {
1235 bool boundary = cur.boundary();
1236 setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1237 if (!checkAndActivateInset(cur, false)) {
1238 if (false && !boundary &&
1239 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1240 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1245 if (cur.pit() != 0) {
1246 // steps into the paragraph above
1247 setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1252 void LyXText::cursorRight(LCursor & cur)
1254 if (false && cur.boundary()) {
1255 setCursor(cur, cur.pit(), cur.pos(), true, false);
1259 if (cur.pos() != cur.lastpos()) {
1260 if (!checkAndActivateInset(cur, true)) {
1261 setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1262 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1264 setCursor(cur, cur.pit(), cur.pos(), true, true);
1269 if (cur.pit() != cur.lastpit())
1270 setCursor(cur, cur.pit() + 1, 0);
1274 void LyXText::cursorUp(LCursor & cur)
1276 Paragraph const & par = cur.paragraph();
1277 int const row = par.pos2row(cur.pos());
1278 int const x = cur.targetX();
1280 if (!cur.selection()) {
1281 int const y = bv_funcs::getPos(cur).y_;
1282 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1287 setCursor(cur, cur.pit(), x2pos(cur.pit(), row - 1, x));
1288 } else if (cur.pit() > 0) {
1290 setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1298 void LyXText::cursorDown(LCursor & cur)
1302 Paragraph const & par = cur.paragraph();
1303 int const row = par.pos2row(cur.pos());
1304 int const x = cur.targetX();
1306 if (!cur.selection()) {
1307 int const y = bv_funcs::getPos(cur).y_;
1308 editXY(cur, x, y + par.rows()[row].descent() + 1);
1312 if (row + 1 < int(par.rows().size())) {
1313 setCursor(cur, cur.pit(), x2pos(cur.pit(), row + 1, x));
1314 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1316 setCursor(cur, cur.pit(), x2pos(cur.pit(), 0, x));
1323 void LyXText::cursorUpParagraph(LCursor & cur)
1326 setCursor(cur, cur.pit(), 0);
1327 else if (cur.pit() != 0)
1328 setCursor(cur, cur.pit() - 1, 0);
1332 void LyXText::cursorDownParagraph(LCursor & cur)
1334 if (cur.pit() != cur.lastpit())
1335 setCursor(cur, cur.pit() + 1, 0);
1337 setCursor(cur, cur.pit(), 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.pit() != where.pit())
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.pit()];
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.pit() != cur.pit() || 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.pit()].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.pit() == cur.pit())
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().pit() == old.pit() && 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.pit(), cur.pit());
1453 pars_.erase(pars_.begin() + old.pit());
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.pit() > old.pit())
1460 #ifdef WITH_WARNINGS
1461 #warning DEPM, look here
1463 // if (cur.anchor().pit() > old.pit())
1464 // --cur.anchor().pit();
1466 if (selection_position_was_oldcursor_position) {
1467 // correct selection
1475 if (pars_[old.pit()].stripLeadingSpaces())
1482 ParagraphList & LyXText::paragraphs() const
1484 return const_cast<ParagraphList &>(pars_);
1488 void LyXText::recUndo(pit_type first, pit_type last) const
1490 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1494 void LyXText::recUndo(pit_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);