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 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
347 Paragraph const & par, int max_depth)
349 if (par.layout()->labeltype == LABEL_BIBLIO)
351 int const depth = par.params().depth();
352 if (type == LyXText::INC_DEPTH && depth < max_depth)
354 if (type == LyXText::DEC_DEPTH && depth > 0)
363 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
365 BOOST_ASSERT(this == cur.text());
366 pit_type const beg = cur.selBegin().pit();
367 pit_type const end = cur.selEnd().pit() + 1;
368 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
370 for (pit_type pit = beg; pit != end; ++pit) {
371 if (::changeDepthAllowed(type, pars_[pit], max_depth))
373 max_depth = pars_[pit].getMaxDepthAfter();
379 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
381 BOOST_ASSERT(this == cur.text());
382 pit_type const beg = cur.selBegin().pit();
383 pit_type const end = cur.selEnd().pit() + 1;
384 recordUndoSelection(cur);
385 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
387 for (pit_type pit = beg; pit != end; ++pit) {
388 Paragraph & par = pars_[pit];
389 if (::changeDepthAllowed(type, par, max_depth)) {
390 int const depth = par.params().depth();
391 if (type == INC_DEPTH)
392 par.params().depth(depth + 1);
394 par.params().depth(depth - 1);
396 max_depth = par.getMaxDepthAfter();
398 // this handles the counter labels, and also fixes up
399 // depth values for follow-on (child) paragraphs
404 // set font over selection
405 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
407 BOOST_ASSERT(this == cur.text());
408 // if there is no selection just set the current_font
409 if (!cur.selection()) {
410 // Determine basis font
412 pit_type pit = cur.pit();
413 if (cur.pos() < pars_[pit].beginOfBody())
414 layoutfont = getLabelFont(pars_[pit]);
416 layoutfont = getLayoutFont(pit);
418 // Update current font
419 real_current_font.update(font,
420 cur.buffer().params().language,
423 // Reduce to implicit settings
424 current_font = real_current_font;
425 current_font.reduce(layoutfont);
426 // And resolve it completely
427 real_current_font.realize(layoutfont);
432 // Ok, we have a selection.
433 recordUndoSelection(cur);
435 DocIterator dit = cur.selectionBegin();
436 DocIterator ditend = cur.selectionEnd();
438 BufferParams const & params = cur.buffer().params();
440 // Don't use forwardChar here as ditend might have
441 // pos() == lastpos() and forwardChar would miss it.
442 for (; dit != ditend; dit.forwardPos()) {
443 if (dit.pos() != dit.lastpos()) {
444 LyXFont f = getFont(dit.paragraph(), dit.pos());
445 f.update(font, params.language, toggleall);
446 setCharFont(dit.pit(), dit.pos(), f);
452 // the cursor set functions have a special mechanism. When they
453 // realize you left an empty paragraph, they will delete it.
455 void LyXText::cursorHome(LCursor & cur)
457 BOOST_ASSERT(this == cur.text());
458 setCursor(cur, cur.pit(), cur.textRow().pos());
462 void LyXText::cursorEnd(LCursor & cur)
464 BOOST_ASSERT(this == cur.text());
465 // if not on the last row of the par, put the cursor before
467 // FIXME: does this final space exist?
468 pos_type const end = cur.textRow().endpos();
469 setCursor(cur, cur.pit(), end == cur.lastpos() ? end : end - 1);
473 void LyXText::cursorTop(LCursor & cur)
475 BOOST_ASSERT(this == cur.text());
476 setCursor(cur, 0, 0);
480 void LyXText::cursorBottom(LCursor & cur)
482 BOOST_ASSERT(this == cur.text());
483 setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
487 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
489 BOOST_ASSERT(this == cur.text());
490 // If the mask is completely neutral, tell user
491 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
492 // Could only happen with user style
493 cur.message(_("No font change defined. "
494 "Use Character under the Layout menu to define font change."));
498 // Try implicit word selection
499 // If there is a change in the language the implicit word selection
501 CursorSlice resetCursor = cur.top();
502 bool implicitSelection =
503 font.language() == ignore_language
504 && font.number() == LyXFont::IGNORE
505 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
508 setFont(cur, font, toggleall);
510 // Implicit selections are cleared afterwards
511 // and cursor is set to the original position.
512 if (implicitSelection) {
513 cur.clearSelection();
514 cur.top() = resetCursor;
520 string LyXText::getStringToIndex(LCursor & cur)
522 BOOST_ASSERT(this == cur.text());
523 // Try implicit word selection
524 // If there is a change in the language the implicit word selection
526 CursorSlice const reset_cursor = cur.top();
527 bool const implicitSelection =
528 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
531 if (!cur.selection())
532 cur.message(_("Nothing to index!"));
533 else if (cur.selBegin().pit() != cur.selEnd().pit())
534 cur.message(_("Cannot index more than one paragraph!"));
536 idxstring = cur.selectionAsString(false);
538 // Reset cursors to their original position.
539 cur.top() = reset_cursor;
542 // Clear the implicit selection.
543 if (implicitSelection)
544 cur.clearSelection();
550 void LyXText::setParagraph(LCursor & cur,
551 Spacing const & spacing, LyXAlignment align,
552 string const & labelwidthstring, bool noindent)
554 BOOST_ASSERT(cur.text());
555 // make sure that the depth behind the selection are restored, too
556 pit_type undopit = undoSpan(cur.selEnd().pit());
557 recUndo(cur.selBegin().pit(), undopit - 1);
559 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
561 Paragraph & par = pars_[pit];
562 ParagraphParameters & params = par.params();
563 params.spacing(spacing);
565 // does the layout allow the new alignment?
566 LyXLayout_ptr const & layout = par.layout();
568 if (align == LYX_ALIGN_LAYOUT)
569 align = layout->align;
570 if (align & layout->alignpossible) {
571 if (align == layout->align)
572 params.align(LYX_ALIGN_LAYOUT);
576 par.setLabelWidthString(labelwidthstring);
577 params.noindent(noindent);
582 string expandLabel(LyXTextClass const & textclass,
583 LyXLayout_ptr const & layout, bool appendix)
585 string fmt = appendix ?
586 layout->labelstring_appendix() : layout->labelstring();
588 // handle 'inherited level parts' in 'fmt',
589 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
590 size_t const i = fmt.find('@', 0);
591 if (i != string::npos) {
592 size_t const j = fmt.find('@', i + 1);
593 if (j != string::npos) {
594 string parent(fmt, i + 1, j - i - 1);
595 string label = expandLabel(textclass, textclass[parent], appendix);
596 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
600 return textclass.counters().counterLabel(fmt);
606 void incrementItemDepth(ParagraphList & pars, pit_type pit, pit_type first_pit)
608 int const cur_labeltype = pars[pit].layout()->labeltype;
610 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
613 int const cur_depth = pars[pit].getDepth();
615 pit_type prev_pit = pit - 1;
617 int const prev_depth = pars[prev_pit].getDepth();
618 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
619 if (prev_depth == 0 && cur_depth > 0) {
620 if (prev_labeltype == cur_labeltype) {
621 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
624 } else if (prev_depth < cur_depth) {
625 if (prev_labeltype == cur_labeltype) {
626 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
629 } else if (prev_depth == cur_depth) {
630 if (prev_labeltype == cur_labeltype) {
631 pars[pit].itemdepth = pars[prev_pit].itemdepth;
635 if (prev_pit == first_pit)
643 void resetEnumCounterIfNeeded(ParagraphList & pars, pit_type pit,
644 pit_type firstpit, Counters & counters)
649 int const cur_depth = pars[pit].getDepth();
650 pit_type prev_pit = pit - 1;
652 int const prev_depth = pars[prev_pit].getDepth();
653 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
654 if (prev_depth <= cur_depth) {
655 if (prev_labeltype != LABEL_ENUMERATE) {
656 switch (pars[pit].itemdepth) {
658 counters.reset("enumi");
660 counters.reset("enumii");
662 counters.reset("enumiii");
664 counters.reset("enumiv");
670 if (prev_pit == firstpit)
680 // set the counter of a paragraph. This includes the labels
681 void LyXText::setCounter(Buffer const & buf, pit_type pit)
683 Paragraph & par = pars_[pit];
684 BufferParams const & bufparams = buf.params();
685 LyXTextClass const & textclass = bufparams.getLyXTextClass();
686 LyXLayout_ptr const & layout = par.layout();
687 Counters & counters = textclass.counters();
693 par.params().appendix(par.params().startOfAppendix());
695 par.params().appendix(pars_[pit - 1].params().appendix());
696 if (!par.params().appendix() &&
697 par.params().startOfAppendix()) {
698 par.params().appendix(true);
699 textclass.counters().reset();
702 // Maybe we have to increment the item depth.
703 incrementItemDepth(pars_, pit, 0);
706 // erase what was there before
707 par.params().labelString(string());
709 if (layout->margintype == MARGIN_MANUAL) {
710 if (par.params().labelWidthString().empty())
711 par.setLabelWidthString(layout->labelstring());
713 par.setLabelWidthString(string());
716 // is it a layout that has an automatic label?
717 if (layout->labeltype == LABEL_COUNTER) {
718 BufferParams const & bufparams = buf.params();
719 LyXTextClass const & textclass = bufparams.getLyXTextClass();
720 counters.step(layout->counter);
721 string label = expandLabel(textclass, layout, par.params().appendix());
722 par.params().labelString(label);
723 } else if (layout->labeltype == LABEL_ITEMIZE) {
724 // At some point of time we should do something more
725 // clever here, like:
726 // par.params().labelString(
727 // bufparams.user_defined_bullet(par.itemdepth).getText());
728 // for now, use a simple hardcoded label
730 switch (par.itemdepth) {
745 par.params().labelString(itemlabel);
746 } else if (layout->labeltype == LABEL_ENUMERATE) {
747 // Maybe we have to reset the enumeration counter.
748 resetEnumCounterIfNeeded(pars_, pit, 0, counters);
751 // Yes I know this is a really, really! bad solution
753 string enumcounter = "enum";
755 switch (par.itemdepth) {
767 // not a valid enumdepth...
771 counters.step(enumcounter);
773 par.params().labelString(counters.enumLabel(enumcounter));
774 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
775 counters.step("bibitem");
776 int number = counters.value("bibitem");
778 par.bibitem()->setCounter(number);
779 par.params().labelString(layout->labelstring());
781 // In biblio should't be following counters but...
783 string s = buf.B_(layout->labelstring());
786 if (layout->labeltype == LABEL_SENSITIVE) {
787 pit_type end = paragraphs().size();
788 pit_type tmppit = pit;
791 while (tmppit != end) {
792 in = pars_[tmppit].inInset();
793 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
794 in->lyxCode() == InsetBase::WRAP_CODE) {
799 #warning replace this code by something that works
800 // This code does not work because we have currently no way to move up
801 // in the hierarchy of insets (JMarc 16/08/2004)
804 /* I think this code is supposed to be useful when one has a caption
805 * in a minipage in a figure inset. We need to go up to be able to see
806 * that the caption should use "Figure" as label
809 Paragraph const * owner = &ownerPar(buf, in);
811 for ( ; tmppit != end; ++tmppit)
812 if (&pars_[tmppit] == owner)
823 if (in->lyxCode() == InsetBase::FLOAT_CODE)
824 type = static_cast<InsetFloat*>(in)->params().type;
825 else if (in->lyxCode() == InsetBase::WRAP_CODE)
826 type = static_cast<InsetWrap*>(in)->params().type;
830 Floating const & fl = textclass.floats().getType(type);
832 counters.step(fl.type());
834 // Doesn't work... yet.
835 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
837 // par->SetLayout(0);
838 // s = layout->labelstring;
839 s = _("Senseless: ");
842 par.params().labelString(s);
848 // Updates all counters.
849 void LyXText::updateCounters()
852 bv()->buffer()->params().getLyXTextClass().counters().reset();
854 bool update_pos = false;
856 pit_type end = paragraphs().size();
857 for (pit_type pit = 0; pit != end; ++pit) {
858 string const oldLabel = pars_[pit].params().labelString();
861 maxdepth = pars_[pit - 1].getMaxDepthAfter();
863 if (pars_[pit].params().depth() > maxdepth)
864 pars_[pit].params().depth(maxdepth);
866 // setCounter can potentially change the labelString.
867 setCounter(*bv()->buffer(), pit);
868 string const & newLabel = pars_[pit].params().labelString();
869 if (oldLabel != newLabel) {
870 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
871 // << newLabel << endl;
878 // this really should just insert the inset and not move the cursor.
879 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
881 BOOST_ASSERT(this == cur.text());
883 cur.paragraph().insertInset(cur.pos(), inset);
887 // needed to insert the selection
888 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
890 pit_type pit = cur.pit();
891 pos_type pos = cur.pos();
894 // only to be sure, should not be neccessary
895 cur.clearSelection();
896 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
899 setCursor(cur, cur.pit(), pos);
904 // turn double CR to single CR, others are converted into one
905 // blank. Then insertStringAsLines is called
906 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
908 string linestr = str;
909 bool newline_inserted = false;
911 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
912 if (linestr[i] == '\n') {
913 if (newline_inserted) {
914 // we know that \r will be ignored by
915 // insertStringAsLines. Of course, it is a dirty
916 // trick, but it works...
917 linestr[i - 1] = '\r';
921 newline_inserted = true;
923 } else if (IsPrintable(linestr[i])) {
924 newline_inserted = false;
927 insertStringAsLines(cur, linestr);
931 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
932 bool setfont, bool boundary)
935 setCursorIntern(cur, par, pos, setfont, boundary);
936 return deleteEmptyParagraphMechanism(cur, old);
940 void LyXText::setCursor(CursorSlice & cur, pit_type par,
941 pos_type pos, bool boundary)
943 BOOST_ASSERT(par != int(paragraphs().size()));
946 cur.boundary() = boundary;
948 // now some strict checking
949 Paragraph & para = getPar(par);
951 // None of these should happen, but we're scaredy-cats
953 lyxerr << "dont like -1" << endl;
957 if (pos > para.size()) {
958 lyxerr << "dont like 1, pos: " << pos
959 << " size: " << para.size()
960 << " par: " << par << endl;
966 void LyXText::setCursorIntern(LCursor & cur,
967 pit_type par, pos_type pos, bool setfont, bool boundary)
969 setCursor(cur.top(), par, pos, boundary);
976 void LyXText::setCurrentFont(LCursor & cur)
978 BOOST_ASSERT(this == cur.text());
979 pos_type pos = cur.pos();
980 Paragraph & par = cur.paragraph();
982 if (cur.boundary() && pos > 0)
986 if (pos == cur.lastpos())
988 else // potentional bug... BUG (Lgb)
989 if (par.isSeparator(pos)) {
990 if (pos > cur.textRow().pos() &&
991 bidi.level(pos) % 2 ==
992 bidi.level(pos - 1) % 2)
994 else if (pos + 1 < cur.lastpos())
999 BufferParams const & bufparams = cur.buffer().params();
1000 current_font = par.getFontSettings(bufparams, pos);
1001 real_current_font = getFont(par, pos);
1003 if (cur.pos() == cur.lastpos()
1004 && bidi.isBoundary(cur.buffer(), par, cur.pos())
1005 && !cur.boundary()) {
1006 Language const * lang = par.getParLanguage(bufparams);
1007 current_font.setLanguage(lang);
1008 current_font.setNumber(LyXFont::OFF);
1009 real_current_font.setLanguage(lang);
1010 real_current_font.setNumber(LyXFont::OFF);
1015 // x is an absolute screen coord
1016 // returns the column near the specified x-coordinate of the row
1017 // x is set to the real beginning of this column
1018 pos_type LyXText::getColumnNearX(pit_type const pit,
1019 Row const & row, int & x, bool & boundary) const
1021 int const xo = theCoords.get(this, pit).x_;
1023 RowMetrics const r = computeRowMetrics(pit, row);
1024 Paragraph const & par = pars_[pit];
1026 pos_type vc = row.pos();
1027 pos_type end = row.endpos();
1029 LyXLayout_ptr const & layout = par.layout();
1031 bool left_side = false;
1033 pos_type body_pos = par.beginOfBody();
1036 double last_tmpx = tmpx;
1039 (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1042 // check for empty row
1048 while (vc < end && tmpx <= x) {
1049 c = bidi.vis2log(vc);
1051 if (body_pos > 0 && c == body_pos - 1) {
1052 tmpx += r.label_hfill +
1053 font_metrics::width(layout->labelsep, getLabelFont(par));
1054 if (par.isLineSeparator(body_pos - 1))
1055 tmpx -= singleWidth(par, body_pos - 1);
1058 if (hfillExpansion(par, row, c)) {
1059 tmpx += singleWidth(par, c);
1063 tmpx += r.label_hfill;
1064 } else if (par.isSeparator(c)) {
1065 tmpx += singleWidth(par, c);
1067 tmpx += r.separator;
1069 tmpx += singleWidth(par, c);
1074 if ((tmpx + last_tmpx) / 2 > x) {
1079 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1082 // This (rtl_support test) is not needed, but gives
1083 // some speedup if rtl_support == false
1084 bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1086 // If lastrow is false, we don't need to compute
1087 // the value of rtl.
1088 bool const rtl = lastrow ? isRTL(par) : false;
1090 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1091 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1093 else if (vc == row.pos()) {
1094 c = bidi.vis2log(vc);
1095 if (bidi.level(c) % 2 == 1)
1098 c = bidi.vis2log(vc - 1);
1099 bool const rtl = (bidi.level(c) % 2 == 1);
1100 if (left_side == rtl) {
1102 boundary = bidi.isBoundary(*bv()->buffer(), par, c);
1106 if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1107 if (bidi.level(end -1) % 2 == 0)
1108 tmpx -= singleWidth(par, end - 1);
1110 tmpx += singleWidth(par, end - 1);
1115 return c - row.pos();
1119 // y is screen coordinate
1120 pit_type LyXText::getPitNearY(int y) const
1122 BOOST_ASSERT(!paragraphs().empty());
1123 BOOST_ASSERT(theCoords.pars_.find(this) != theCoords.pars_.end());
1124 CoordCache::InnerParPosCache const & cc = theCoords.pars_[this];
1125 lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
1126 << cc.size() << endl;
1128 // look for highest numbered paragraph with y coordinate less than given y
1131 CoordCache::InnerParPosCache::const_iterator it = cc.begin();
1132 CoordCache::InnerParPosCache::const_iterator et = cc.end();
1133 for (; it != et; ++it) {
1134 lyxerr << " examining: pit: " << it->first << " y: "
1135 << it->second.y_ << endl;
1136 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
1142 lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
1147 Row const & LyXText::getRowNearY(int y, pit_type pit) const
1149 Paragraph const & par = pars_[pit];
1150 int yy = theCoords.get(this, pit).y_ - par.ascent();
1151 BOOST_ASSERT(!par.rows().empty());
1152 RowList::const_iterator rit = par.rows().begin();
1153 RowList::const_iterator const rlast = boost::prior(par.rows().end());
1154 for (; rit != rlast; yy += rit->height(), ++rit)
1155 if (yy + rit->height() > y)
1160 // x,y are absolute screen coordinates
1161 // sets cursor recursively descending into nested editable insets
1162 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
1164 pit_type pit = getPitNearY(y);
1165 BOOST_ASSERT(pit != -1);
1166 Row const & row = getRowNearY(y, pit);
1169 int xx = x; // is modified by getColumnNearX
1170 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1173 cur.boundary() = bound;
1176 // try to descend into nested insets
1177 InsetBase * inset = checkInsetHit(x, y);
1178 lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1180 //either we deconst editXY or better we move current_font
1181 //and real_current_font to LCursor
1182 const_cast<LyXText *>(this)->setCurrentFont(cur);
1186 // This should be just before or just behind the
1187 // cursor position set above.
1188 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1189 || inset == pars_[pit].getInset(pos));
1190 // Make sure the cursor points to the position before
1192 if (inset == pars_[pit].getInset(pos - 1))
1194 inset = inset->editXY(cur, x, y);
1195 if (cur.top().text() == this)
1196 const_cast<LyXText *>(this)->setCurrentFont(cur);
1201 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1203 if (cur.selection())
1205 if (cur.pos() == cur.lastpos())
1207 InsetBase * inset = cur.nextInset();
1208 if (!isHighlyEditableInset(inset))
1210 inset->edit(cur, front);
1215 void LyXText::cursorLeft(LCursor & cur)
1217 if (cur.pos() != 0) {
1218 bool boundary = cur.boundary();
1219 setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1220 if (!checkAndActivateInset(cur, false)) {
1221 if (false && !boundary &&
1222 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1223 setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1228 if (cur.pit() != 0) {
1229 // steps into the paragraph above
1230 setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1235 void LyXText::cursorRight(LCursor & cur)
1237 if (false && cur.boundary()) {
1238 setCursor(cur, cur.pit(), cur.pos(), true, false);
1242 if (cur.pos() != cur.lastpos()) {
1243 if (!checkAndActivateInset(cur, true)) {
1244 setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1245 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1247 setCursor(cur, cur.pit(), cur.pos(), true, true);
1252 if (cur.pit() != cur.lastpit())
1253 setCursor(cur, cur.pit() + 1, 0);
1257 void LyXText::cursorUp(LCursor & cur)
1259 Paragraph const & par = cur.paragraph();
1260 int const row = par.pos2row(cur.pos());
1261 int const x = cur.targetX();
1263 if (!cur.selection()) {
1264 int const y = bv_funcs::getPos(cur).y_;
1265 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1270 setCursor(cur, cur.pit(), x2pos(cur.pit(), row - 1, x));
1271 } else if (cur.pit() > 0) {
1273 setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1281 void LyXText::cursorDown(LCursor & cur)
1285 Paragraph const & par = cur.paragraph();
1286 int const row = par.pos2row(cur.pos());
1287 int const x = cur.targetX();
1289 if (!cur.selection()) {
1290 int const y = bv_funcs::getPos(cur).y_;
1291 editXY(cur, x, y + par.rows()[row].descent() + 1);
1295 if (row + 1 < int(par.rows().size())) {
1296 setCursor(cur, cur.pit(), x2pos(cur.pit(), row + 1, x));
1297 } else if (cur.pit() + 1 < int(paragraphs().size())) {
1299 setCursor(cur, cur.pit(), x2pos(cur.pit(), 0, x));
1306 void LyXText::cursorUpParagraph(LCursor & cur)
1309 setCursor(cur, cur.pit(), 0);
1310 else if (cur.pit() != 0)
1311 setCursor(cur, cur.pit() - 1, 0);
1315 void LyXText::cursorDownParagraph(LCursor & cur)
1317 if (cur.pit() != cur.lastpit())
1318 setCursor(cur, cur.pit() + 1, 0);
1320 setCursor(cur, cur.pit(), cur.lastpos());
1324 // fix the cursor `cur' after a characters has been deleted at `where'
1325 // position. Called by deleteEmptyParagraphMechanism
1326 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1328 // do notheing if cursor is not in the paragraph where the
1329 // deletion occured,
1330 if (cur.pit() != where.pit())
1333 // if cursor position is after the deletion place update it
1334 if (cur.pos() > where.pos())
1337 // check also if we don't want to set the cursor on a spot behind the
1338 // pagragraph because we erased the last character.
1339 if (cur.pos() > cur.lastpos())
1340 cur.pos() = cur.lastpos();
1344 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1346 BOOST_ASSERT(cur.size() == old.size());
1347 // Would be wrong to delete anything if we have a selection.
1348 if (cur.selection())
1351 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1352 Paragraph const & oldpar = pars_[old.pit()];
1354 // We allow all kinds of "mumbo-jumbo" when freespacing.
1355 if (oldpar.isFreeSpacing())
1358 /* Ok I'll put some comments here about what is missing.
1359 I have fixed BackSpace (and thus Delete) to not delete
1360 double-spaces automagically. I have also changed Cut,
1361 Copy and Paste to hopefully do some sensible things.
1362 There are still some small problems that can lead to
1363 double spaces stored in the document file or space at
1364 the beginning of paragraphs(). This happens if you have
1365 the cursor between to spaces and then save. Or if you
1366 cut and paste and the selection have a space at the
1367 beginning and then save right after the paste. I am
1368 sure none of these are very hard to fix, but I will
1369 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1370 that I can get some feedback. (Lgb)
1373 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1374 // delete the LineSeparator.
1377 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1378 // delete the LineSeparator.
1381 // If the chars around the old cursor were spaces, delete one of them.
1382 if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1384 // Only if the cursor has really moved.
1386 && old.pos() < oldpar.size()
1387 && oldpar.isLineSeparator(old.pos())
1388 && oldpar.isLineSeparator(old.pos() - 1)) {
1389 pars_[old.pit()].erase(old.pos() - 1);
1390 #ifdef WITH_WARNINGS
1391 #warning This will not work anymore when we have multiple views of the same buffer
1392 // In this case, we will have to correct also the cursors held by
1393 // other bufferviews. It will probably be easier to do that in a more
1394 // automated way in CursorSlice code. (JMarc 26/09/2001)
1396 // correct all cursor parts
1397 fixCursorAfterDelete(cur.top(), old.top());
1398 #ifdef WITH_WARNINGS
1399 #warning DEPM, look here
1401 //fixCursorAfterDelete(cur.anchor(), old.top());
1406 // only do our magic if we changed paragraph
1407 if (old.pit() == cur.pit())
1410 // don't delete anything if this is the ONLY paragraph!
1411 if (pars_.size() == 1)
1414 // Do not delete empty paragraphs with keepempty set.
1415 if (oldpar.allowEmpty())
1418 // record if we have deleted a paragraph
1419 // we can't possibly have deleted a paragraph before this point
1420 bool deleted = false;
1422 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1423 // ok, we will delete something
1424 CursorSlice tmpcursor;
1428 bool selection_position_was_oldcursor_position =
1429 cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1431 // This is a bit of a overkill. We change the old and the cur par
1432 // at max, certainly not everything in between...
1433 recUndo(old.pit(), cur.pit());
1436 pars_.erase(pars_.begin() + old.pit());
1438 // Update cursor par offset if necessary.
1439 // Some 'iterator registration' would be nice that takes care of
1440 // such events. Maybe even signal/slot?
1441 if (cur.pit() > old.pit())
1443 #ifdef WITH_WARNINGS
1444 #warning DEPM, look here
1446 // if (cur.anchor().pit() > old.pit())
1447 // --cur.anchor().pit();
1449 if (selection_position_was_oldcursor_position) {
1450 // correct selection
1458 if (pars_[old.pit()].stripLeadingSpaces())
1465 ParagraphList & LyXText::paragraphs() const
1467 return const_cast<ParagraphList &>(pars_);
1471 void LyXText::recUndo(pit_type first, pit_type last) const
1473 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1477 void LyXText::recUndo(pit_type par) const
1479 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1483 int defaultRowHeight()
1485 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);