3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
15 * \author Jürgen Vigna
17 * Full author contact details are available in file CREDITS.
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
31 #include "CutAndPaste.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
36 #include "FloatList.h"
37 #include "funcrequest.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
50 #include "frontends/font_metrics.h"
51 #include "frontends/LyXView.h"
53 #include "insets/insetbibitem.h"
54 #include "insets/insetenv.h"
55 #include "insets/insetfloat.h"
56 #include "insets/insetwrap.h"
58 #include "support/lstrings.h"
59 #include "support/textutils.h"
60 #include "support/tostr.h"
66 using lyx::support::bformat;
69 using std::ostringstream;
73 LyXText::LyXText(BufferView * bv)
74 : width_(0), maxwidth_(bv ? bv->workWidth() : 100), height_(0),
75 background_color_(LColor::background),
76 bv_owner(bv), xo_(0), yo_(0)
80 void LyXText::init(BufferView * bv)
84 maxwidth_ = bv->workWidth();
88 par_type const end = paragraphs().size();
89 for (par_type pit = 0; pit != end; ++pit)
90 pars_[pit].rows.clear();
92 current_font = getFont(0, 0);
93 redoParagraphs(0, end);
98 bool LyXText::isMainText() const
100 return &bv()->buffer()->text() == this;
104 // Gets the fully instantiated font at a given position in a paragraph
105 // Basically the same routine as Paragraph::getFont() in paragraph.C.
106 // The difference is that this one is used for displaying, and thus we
107 // are allowed to make cosmetic improvements. For instance make footnotes
109 LyXFont LyXText::getFont(par_type pit, pos_type pos) const
111 BOOST_ASSERT(pos >= 0);
113 LyXLayout_ptr const & layout = pars_[pit].layout();
117 BufferParams const & params = bv()->buffer()->params();
118 pos_type const body_pos = pars_[pit].beginOfBody();
120 // We specialize the 95% common case:
121 if (!pars_[pit].getDepth()) {
122 LyXFont f = pars_[pit].getFontSettings(params, pos);
125 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
126 return f.realize(layout->reslabelfont);
128 return f.realize(layout->resfont);
131 // The uncommon case need not be optimized as much
134 layoutfont = layout->labelfont;
136 layoutfont = layout->font;
138 LyXFont font = pars_[pit].getFontSettings(params, pos);
139 font.realize(layoutfont);
144 // Realize with the fonts of lesser depth.
145 //font.realize(outerFont(pit, paragraphs()));
146 font.realize(defaultfont_);
152 LyXFont LyXText::getLayoutFont(par_type pit) const
154 LyXLayout_ptr const & layout = pars_[pit].layout();
156 if (!pars_[pit].getDepth())
157 return layout->resfont;
159 LyXFont font = layout->font;
160 // Realize with the fonts of lesser depth.
161 //font.realize(outerFont(pit, paragraphs()));
162 font.realize(defaultfont_);
168 LyXFont LyXText::getLabelFont(par_type pit) const
170 LyXLayout_ptr const & layout = pars_[pit].layout();
172 if (!pars_[pit].getDepth())
173 return layout->reslabelfont;
175 LyXFont font = layout->labelfont;
176 // Realize with the fonts of lesser depth.
177 font.realize(outerFont(pit, paragraphs()));
178 font.realize(defaultfont_);
184 void LyXText::setCharFont(par_type pit, pos_type pos, LyXFont const & fnt)
187 LyXLayout_ptr const & layout = pars_[pit].layout();
189 // Get concrete layout font to reduce against
192 if (pos < pars_[pit].beginOfBody())
193 layoutfont = layout->labelfont;
195 layoutfont = layout->font;
197 // Realize against environment font information
198 if (pars_[pit].getDepth()) {
200 while (!layoutfont.resolved() &&
201 tp != par_type(paragraphs().size()) &&
202 pars_[tp].getDepth()) {
203 tp = outerHook(tp, paragraphs());
204 if (tp != par_type(paragraphs().size()))
205 layoutfont.realize(pars_[tp].layout()->font);
209 layoutfont.realize(defaultfont_);
211 // Now, reduce font against full layout font
212 font.reduce(layoutfont);
214 pars_[pit].setFont(pos, font);
219 // Asger is not sure we want to do this...
220 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
223 LyXLayout_ptr const & layout = par.layout();
224 pos_type const psize = par.size();
227 for (pos_type pos = 0; pos < psize; ++pos) {
228 if (pos < par.beginOfBody())
229 layoutfont = layout->labelfont;
231 layoutfont = layout->font;
233 LyXFont tmpfont = par.getFontSettings(params, pos);
234 tmpfont.reduce(layoutfont);
235 par.setFont(pos, tmpfont);
240 // return past-the-last paragraph influenced by a layout change on pit
241 par_type LyXText::undoSpan(par_type pit)
243 par_type end = paragraphs().size();
244 par_type nextpit = pit + 1;
247 //because of parindents
248 if (!pars_[pit].getDepth())
249 return boost::next(nextpit);
250 //because of depth constrains
251 for (; nextpit != end; ++pit, ++nextpit) {
252 if (!pars_[pit].getDepth())
259 par_type LyXText::setLayout(par_type start, par_type end, string const & layout)
261 BOOST_ASSERT(start != end);
262 par_type undopit = undoSpan(end - 1);
263 recUndo(start, undopit - 1);
265 BufferParams const & bufparams = bv()->buffer()->params();
266 LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
268 for (par_type pit = start; pit != end; ++pit) {
269 pars_[pit].applyLayout(lyxlayout);
270 makeFontEntriesLayoutSpecific(bufparams, pars_[pit]);
271 if (lyxlayout->margintype == MARGIN_MANUAL)
272 pars_[pit].setLabelWidthString(lyxlayout->labelstring());
279 // set layout over selection and make a total rebreak of those paragraphs
280 void LyXText::setLayout(LCursor & cur, string const & layout)
282 BOOST_ASSERT(this == cur.text());
283 // special handling of new environment insets
284 BufferView & bv = cur.bv();
285 BufferParams const & params = bv.buffer()->params();
286 LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
287 if (lyxlayout->is_environment) {
288 // move everything in a new environment inset
289 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
290 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
291 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
292 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
293 InsetBase * inset = new InsetEnvironment(params, layout);
294 insertInset(cur, inset);
295 //inset->edit(cur, true);
296 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
300 par_type start = cur.selBegin().par();
301 par_type end = cur.selEnd().par() + 1;
302 par_type endpit = setLayout(start, end, layout);
303 redoParagraphs(start, endpit);
311 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
313 if (!cur.selection()) {
317 beg = cur.selBegin().par();
318 end = cur.selEnd().par() + 1;
323 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
324 Paragraph const & par, int max_depth)
326 if (par.layout()->labeltype == LABEL_BIBLIO)
328 int const depth = par.params().depth();
329 if (type == LyXText::INC_DEPTH && depth < max_depth)
331 if (type == LyXText::DEC_DEPTH && depth > 0)
340 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
342 BOOST_ASSERT(this == cur.text());
344 getSelectionSpan(cur, beg, end);
347 max_depth = pars_[beg - 1].getMaxDepthAfter();
349 for (par_type pit = beg; pit != end; ++pit) {
350 if (::changeDepthAllowed(type, pars_[pit], max_depth))
352 max_depth = pars_[pit].getMaxDepthAfter();
358 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
360 BOOST_ASSERT(this == cur.text());
362 getSelectionSpan(cur, beg, end);
363 recordUndoSelection(cur);
367 max_depth = pars_[beg - 1].getMaxDepthAfter();
369 for (par_type pit = beg; pit != end; ++pit) {
370 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
371 int const depth = pars_[pit].params().depth();
372 if (type == INC_DEPTH)
373 pars_[pit].params().depth(depth + 1);
375 pars_[pit].params().depth(depth - 1);
377 max_depth = pars_[pit].getMaxDepthAfter();
379 // this handles the counter labels, and also fixes up
380 // depth values for follow-on (child) paragraphs
385 // set font over selection
386 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
388 BOOST_ASSERT(this == cur.text());
389 // if there is no selection just set the current_font
390 if (!cur.selection()) {
391 // Determine basis font
393 par_type pit = cur.par();
394 if (cur.pos() < pars_[pit].beginOfBody())
395 layoutfont = getLabelFont(pit);
397 layoutfont = getLayoutFont(pit);
399 // Update current font
400 real_current_font.update(font,
401 cur.buffer().params().language,
404 // Reduce to implicit settings
405 current_font = real_current_font;
406 current_font.reduce(layoutfont);
407 // And resolve it completely
408 real_current_font.realize(layoutfont);
413 // Ok, we have a selection.
414 recordUndoSelection(cur);
416 par_type const beg = cur.selBegin().par();
417 par_type const end = cur.selEnd().par();
419 DocIterator pos = cur.selectionBegin();
420 DocIterator posend = cur.selectionEnd();
422 lyxerr[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
425 BufferParams const & params = cur.buffer().params();
427 // Don't use forwardChar here as posend might have
428 // pos() == lastpos() and forwardChar would miss it.
429 for (; pos != posend; pos.forwardPos()) {
430 if (pos.pos() != pos.lastpos()) {
431 LyXFont f = getFont(pos.par(), pos.pos());
432 f.update(font, params.language, toggleall);
433 setCharFont(pos.par(), pos.pos(), f);
437 redoParagraphs(beg, end + 1);
441 // the cursor set functions have a special mechanism. When they
442 // realize you left an empty paragraph, they will delete it.
444 void LyXText::cursorHome(LCursor & cur)
446 BOOST_ASSERT(this == cur.text());
447 setCursor(cur, cur.par(), cur.textRow().pos());
451 void LyXText::cursorEnd(LCursor & cur)
453 BOOST_ASSERT(this == cur.text());
454 // if not on the last row of the par, put the cursor before
456 pos_type const end = cur.textRow().endpos();
457 setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
461 void LyXText::cursorTop(LCursor & cur)
463 BOOST_ASSERT(this == cur.text());
464 setCursor(cur, 0, 0);
468 void LyXText::cursorBottom(LCursor & cur)
470 BOOST_ASSERT(this == cur.text());
471 setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
475 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
477 BOOST_ASSERT(this == cur.text());
478 // If the mask is completely neutral, tell user
479 if (font == LyXFont(LyXFont::ALL_IGNORE)) {
480 // Could only happen with user style
481 cur.message(_("No font change defined. "
482 "Use Character under the Layout menu to define font change."));
486 // Try implicit word selection
487 // If there is a change in the language the implicit word selection
489 CursorSlice resetCursor = cur.top();
490 bool implicitSelection =
491 font.language() == ignore_language
492 && font.number() == LyXFont::IGNORE
493 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
496 setFont(cur, font, toggleall);
498 // Implicit selections are cleared afterwards
499 // and cursor is set to the original position.
500 if (implicitSelection) {
501 cur.clearSelection();
502 cur.top() = resetCursor;
508 string LyXText::getStringToIndex(LCursor & cur)
510 BOOST_ASSERT(this == cur.text());
511 // Try implicit word selection
512 // If there is a change in the language the implicit word selection
514 CursorSlice const reset_cursor = cur.top();
515 bool const implicitSelection =
516 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
519 if (!cur.selection())
520 cur.message(_("Nothing to index!"));
521 else if (cur.selBegin().par() != cur.selEnd().par())
522 cur.message(_("Cannot index more than one paragraph!"));
524 idxstring = cur.selectionAsString(false);
526 // Reset cursors to their original position.
527 cur.top() = reset_cursor;
530 // Clear the implicit selection.
531 if (implicitSelection)
532 cur.clearSelection();
538 void LyXText::setParagraph(LCursor & cur,
539 Spacing const & spacing, LyXAlignment align,
540 string const & labelwidthstring, bool noindent)
542 BOOST_ASSERT(cur.text());
543 // make sure that the depth behind the selection are restored, too
544 par_type undopit = undoSpan(cur.selEnd().par());
545 recUndo(cur.selBegin().par(), undopit - 1);
547 for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
549 Paragraph & par = pars_[pit];
550 ParagraphParameters & params = par.params();
551 params.spacing(spacing);
553 // does the layout allow the new alignment?
554 LyXLayout_ptr const & layout = par.layout();
556 if (align == LYX_ALIGN_LAYOUT)
557 align = layout->align;
558 if (align & layout->alignpossible) {
559 if (align == layout->align)
560 params.align(LYX_ALIGN_LAYOUT);
564 par.setLabelWidthString(labelwidthstring);
565 params.noindent(noindent);
568 redoParagraphs(cur.selBegin().par(), undopit);
572 string expandLabel(LyXTextClass const & textclass,
573 LyXLayout_ptr const & layout, bool appendix)
575 string fmt = appendix ?
576 layout->labelstring_appendix() : layout->labelstring();
578 // handle 'inherited level parts' in 'fmt',
579 // i.e. the stuff between '@' in '@Section@.\arabic{subsection}'
580 size_t const i = fmt.find('@', 0);
581 if (i != string::npos) {
582 size_t const j = fmt.find('@', i + 1);
583 if (j != string::npos) {
584 string parent(fmt, i + 1, j - i - 1);
585 string label = expandLabel(textclass, textclass[parent], appendix);
586 fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
590 return textclass.counters().counterLabel(fmt);
596 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
598 int const cur_labeltype = pars[pit].layout()->labeltype;
600 if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
603 int const cur_depth = pars[pit].getDepth();
605 par_type prev_pit = pit - 1;
607 int const prev_depth = pars[prev_pit].getDepth();
608 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
609 if (prev_depth == 0 && cur_depth > 0) {
610 if (prev_labeltype == cur_labeltype) {
611 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
614 } else if (prev_depth < cur_depth) {
615 if (prev_labeltype == cur_labeltype) {
616 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
619 } else if (prev_depth == cur_depth) {
620 if (prev_labeltype == cur_labeltype) {
621 pars[pit].itemdepth = pars[prev_pit].itemdepth;
625 if (prev_pit == first_pit)
633 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
634 par_type firstpit, Counters & counters)
639 int const cur_depth = pars[pit].getDepth();
640 par_type prev_pit = pit - 1;
642 int const prev_depth = pars[prev_pit].getDepth();
643 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
644 if (prev_depth <= cur_depth) {
645 if (prev_labeltype != LABEL_ENUMERATE) {
646 switch (pars[pit].itemdepth) {
648 counters.reset("enumi");
650 counters.reset("enumii");
652 counters.reset("enumiii");
654 counters.reset("enumiv");
660 if (prev_pit == firstpit)
670 // set the counter of a paragraph. This includes the labels
671 void LyXText::setCounter(Buffer const & buf, par_type pit)
673 BufferParams const & bufparams = buf.params();
674 LyXTextClass const & textclass = bufparams.getLyXTextClass();
675 LyXLayout_ptr const & layout = pars_[pit].layout();
676 par_type first_pit = 0;
677 Counters & counters = textclass.counters();
680 pars_[pit].itemdepth = 0;
682 if (pit == first_pit) {
683 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
685 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
686 if (!pars_[pit].params().appendix() &&
687 pars_[pit].params().startOfAppendix()) {
688 pars_[pit].params().appendix(true);
689 textclass.counters().reset();
692 // Maybe we have to increment the item depth.
693 incrementItemDepth(pars_, pit, first_pit);
696 // erase what was there before
697 pars_[pit].params().labelString(string());
699 if (layout->margintype == MARGIN_MANUAL) {
700 if (pars_[pit].params().labelWidthString().empty())
701 pars_[pit].setLabelWidthString(layout->labelstring());
703 pars_[pit].setLabelWidthString(string());
706 // is it a layout that has an automatic label?
707 if (layout->labeltype == LABEL_COUNTER) {
708 BufferParams const & bufparams = buf.params();
709 LyXTextClass const & textclass = bufparams.getLyXTextClass();
710 counters.step(layout->counter);
711 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
712 pars_[pit].params().labelString(label);
713 } else if (layout->labeltype == LABEL_ITEMIZE) {
714 // At some point of time we should do something more
715 // clever here, like:
716 // pars_[pit].params().labelString(
717 // bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
718 // for now, use a simple hardcoded label
720 switch (pars_[pit].itemdepth) {
735 pars_[pit].params().labelString(itemlabel);
736 } else if (layout->labeltype == LABEL_ENUMERATE) {
737 // Maybe we have to reset the enumeration counter.
738 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
741 // Yes I know this is a really, really! bad solution
743 string enumcounter = "enum";
745 switch (pars_[pit].itemdepth) {
757 // not a valid enumdepth...
761 counters.step(enumcounter);
763 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
764 } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
765 counters.step("bibitem");
766 int number = counters.value("bibitem");
767 if (pars_[pit].bibitem()) {
768 pars_[pit].bibitem()->setCounter(number);
769 pars_[pit].params().labelString(layout->labelstring());
771 // In biblio should't be following counters but...
773 string s = buf.B_(layout->labelstring());
776 if (layout->labeltype == LABEL_SENSITIVE) {
777 par_type end = paragraphs().size();
778 par_type tmppit = pit;
781 while (tmppit != end) {
782 in = pars_[tmppit].inInset();
783 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
784 in->lyxCode() == InsetBase::WRAP_CODE) {
788 Paragraph const * owner = &ownerPar(buf, in);
790 for ( ; tmppit != end; ++tmppit)
791 if (&pars_[tmppit] == owner)
799 if (in->lyxCode() == InsetBase::FLOAT_CODE)
800 type = static_cast<InsetFloat*>(in)->params().type;
801 else if (in->lyxCode() == InsetBase::WRAP_CODE)
802 type = static_cast<InsetWrap*>(in)->params().type;
806 Floating const & fl = textclass.floats().getType(type);
808 counters.step(fl.type());
810 // Doesn't work... yet.
811 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
813 // par->SetLayout(0);
814 // s = layout->labelstring;
815 s = _("Senseless: ");
818 pars_[pit].params().labelString(s);
824 // Updates all counters.
825 void LyXText::updateCounters()
828 bv()->buffer()->params().getLyXTextClass().counters().reset();
830 bool update_pos = false;
832 par_type end = paragraphs().size();
833 for (par_type pit = 0; pit != end; ++pit) {
834 string const oldLabel = pars_[pit].params().labelString();
837 maxdepth = pars_[pit - 1].getMaxDepthAfter();
839 if (pars_[pit].params().depth() > maxdepth)
840 pars_[pit].params().depth(maxdepth);
842 // setCounter can potentially change the labelString.
843 setCounter(*bv()->buffer(), pit);
844 string const & newLabel = pars_[pit].params().labelString();
845 if (oldLabel != newLabel) {
846 //lyxerr[Debug::DEBUG] << "changing labels: old: " << oldLabel << " new: "
847 // << newLabel << endl;
848 redoParagraphInternal(pit);
853 updateParPositions();
857 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
859 BOOST_ASSERT(this == cur.text());
861 cur.paragraph().insertInset(cur.pos(), inset);
863 setCursor(cur, cur.par(), cur.pos() + 1, false, cur.boundary());
867 // needed to insert the selection
868 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
870 par_type pit = cur.par();
871 par_type endpit = cur.par() + 1;
872 pos_type pos = cur.pos();
875 // only to be sure, should not be neccessary
876 cur.clearSelection();
877 cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
879 redoParagraphs(cur.par(), endpit);
881 setCursor(cur, cur.par(), pos);
886 // turn double CR to single CR, others are converted into one
887 // blank. Then insertStringAsLines is called
888 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
890 string linestr = str;
891 bool newline_inserted = false;
893 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
894 if (linestr[i] == '\n') {
895 if (newline_inserted) {
896 // we know that \r will be ignored by
897 // insertStringAsLines. Of course, it is a dirty
898 // trick, but it works...
899 linestr[i - 1] = '\r';
903 newline_inserted = true;
905 } else if (IsPrintable(linestr[i])) {
906 newline_inserted = false;
909 insertStringAsLines(cur, linestr);
913 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
914 bool setfont, bool boundary)
917 setCursorIntern(cur, par, pos, setfont, boundary);
918 return deleteEmptyParagraphMechanism(cur, old);
922 void LyXText::setCursor(CursorSlice & cur, par_type par,
923 pos_type pos, bool boundary)
925 BOOST_ASSERT(par != int(paragraphs().size()));
929 cur.boundary() = boundary;
931 // no rows, no fun...
932 if (paragraphs().begin()->rows.empty())
935 // now some strict checking
936 Paragraph & para = getPar(par);
937 Row const & row = *para.getRow(pos);
938 pos_type const end = row.endpos();
940 // None of these should happen, but we're scaredy-cats
942 lyxerr << "dont like -1" << endl;
946 if (pos > para.size()) {
947 lyxerr << "dont like 1, pos: " << pos
948 << " size: " << para.size()
949 << " row.pos():" << row.pos()
950 << " par: " << par << endl;
955 lyxerr << "dont like 2, pos: " << pos
956 << " size: " << para.size()
957 << " row.pos():" << row.pos()
958 << " par: " << par << endl;
959 // This shouldn't happen.
963 if (pos < row.pos()) {
964 lyxerr << "dont like 3 please report pos:" << pos
965 << " size: " << para.size()
966 << " row.pos():" << row.pos()
967 << " par: " << par << endl;
973 void LyXText::setCursorIntern(LCursor & cur,
974 par_type par, pos_type pos, bool setfont, bool boundary)
976 setCursor(cur.top(), par, pos, boundary);
977 cur.x_target() = cursorX(cur.top());
983 void LyXText::setCurrentFont(LCursor & cur)
985 BOOST_ASSERT(this == cur.text());
986 pos_type pos = cur.pos();
987 par_type pit = cur.par();
989 if (cur.boundary() && pos > 0)
993 if (pos == cur.lastpos())
995 else // potentional bug... BUG (Lgb)
996 if (pars_[pit].isSeparator(pos)) {
997 if (pos > cur.textRow().pos() &&
998 bidi.level(pos) % 2 ==
999 bidi.level(pos - 1) % 2)
1001 else if (pos + 1 < cur.lastpos())
1006 BufferParams const & bufparams = cur.buffer().params();
1007 current_font = pars_[pit].getFontSettings(bufparams, pos);
1008 real_current_font = getFont(pit, pos);
1010 if (cur.pos() == cur.lastpos()
1011 && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1012 && !cur.boundary()) {
1013 Language const * lang = pars_[pit].getParLanguage(bufparams);
1014 current_font.setLanguage(lang);
1015 current_font.setNumber(LyXFont::OFF);
1016 real_current_font.setLanguage(lang);
1017 real_current_font.setNumber(LyXFont::OFF);
1022 // x is an absolute screen coord
1023 // returns the column near the specified x-coordinate of the row
1024 // x is set to the real beginning of this column
1025 pos_type LyXText::getColumnNearX(par_type pit,
1026 Row const & row, int & x, bool & boundary) const
1029 RowMetrics const r = computeRowMetrics(pit, row);
1031 pos_type vc = row.pos();
1032 pos_type end = row.endpos();
1034 LyXLayout_ptr const & layout = pars_[pit].layout();
1036 bool left_side = false;
1038 pos_type body_pos = pars_[pit].beginOfBody();
1041 double last_tmpx = tmpx;
1044 (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1047 // check for empty row
1049 x = int(tmpx) + xo_;
1053 while (vc < end && tmpx <= x) {
1054 c = bidi.vis2log(vc);
1056 if (body_pos > 0 && c == body_pos - 1) {
1057 tmpx += r.label_hfill +
1058 font_metrics::width(layout->labelsep, getLabelFont(pit));
1059 if (pars_[pit].isLineSeparator(body_pos - 1))
1060 tmpx -= singleWidth(pit, body_pos - 1);
1063 if (hfillExpansion(pars_[pit], row, c)) {
1064 tmpx += singleWidth(pit, c);
1068 tmpx += r.label_hfill;
1069 } else if (pars_[pit].isSeparator(c)) {
1070 tmpx += singleWidth(pit, c);
1072 tmpx += r.separator;
1074 tmpx += singleWidth(pit, c);
1079 if ((tmpx + last_tmpx) / 2 > x) {
1084 BOOST_ASSERT(vc <= end); // This shouldn't happen.
1087 // This (rtl_support test) is not needed, but gives
1088 // some speedup if rtl_support == false
1089 bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1091 // If lastrow is false, we don't need to compute
1092 // the value of rtl.
1093 bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1095 ((rtl && left_side && vc == row.pos() && x < tmpx - 5) ||
1096 (!rtl && !left_side && vc == end && x > tmpx + 5)))
1098 else if (vc == row.pos()) {
1099 c = bidi.vis2log(vc);
1100 if (bidi.level(c) % 2 == 1)
1103 c = bidi.vis2log(vc - 1);
1104 bool const rtl = (bidi.level(c) % 2 == 1);
1105 if (left_side == rtl) {
1107 boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1111 if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1112 if (bidi.level(end -1) % 2 == 0)
1113 tmpx -= singleWidth(pit, end - 1);
1115 tmpx += singleWidth(pit, end - 1);
1119 x = int(tmpx) + xo_;
1120 return c - row.pos();
1124 // x,y are absolute coordinates
1125 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1130 Row const & row = getRowNearY(y, pit);
1131 lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1132 << row.pos() << endl;
1134 int xx = x + xo_; // getRowNearX get absolute x coords
1135 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1136 setCursor(cur, pit, pos, true, bound);
1140 // x,y are absolute screen coordinates
1141 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1144 Row const & row = getRowNearY(y - yo_, pit);
1147 int xx = x; // is modified by getColumnNearX
1148 pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1151 cur.boundary() = bound;
1153 // try to descend into nested insets
1154 InsetBase * inset = checkInsetHit(x, y);
1155 lyxerr[Debug::DEBUG] << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1159 // This should be just before or just behind the
1160 // cursor position set above.
1161 BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1162 || inset == pars_[pit].getInset(pos));
1163 // Make sure the cursor points to the position before
1165 if (inset == pars_[pit].getInset(pos - 1))
1167 return inset->editXY(cur, x, y);
1171 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1173 if (cur.selection())
1175 if (cur.pos() == cur.lastpos())
1177 InsetBase * inset = cur.nextInset();
1178 if (!isHighlyEditableInset(inset))
1180 inset->edit(cur, front);
1185 void LyXText::cursorLeft(LCursor & cur)
1187 if (cur.pos() != 0) {
1188 bool boundary = cur.boundary();
1189 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1190 if (!checkAndActivateInset(cur, false)) {
1191 if (false && !boundary &&
1192 bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1193 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1198 if (cur.par() != 0) {
1199 // steps into the paragraph above
1200 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1205 void LyXText::cursorRight(LCursor & cur)
1207 if (false && cur.boundary()) {
1208 setCursor(cur, cur.par(), cur.pos(), true, false);
1212 if (cur.pos() != cur.lastpos()) {
1213 if (!checkAndActivateInset(cur, true)) {
1214 setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1215 if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1217 setCursor(cur, cur.par(), cur.pos(), true, true);
1222 if (cur.par() != cur.lastpar())
1223 setCursor(cur, cur.par() + 1, 0);
1227 void LyXText::cursorUp(LCursor & cur)
1229 Row const & row = cur.textRow();
1230 int x = cur.x_target();
1231 int y = cursorY(cur.top()) - row.baseline() - 1;
1232 setCursorFromCoordinates(cur, x, y);
1234 if (!cur.selection()) {
1235 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1236 if (inset_hit && isHighlyEditableInset(inset_hit))
1237 inset_hit->editXY(cur, cur.x_target(), y);
1242 void LyXText::cursorDown(LCursor & cur)
1244 Row const & row = cur.textRow();
1245 int x = cur.x_target();
1246 int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1247 setCursorFromCoordinates(cur, x, y);
1249 if (!cur.selection()) {
1250 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1251 if (inset_hit && isHighlyEditableInset(inset_hit))
1252 inset_hit->editXY(cur, cur.x_target(), y);
1257 void LyXText::cursorUpParagraph(LCursor & cur)
1260 setCursor(cur, cur.par(), 0);
1261 else if (cur.par() != 0)
1262 setCursor(cur, cur.par() - 1, 0);
1266 void LyXText::cursorDownParagraph(LCursor & cur)
1268 if (cur.par() != cur.lastpar())
1269 setCursor(cur, cur.par() + 1, 0);
1271 setCursor(cur, cur.par(), cur.lastpos());
1275 // fix the cursor `cur' after a characters has been deleted at `where'
1276 // position. Called by deleteEmptyParagraphMechanism
1277 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1279 // do notheing if cursor is not in the paragraph where the
1280 // deletion occured,
1281 if (cur.par() != where.par())
1284 // if cursor position is after the deletion place update it
1285 if (cur.pos() > where.pos())
1288 // check also if we don't want to set the cursor on a spot behind the
1289 // pagragraph because we erased the last character.
1290 if (cur.pos() > cur.lastpos())
1291 cur.pos() = cur.lastpos();
1295 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1297 BOOST_ASSERT(cur.size() == old.size());
1298 // Would be wrong to delete anything if we have a selection.
1299 if (cur.selection())
1302 //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1303 Paragraph const & oldpar = pars_[old.par()];
1305 // We allow all kinds of "mumbo-jumbo" when freespacing.
1306 if (oldpar.isFreeSpacing())
1309 /* Ok I'll put some comments here about what is missing.
1310 I have fixed BackSpace (and thus Delete) to not delete
1311 double-spaces automagically. I have also changed Cut,
1312 Copy and Paste to hopefully do some sensible things.
1313 There are still some small problems that can lead to
1314 double spaces stored in the document file or space at
1315 the beginning of paragraphs(). This happens if you have
1316 the cursor between to spaces and then save. Or if you
1317 cut and paste and the selection have a space at the
1318 beginning and then save right after the paste. I am
1319 sure none of these are very hard to fix, but I will
1320 put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1321 that I can get some feedback. (Lgb)
1324 // If old.pos() == 0 and old.pos()(1) == LineSeparator
1325 // delete the LineSeparator.
1328 // If old.pos() == 1 and old.pos()(0) == LineSeparator
1329 // delete the LineSeparator.
1332 // If the chars around the old cursor were spaces, delete one of them.
1333 if (old.par() != cur.par() || old.pos() != cur.pos()) {
1335 // Only if the cursor has really moved.
1337 && old.pos() < oldpar.size()
1338 && oldpar.isLineSeparator(old.pos())
1339 && oldpar.isLineSeparator(old.pos() - 1)) {
1340 pars_[old.par()].erase(old.pos() - 1);
1341 #ifdef WITH_WARNINGS
1342 #warning This will not work anymore when we have multiple views of the same buffer
1343 // In this case, we will have to correct also the cursors held by
1344 // other bufferviews. It will probably be easier to do that in a more
1345 // automated way in CursorSlice code. (JMarc 26/09/2001)
1347 // correct all cursor parts
1348 fixCursorAfterDelete(cur.top(), old.top());
1349 #ifdef WITH_WARNINGS
1350 #warning DEPM, look here
1352 //fixCursorAfterDelete(cur.anchor(), old.top());
1357 // only do our magic if we changed paragraph
1358 if (old.par() == cur.par())
1361 // don't delete anything if this is the ONLY paragraph!
1362 if (pars_.size() == 1)
1365 // Do not delete empty paragraphs with keepempty set.
1366 if (oldpar.allowEmpty())
1369 // record if we have deleted a paragraph
1370 // we can't possibly have deleted a paragraph before this point
1371 bool deleted = false;
1373 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1374 // ok, we will delete something
1375 CursorSlice tmpcursor;
1379 bool selection_position_was_oldcursor_position =
1380 cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1382 // This is a bit of a overkill. We change the old and the cur par
1383 // at max, certainly not everything in between...
1384 recUndo(old.par(), cur.par());
1387 pars_.erase(pars_.begin() + old.par());
1389 // Update cursor par offset if necessary.
1390 // Some 'iterator registration' would be nice that takes care of
1391 // such events. Maybe even signal/slot?
1392 if (cur.par() > old.par())
1394 #ifdef WITH_WARNINGS
1395 #warning DEPM, look here
1397 // if (cur.anchor().par() > old.par())
1398 // --cur.anchor().par();
1400 if (selection_position_was_oldcursor_position) {
1401 // correct selection
1409 if (pars_[old.par()].stripLeadingSpaces())
1416 ParagraphList & LyXText::paragraphs() const
1418 return const_cast<ParagraphList &>(pars_);
1422 void LyXText::recUndo(par_type first, par_type last) const
1424 recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1428 void LyXText::recUndo(par_type par) const
1430 recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1434 int defaultRowHeight()
1436 return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) * 1.2);