]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
addToToc, pass parent ParConstIterator, fix bug 3711
[lyx.git] / src / Text2.cpp
1 /**
2  * \file text2.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Dekel Tsur
15  * \author Jürgen Vigna
16  *
17  * Full author contact details are available in file CREDITS.
18  */
19
20 #include <config.h>
21
22 #include "Text.h"
23
24 #include "Buffer.h"
25 #include "buffer_funcs.h"
26 #include "BufferList.h"
27 #include "BufferParams.h"
28 #include "BufferView.h"
29 #include "bufferview_funcs.h"
30 #include "Bullet.h"
31 #include "CoordCache.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "debug.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
38 #include "gettext.h"
39 #include "Language.h"
40 #include "Color.h"
41 #include "LyXFunc.h"
42 #include "LyXRC.h"
43 #include "Row.h"
44 #include "Paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "ParIterator.h"
49 #include "Server.h"
50 #include "ServerSocket.h"
51 #include "Undo.h"
52 #include "VSpace.h"
53
54 #include "frontends/FontMetrics.h"
55
56 #include "insets/InsetEnvironment.h"
57
58 #include "mathed/InsetMathHull.h"
59
60 #include "support/textutils.h"
61
62 #include <boost/current_function.hpp>
63
64 #include <sstream>
65
66
67 namespace lyx {
68
69 using std::endl;
70 using std::ostringstream;
71 using std::string;
72 using std::max;
73 using std::min;
74
75
76 Text::Text()
77         : current_font(Font::ALL_INHERIT),
78           background_color_(Color::background),
79           autoBreakRows_(false)
80 {}
81
82
83 bool Text::isMainText(Buffer const & buffer) const
84 {
85         return &buffer.text() == this;
86 }
87
88
89 //takes screen x,y coordinates
90 Inset * Text::checkInsetHit(BufferView & bv, int x, int y)
91 {
92         pit_type pit = getPitNearY(bv, y);
93         BOOST_ASSERT(pit != -1);
94
95         Paragraph const & par = pars_[pit];
96
97         LYXERR(Debug::DEBUG)
98                 << BOOST_CURRENT_FUNCTION
99                 << ": x: " << x
100                 << " y: " << y
101                 << "  pit: " << pit
102                 << endl;
103         InsetList::const_iterator iit = par.insetlist.begin();
104         InsetList::const_iterator iend = par.insetlist.end();
105         for (; iit != iend; ++iit) {
106                 Inset * inset = iit->inset;
107 #if 1
108                 LYXERR(Debug::DEBUG)
109                         << BOOST_CURRENT_FUNCTION
110                         << ": examining inset " << inset << endl;
111
112                 if (bv.coordCache().getInsets().has(inset))
113                         LYXERR(Debug::DEBUG)
114                                 << BOOST_CURRENT_FUNCTION
115                                 << ": xo: " << inset->xo(bv) << "..."
116                                 << inset->xo(bv) + inset->width()
117                                 << " yo: " << inset->yo(bv) - inset->ascent()
118                                 << "..."
119                                 << inset->yo(bv) + inset->descent()
120                                 << endl;
121                 else
122                         LYXERR(Debug::DEBUG)
123                                 << BOOST_CURRENT_FUNCTION
124                                 << ": inset has no cached position" << endl;
125 #endif
126                 if (inset->covers(bv, x, y)) {
127                         LYXERR(Debug::DEBUG)
128                                 << BOOST_CURRENT_FUNCTION
129                                 << ": Hit inset: " << inset << endl;
130                         return inset;
131                 }
132         }
133         LYXERR(Debug::DEBUG)
134                 << BOOST_CURRENT_FUNCTION
135                 << ": No inset hit. " << endl;
136         return 0;
137 }
138
139
140
141 // Gets the fully instantiated font at a given position in a paragraph
142 // Basically the same routine as Paragraph::getFont() in Paragraph.cpp.
143 // The difference is that this one is used for displaying, and thus we
144 // are allowed to make cosmetic improvements. For instance make footnotes
145 // smaller. (Asger)
146 Font Text::getFont(Buffer const & buffer, Paragraph const & par,
147                 pos_type const pos) const
148 {
149         BOOST_ASSERT(pos >= 0);
150
151         Layout_ptr const & layout = par.layout();
152 #ifdef WITH_WARNINGS
153 #warning broken?
154 #endif
155         BufferParams const & params = buffer.params();
156         pos_type const body_pos = par.beginOfBody();
157
158         // We specialize the 95% common case:
159         if (!par.getDepth()) {
160                 Font f = par.getFontSettings(params, pos);
161                 if (!isMainText(buffer))
162                         applyOuterFont(buffer, f);
163                 Font lf;
164                 Font rlf;
165                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
166                         lf = layout->labelfont;
167                         rlf = layout->reslabelfont;
168                 } else {
169                         lf = layout->font;
170                         rlf = layout->resfont;
171                 }
172                 // In case the default family has been customized
173                 if (lf.family() == Font::INHERIT_FAMILY)
174                         rlf.setFamily(params.getFont().family());
175                 return f.realize(rlf);
176         }
177
178         // The uncommon case need not be optimized as much
179         Font layoutfont;
180         if (pos < body_pos)
181                 layoutfont = layout->labelfont;
182         else
183                 layoutfont = layout->font;
184
185         Font font = par.getFontSettings(params, pos);
186         font.realize(layoutfont);
187
188         if (!isMainText(buffer))
189                 applyOuterFont(buffer, font);
190
191         // Find the pit value belonging to paragraph. This will not break
192         // even if pars_ would not be a vector anymore.
193         // Performance appears acceptable.
194
195         pit_type pit = pars_.size();
196         for (pit_type it = 0; it < pit; ++it)
197                 if (&pars_[it] == &par) {
198                         pit = it;
199                         break;
200                 }
201         // Realize against environment font information
202         // NOTE: the cast to pit_type should be removed when pit_type
203         // changes to a unsigned integer.
204         if (pit < pit_type(pars_.size()))
205                 font.realize(outerFont(pit, pars_));
206
207         // Realize with the fonts of lesser depth.
208         font.realize(params.getFont());
209
210         return font;
211 }
212
213 // There are currently two font mechanisms in LyX:
214 // 1. The font attributes in a lyxtext, and
215 // 2. The inset-specific font properties, defined in an inset's
216 // metrics() and draw() methods and handed down the inset chain through
217 // the pi/mi parameters, and stored locally in a lyxtext in font_.
218 // This is where the two are integrated in the final fully realized
219 // font.
220 void Text::applyOuterFont(Buffer const & buffer, Font & font) const {
221         Font lf(font_);
222         lf.reduce(buffer.params().getFont());
223         lf.realize(font);
224         lf.setLanguage(font.language());
225         font = lf;
226 }
227
228
229 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
230 {
231         Layout_ptr const & layout = pars_[pit].layout();
232
233         if (!pars_[pit].getDepth())  {
234                 Font lf = layout->resfont;
235                 // In case the default family has been customized
236                 if (layout->font.family() == Font::INHERIT_FAMILY)
237                         lf.setFamily(buffer.params().getFont().family());
238                 return lf;
239         }
240
241         Font font = layout->font;
242         // Realize with the fonts of lesser depth.
243         //font.realize(outerFont(pit, paragraphs()));
244         font.realize(buffer.params().getFont());
245
246         return font;
247 }
248
249
250 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
251 {
252         Layout_ptr const & layout = par.layout();
253
254         if (!par.getDepth()) {
255                 Font lf = layout->reslabelfont;
256                 // In case the default family has been customized
257                 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
258                         lf.setFamily(buffer.params().getFont().family());
259                 return lf;
260         }
261
262         Font font = layout->labelfont;
263         // Realize with the fonts of lesser depth.
264         font.realize(buffer.params().getFont());
265
266         return font;
267 }
268
269
270 void Text::setCharFont(Buffer const & buffer, pit_type pit,
271                 pos_type pos, Font const & fnt)
272 {
273         Font font = fnt;
274         Layout_ptr const & layout = pars_[pit].layout();
275
276         // Get concrete layout font to reduce against
277         Font layoutfont;
278
279         if (pos < pars_[pit].beginOfBody())
280                 layoutfont = layout->labelfont;
281         else
282                 layoutfont = layout->font;
283
284         // Realize against environment font information
285         if (pars_[pit].getDepth()) {
286                 pit_type tp = pit;
287                 while (!layoutfont.resolved() &&
288                        tp != pit_type(paragraphs().size()) &&
289                        pars_[tp].getDepth()) {
290                         tp = outerHook(tp, paragraphs());
291                         if (tp != pit_type(paragraphs().size()))
292                                 layoutfont.realize(pars_[tp].layout()->font);
293                 }
294         }
295
296         // Inside inset, apply the inset's font attributes if any
297         // (charstyle!)
298         if (!isMainText(buffer))
299                 layoutfont.realize(font_);
300
301         layoutfont.realize(buffer.params().getFont());
302
303         // Now, reduce font against full layout font
304         font.reduce(layoutfont);
305
306         pars_[pit].setFont(pos, font);
307 }
308
309
310 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
311                 pos_type pos, Font const & font, bool toggleall)
312 {
313         BOOST_ASSERT(pars_[pit].isInset(pos) &&
314                      pars_[pit].getInset(pos)->noFontChange());
315
316         Inset * const inset = pars_[pit].getInset(pos);
317         DocIterator dit = doc_iterator_begin(*inset);
318         // start of the last cell
319         DocIterator end = dit;
320         end.idx() = end.lastidx();
321
322         while (true) {
323                 Text * text = dit.text();
324                 Inset * cell = dit.realInset();
325                 if (text && cell) {
326                         DocIterator cellbegin = doc_iterator_begin(*cell);
327                         // last position of the cell
328                         DocIterator cellend = cellbegin;
329                         cellend.pit() = cellend.lastpit();
330                         cellend.pos() = cellend.lastpos();
331                         text->setFont(buffer, cellbegin, cellend, font, toggleall);
332                 }
333                 if (dit == end)
334                         break;
335                 dit.forwardIdx();
336         }
337 }
338
339
340 // return past-the-last paragraph influenced by a layout change on pit
341 pit_type Text::undoSpan(pit_type pit)
342 {
343         pit_type end = paragraphs().size();
344         pit_type nextpit = pit + 1;
345         if (nextpit == end)
346                 return nextpit;
347         //because of parindents
348         if (!pars_[pit].getDepth())
349                 return boost::next(nextpit);
350         //because of depth constrains
351         for (; nextpit != end; ++pit, ++nextpit) {
352                 if (!pars_[pit].getDepth())
353                         break;
354         }
355         return nextpit;
356 }
357
358
359 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
360                 string const & layout)
361 {
362         BOOST_ASSERT(start != end);
363
364         BufferParams const & bufparams = buffer.params();
365         Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
366
367         for (pit_type pit = start; pit != end; ++pit) {
368                 Paragraph & par = pars_[pit];
369                 par.applyLayout(lyxlayout);
370                 if (lyxlayout->margintype == MARGIN_MANUAL)
371                         par.setLabelWidthString(par.translateIfPossible(
372                                 lyxlayout->labelstring(), buffer.params()));
373         }
374 }
375
376
377 // set layout over selection and make a total rebreak of those paragraphs
378 void Text::setLayout(Cursor & cur, string const & layout)
379 {
380         BOOST_ASSERT(this == cur.text());
381         // special handling of new environment insets
382         BufferView & bv = cur.bv();
383         BufferParams const & params = bv.buffer()->params();
384         Layout_ptr const & lyxlayout = params.getTextClass()[layout];
385         if (lyxlayout->is_environment) {
386                 // move everything in a new environment inset
387                 LYXERR(Debug::DEBUG) << "setting layout " << layout << endl;
388                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
389                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
390                 lyx::dispatch(FuncRequest(LFUN_CUT));
391                 Inset * inset = new InsetEnvironment(params, layout);
392                 insertInset(cur, inset);
393                 //inset->edit(cur, true);
394                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
395                 return;
396         }
397
398         pit_type start = cur.selBegin().pit();
399         pit_type end = cur.selEnd().pit() + 1;
400         pit_type undopit = undoSpan(end - 1);
401         recUndo(cur, start, undopit - 1);
402         setLayout(cur.buffer(), start, end, layout);
403         updateLabels(cur.buffer());
404 }
405
406
407 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
408                         Paragraph const & par, int max_depth)
409 {
410         if (par.layout()->labeltype == LABEL_BIBLIO)
411                 return false;
412         int const depth = par.params().depth();
413         if (type == Text::INC_DEPTH && depth < max_depth)
414                 return true;
415         if (type == Text::DEC_DEPTH && depth > 0)
416                 return true;
417         return false;
418 }
419
420
421 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
422 {
423         BOOST_ASSERT(this == cur.text());
424         // this happens when selecting several cells in tabular (bug 2630)
425         if (cur.selBegin().idx() != cur.selEnd().idx())
426                 return false;
427
428         pit_type const beg = cur.selBegin().pit();
429         pit_type const end = cur.selEnd().pit() + 1;
430         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
431
432         for (pit_type pit = beg; pit != end; ++pit) {
433                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
434                         return true;
435                 max_depth = pars_[pit].getMaxDepthAfter();
436         }
437         return false;
438 }
439
440
441 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
442 {
443         BOOST_ASSERT(this == cur.text());
444         pit_type const beg = cur.selBegin().pit();
445         pit_type const end = cur.selEnd().pit() + 1;
446         recordUndoSelection(cur);
447         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
448
449         for (pit_type pit = beg; pit != end; ++pit) {
450                 Paragraph & par = pars_[pit];
451                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
452                         int const depth = par.params().depth();
453                         if (type == INC_DEPTH)
454                                 par.params().depth(depth + 1);
455                         else
456                                 par.params().depth(depth - 1);
457                 }
458                 max_depth = par.getMaxDepthAfter();
459         }
460         // this handles the counter labels, and also fixes up
461         // depth values for follow-on (child) paragraphs
462         updateLabels(cur.buffer());
463 }
464
465
466 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
467 {
468         BOOST_ASSERT(this == cur.text());
469         // Set the current_font
470         // Determine basis font
471         Font layoutfont;
472         pit_type pit = cur.pit();
473         if (cur.pos() < pars_[pit].beginOfBody())
474                 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
475         else
476                 layoutfont = getLayoutFont(cur.buffer(), pit);
477
478         // Update current font
479         real_current_font.update(font,
480                                         cur.buffer().params().language,
481                                         toggleall);
482
483         // Reduce to implicit settings
484         current_font = real_current_font;
485         current_font.reduce(layoutfont);
486         // And resolve it completely
487         real_current_font.realize(layoutfont);
488
489         // if there is no selection that's all we need to do
490         if (!cur.selection())
491                 return;
492
493         // Ok, we have a selection.
494         recordUndoSelection(cur);
495
496         setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
497                 toggleall);
498 }
499
500
501 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
502                 DocIterator const & end, Font const & font,
503                 bool toggleall)
504 {
505         // Don't use forwardChar here as ditend might have
506         // pos() == lastpos() and forwardChar would miss it.
507         // Can't use forwardPos either as this descends into
508         // nested insets.
509         Language const * language = buffer.params().language;
510         for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
511                 if (dit.pos() != dit.lastpos()) {
512                         pit_type const pit = dit.pit();
513                         pos_type const pos = dit.pos();
514                         if (pars_[pit].isInset(pos) &&
515                             pars_[pit].getInset(pos)->noFontChange())
516                                 // We need to propagate the font change to all
517                                 // text cells of the inset (bug 1973).
518                                 // FIXME: This should change, see documentation
519                                 // of noFontChange in Inset.h
520                                 setInsetFont(buffer, pit, pos, font, toggleall);
521                         Font f = getFont(buffer, dit.paragraph(), pos);
522                         f.update(font, language, toggleall);
523                         setCharFont(buffer, pit, pos, f);
524                 }
525         }
526 }
527
528
529 // the cursor set functions have a special mechanism. When they
530 // realize you left an empty paragraph, they will delete it.
531
532 bool Text::cursorHome(Cursor & cur)
533 {
534         BOOST_ASSERT(this == cur.text());
535         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
536         Row const & row = pm.getRow(cur.pos(),cur.boundary());
537         return setCursor(cur, cur.pit(), row.pos());
538 }
539
540
541 bool Text::cursorEnd(Cursor & cur)
542 {
543         BOOST_ASSERT(this == cur.text());
544         // if not on the last row of the par, put the cursor before
545         // the final space exept if I have a spanning inset or one string
546         // is so long that we force a break.
547         pos_type end = cur.textRow().endpos();
548         if (end == 0)
549                 // empty text, end-1 is no valid position
550                 return false;
551         bool boundary = false;
552         if (end != cur.lastpos()) {
553                 if (!cur.paragraph().isLineSeparator(end-1)
554                     && !cur.paragraph().isNewline(end-1))
555                         boundary = true;
556                 else
557                         --end;
558         }
559         return setCursor(cur, cur.pit(), end, true, boundary);
560 }
561
562
563 bool Text::cursorTop(Cursor & cur)
564 {
565         BOOST_ASSERT(this == cur.text());
566         return setCursor(cur, 0, 0);
567 }
568
569
570 bool Text::cursorBottom(Cursor & cur)
571 {
572         BOOST_ASSERT(this == cur.text());
573         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
574 }
575
576
577 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
578 {
579         BOOST_ASSERT(this == cur.text());
580         // If the mask is completely neutral, tell user
581         if (font == Font(Font::ALL_IGNORE)) {
582                 // Could only happen with user style
583                 cur.message(_("No font change defined."));
584                 return;
585         }
586
587         // Try implicit word selection
588         // If there is a change in the language the implicit word selection
589         // is disabled.
590         CursorSlice resetCursor = cur.top();
591         bool implicitSelection =
592                 font.language() == ignore_language
593                 && font.number() == Font::IGNORE
594                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
595
596         // Set font
597         setFont(cur, font, toggleall);
598
599         // Implicit selections are cleared afterwards
600         // and cursor is set to the original position.
601         if (implicitSelection) {
602                 cur.clearSelection();
603                 cur.top() = resetCursor;
604                 cur.resetAnchor();
605         }
606 }
607
608
609 docstring Text::getStringToIndex(Cursor const & cur)
610 {
611         BOOST_ASSERT(this == cur.text());
612
613         docstring idxstring;
614         if (cur.selection())
615                 idxstring = cur.selectionAsString(false);
616         else {
617                 // Try implicit word selection. If there is a change
618                 // in the language the implicit word selection is
619                 // disabled.
620                 Cursor tmpcur = cur;
621                 selectWord(tmpcur, PREVIOUS_WORD);
622
623                 if (!tmpcur.selection())
624                         cur.message(_("Nothing to index!"));
625                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
626                         cur.message(_("Cannot index more than one paragraph!"));
627                 else
628                         idxstring = tmpcur.selectionAsString(false);
629         }
630
631         return idxstring;
632 }
633
634
635 void Text::setParagraph(Cursor & cur,
636                            Spacing const & spacing, LyXAlignment align,
637                            docstring const & labelwidthstring, bool noindent)
638 {
639         BOOST_ASSERT(cur.text());
640         // make sure that the depth behind the selection are restored, too
641         pit_type undopit = undoSpan(cur.selEnd().pit());
642         recUndo(cur, cur.selBegin().pit(), undopit - 1);
643
644         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
645              pit <= end; ++pit) {
646                 Paragraph & par = pars_[pit];
647                 ParagraphParameters & params = par.params();
648                 params.spacing(spacing);
649
650                 // does the layout allow the new alignment?
651                 Layout_ptr const & layout = par.layout();
652
653                 if (align == LYX_ALIGN_LAYOUT)
654                         align = layout->align;
655                 if (align & layout->alignpossible) {
656                         if (align == layout->align)
657                                 params.align(LYX_ALIGN_LAYOUT);
658                         else
659                                 params.align(align);
660                 }
661                 par.setLabelWidthString(labelwidthstring);
662                 params.noindent(noindent);
663         }
664 }
665
666
667 // this really should just insert the inset and not move the cursor.
668 void Text::insertInset(Cursor & cur, Inset * inset)
669 {
670         BOOST_ASSERT(this == cur.text());
671         BOOST_ASSERT(inset);
672         cur.paragraph().insertInset(cur.pos(), inset,
673                                     Change(cur.buffer().params().trackChanges ?
674                                            Change::INSERTED : Change::UNCHANGED));
675 }
676
677
678 // needed to insert the selection
679 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
680 {
681         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
682                                          current_font, str, autoBreakRows_);
683 }
684
685
686 // turn double CR to single CR, others are converted into one
687 // blank. Then insertStringAsLines is called
688 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
689 {
690         docstring linestr = str;
691         bool newline_inserted = false;
692
693         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
694                 if (linestr[i] == '\n') {
695                         if (newline_inserted) {
696                                 // we know that \r will be ignored by
697                                 // insertStringAsLines. Of course, it is a dirty
698                                 // trick, but it works...
699                                 linestr[i - 1] = '\r';
700                                 linestr[i] = '\n';
701                         } else {
702                                 linestr[i] = ' ';
703                                 newline_inserted = true;
704                         }
705                 } else if (isPrintable(linestr[i])) {
706                         newline_inserted = false;
707                 }
708         }
709         insertStringAsLines(cur, linestr);
710 }
711
712
713 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
714                         bool setfont, bool boundary)
715 {
716         Cursor old = cur;
717         setCursorIntern(cur, par, pos, setfont, boundary);
718         return cur.bv().checkDepm(cur, old);
719 }
720
721
722 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
723 {
724         BOOST_ASSERT(par != int(paragraphs().size()));
725         cur.pit() = par;
726         cur.pos() = pos;
727
728         // now some strict checking
729         Paragraph & para = getPar(par);
730
731         // None of these should happen, but we're scaredy-cats
732         if (pos < 0) {
733                 lyxerr << "dont like -1" << endl;
734                 BOOST_ASSERT(false);
735         }
736
737         if (pos > para.size()) {
738                 lyxerr << "dont like 1, pos: " << pos
739                        << " size: " << para.size()
740                        << " par: " << par << endl;
741                 BOOST_ASSERT(false);
742         }
743 }
744
745
746 void Text::setCursorIntern(Cursor & cur,
747                               pit_type par, pos_type pos, bool setfont, bool boundary)
748 {
749         BOOST_ASSERT(this == cur.text());
750         cur.boundary(boundary);
751         setCursor(cur.top(), par, pos);
752         if (setfont)
753                 setCurrentFont(cur);
754 }
755
756
757 void Text::setCurrentFont(Cursor & cur)
758 {
759         BOOST_ASSERT(this == cur.text());
760         pos_type pos = cur.pos();
761         Paragraph & par = cur.paragraph();
762
763         // ignore empty paragraph
764         if (par.empty())
765                 return;
766         
767         // if on boundary or at paragraph end, set font of previous char
768         if ((pos > 0 && cur.boundary()) || pos == cur.lastpos())
769                 --pos;
770         
771         // we changed the line and the bidi tables are outdated?
772         if (!bidi.inRange(pos))
773                 bidi.computeTables(par, cur.buffer(), cur.textRow());
774
775         // now in range?
776         if (!bidi.inRange(pos))
777                 return;
778
779         if (pos > 0) {
780                 if (pos == cur.lastpos())
781                         --pos;
782                 else // potentional bug... BUG (Lgb)
783                         if (par.isSeparator(pos)) {
784                                 if (pos > cur.textRow().pos() &&
785                                     bidi.level(pos) % 2 ==
786                                     bidi.level(pos - 1) % 2)
787                                         --pos;
788                                 else if (pos + 1 < cur.lastpos())
789                                         ++pos;
790                         }
791         }
792
793         BufferParams const & bufparams = cur.buffer().params();
794         current_font = par.getFontSettings(bufparams, pos);
795         real_current_font = getFont(cur.buffer(), par, pos);
796
797         if (cur.pos() == cur.lastpos()
798             && bidi.isBoundary(cur.buffer(), par, cur.pos())
799             && !cur.boundary()) {
800                 Language const * lang = par.getParLanguage(bufparams);
801                 current_font.setLanguage(lang);
802                 current_font.setNumber(Font::OFF);
803                 real_current_font.setLanguage(lang);
804                 real_current_font.setNumber(Font::OFF);
805         }
806 }
807
808 // y is screen coordinate
809 pit_type Text::getPitNearY(BufferView & bv, int y) const
810 {
811         BOOST_ASSERT(!paragraphs().empty());
812         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
813         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
814         LYXERR(Debug::DEBUG)
815                 << BOOST_CURRENT_FUNCTION
816                 << ": y: " << y << " cache size: " << cc.size()
817                 << endl;
818
819         // look for highest numbered paragraph with y coordinate less than given y
820         pit_type pit = 0;
821         int yy = -1;
822         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
823         CoordCache::InnerParPosCache::const_iterator et = cc.end();
824         CoordCache::InnerParPosCache::const_iterator last = et; last--;
825
826         TextMetrics & tm = bv.textMetrics(this);
827         ParagraphMetrics const & pm = tm.parMetrics(it->first);
828
829         // If we are off-screen (before the visible part)
830         if (y < 0
831                 // and even before the first paragraph in the cache.
832                 && y < it->second.y_ - int(pm.ascent())) {
833                 //  and we are not at the first paragraph in the inset.
834                 if (it->first == 0)
835                         return 0;
836                 // then this is the paragraph we are looking for.
837                 pit = it->first - 1;
838                 // rebreak it and update the CoordCache.
839                 tm.redoParagraph(pit);
840                 bv.coordCache().parPos()[this][pit] =
841                         Point(0, it->second.y_ - pm.descent());
842                 return pit;
843         }
844
845         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
846
847         // If we are off-screen (after the visible part)
848         if (y > bv.workHeight()
849                 // and even after the first paragraph in the cache.
850                 && y >= last->second.y_ + int(pm_last.descent())) {
851                 pit = last->first + 1;
852                 //  and we are not at the last paragraph in the inset.
853                 if (pit == int(pars_.size()))
854                         return last->first;
855                 // then this is the paragraph we are looking for.
856                 // rebreak it and update the CoordCache.
857                 tm.redoParagraph(pit);
858                 bv.coordCache().parPos()[this][pit] =
859                         Point(0, last->second.y_ + pm_last.ascent());
860                 return pit;
861         }
862
863         for (; it != et; ++it) {
864                 LYXERR(Debug::DEBUG)
865                         << BOOST_CURRENT_FUNCTION
866                         << "  examining: pit: " << it->first
867                         << " y: " << it->second.y_
868                         << endl;
869
870                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
871
872                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
873                         pit = it->first;
874                         yy = it->second.y_;
875                 }
876         }
877
878         LYXERR(Debug::DEBUG)
879                 << BOOST_CURRENT_FUNCTION
880                 << ": found best y: " << yy << " for pit: " << pit
881                 << endl;
882
883         return pit;
884 }
885
886
887 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
888 {
889         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
890
891         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
892         BOOST_ASSERT(!pm.rows().empty());
893         RowList::const_iterator rit = pm.rows().begin();
894         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
895         for (; rit != rlast; yy += rit->height(), ++rit)
896                 if (yy + rit->height() > y)
897                         break;
898         return *rit;
899 }
900
901
902 // x,y are absolute screen coordinates
903 // sets cursor recursively descending into nested editable insets
904 Inset * Text::editXY(Cursor & cur, int x, int y)
905 {
906         if (lyxerr.debugging(Debug::WORKAREA)) {
907                 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
908                 cur.bv().coordCache().dump();
909         }
910         pit_type pit = getPitNearY(cur.bv(), y);
911         BOOST_ASSERT(pit != -1);
912
913         Row const & row = getRowNearY(cur.bv(), y, pit);
914         bool bound = false;
915
916         TextMetrics const & tm = cur.bv().textMetrics(this);
917         int xx = x; // is modified by getColumnNearX
918         pos_type const pos = row.pos()
919                 + tm.getColumnNearX(pit, row, xx, bound);
920         cur.pit() = pit;
921         cur.pos() = pos;
922         cur.boundary(bound);
923         cur.setTargetX(x);
924
925         // try to descend into nested insets
926         Inset * inset = checkInsetHit(cur.bv(), x, y);
927         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
928         if (!inset) {
929                 // Either we deconst editXY or better we move current_font
930                 // and real_current_font to Cursor
931                 setCurrentFont(cur);
932                 return 0;
933         }
934
935         Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
936         //Inset * insetBehind = pars_[pit].getInset(pos);
937
938         // This should be just before or just behind the
939         // cursor position set above.
940         BOOST_ASSERT((pos != 0 && inset == insetBefore)
941                 || inset == pars_[pit].getInset(pos));
942
943         // Make sure the cursor points to the position before
944         // this inset.
945         if (inset == insetBefore) {
946                 --cur.pos();
947                 cur.boundary(false);
948         }
949
950         // Try to descend recursively inside the inset.
951         inset = inset->editXY(cur, x, y);
952
953         if (cur.top().text() == this)
954                 setCurrentFont(cur);
955         return inset;
956 }
957
958
959 bool Text::checkAndActivateInset(Cursor & cur, bool front)
960 {
961         if (cur.selection())
962                 return false;
963         if (front && cur.pos() == cur.lastpos())
964                 return false;
965         if (!front && cur.pos() == 0)
966                 return false;
967         Inset * inset = front ? cur.nextInset() : cur.prevInset();
968         if (!isHighlyEditableInset(inset))
969                 return false;
970         /*
971          * Apparently, when entering an inset we are expected to be positioned
972          * *before* it in the containing paragraph, regardless of the direction
973          * from which we are entering. Otherwise, cursor placement goes awry,
974          * and when we exit from the beginning, we'll be placed *after* the
975          * inset.
976          */
977         if (!front)
978                 --cur.pos();
979         inset->edit(cur, front);
980         return true;
981 }
982
983
984 bool Text::cursorLeft(Cursor & cur)
985 {
986         // Tell BufferView to test for FitCursor in any case!
987         cur.updateFlags(Update::FitCursor);
988
989         // not at paragraph start?
990         if (cur.pos() > 0) {
991                 // if on right side of boundary (i.e. not at paragraph end, but line end)
992                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
993                 // there are some exceptions to ignore this: lineseps, newlines, spaces
994 #if 0
995                 // some effectless debug code to see the values in the debugger
996                 bool bound = cur.boundary();
997                 int rowpos = cur.textRow().pos();
998                 int pos = cur.pos();
999                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
1000                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
1001                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
1002 #endif
1003                 if (!cur.boundary() &&
1004                                 cur.textRow().pos() == cur.pos() &&
1005                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
1006                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
1007                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
1008                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
1009                 }
1010                 
1011                 // go left and try to enter inset
1012                 if (checkAndActivateInset(cur, false))
1013                         return false;
1014                 
1015                 // normal character left
1016                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1017         }
1018
1019         // move to the previous paragraph or do nothing
1020         if (cur.pit() > 0)
1021                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1022         return false;
1023 }
1024
1025
1026 bool Text::cursorRight(Cursor & cur)
1027 {
1028         // Tell BufferView to test for FitCursor in any case!
1029         cur.updateFlags(Update::FitCursor);
1030
1031         // not at paragraph end?
1032         if (cur.pos() != cur.lastpos()) {
1033                 // in front of editable inset, i.e. jump into it?
1034                 if (checkAndActivateInset(cur, true))
1035                         return false;
1036
1037                 // if left of boundary -> just jump to right side
1038           // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1039           if (cur.boundary() && 
1040                                 !bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos()))
1041                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
1042
1043                 // next position is left of boundary, 
1044                 // but go to next line for special cases like space, newline, linesep
1045 #if 0
1046                 // some effectless debug code to see the values in the debugger
1047                 int endpos = cur.textRow().endpos();
1048                 int lastpos = cur.lastpos();
1049                 int pos = cur.pos();
1050                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1051                 bool newline = cur.paragraph().isNewline(cur.pos());
1052                 bool sep = cur.paragraph().isSeparator(cur.pos());
1053                 if (cur.pos() != cur.lastpos()) {
1054                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1055                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1056                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1057                 }
1058 #endif
1059                 if (cur.textRow().endpos() == cur.pos() + 1 &&
1060                     cur.textRow().endpos() != cur.lastpos() &&
1061                                 !cur.paragraph().isNewline(cur.pos()) &&
1062                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
1063                                 !cur.paragraph().isSeparator(cur.pos())) {
1064                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1065                 }
1066                 
1067                 // in front of RTL boundary? Stay on this side of the boundary because:
1068                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
1069                 if (bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1070                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1071                 
1072                 // move right
1073                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1074         }
1075
1076         // move to next paragraph
1077         if (cur.pit() != cur.lastpit())
1078                 return setCursor(cur, cur.pit() + 1, 0);
1079         return false;
1080 }
1081
1082
1083 bool Text::cursorUpParagraph(Cursor & cur)
1084 {
1085         bool updated = false;
1086         if (cur.pos() > 0)
1087                 updated = setCursor(cur, cur.pit(), 0);
1088         else if (cur.pit() != 0)
1089                 updated = setCursor(cur, cur.pit() - 1, 0);
1090         return updated;
1091 }
1092
1093
1094 bool Text::cursorDownParagraph(Cursor & cur)
1095 {
1096         bool updated = false;
1097         if (cur.pit() != cur.lastpit())
1098                 updated = setCursor(cur, cur.pit() + 1, 0);
1099         else
1100                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1101         return updated;
1102 }
1103
1104
1105 // fix the cursor `cur' after a characters has been deleted at `where'
1106 // position. Called by deleteEmptyParagraphMechanism
1107 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1108 {
1109         // Do nothing if cursor is not in the paragraph where the
1110         // deletion occured,
1111         if (cur.pit() != where.pit())
1112                 return;
1113
1114         // If cursor position is after the deletion place update it
1115         if (cur.pos() > where.pos())
1116                 --cur.pos();
1117
1118         // Check also if we don't want to set the cursor on a spot behind the
1119         // pagragraph because we erased the last character.
1120         if (cur.pos() > cur.lastpos())
1121                 cur.pos() = cur.lastpos();
1122 }
1123
1124
1125 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1126                 Cursor & old, bool & need_anchor_change)
1127 {
1128         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1129
1130         Paragraph & oldpar = old.paragraph();
1131
1132         // We allow all kinds of "mumbo-jumbo" when freespacing.
1133         if (oldpar.isFreeSpacing())
1134                 return false;
1135
1136         /* Ok I'll put some comments here about what is missing.
1137            There are still some small problems that can lead to
1138            double spaces stored in the document file or space at
1139            the beginning of paragraphs(). This happens if you have
1140            the cursor between to spaces and then save. Or if you
1141            cut and paste and the selection have a space at the
1142            beginning and then save right after the paste. (Lgb)
1143         */
1144
1145         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1146         // delete the LineSeparator.
1147         // MISSING
1148
1149         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1150         // delete the LineSeparator.
1151         // MISSING
1152
1153         bool const same_inset = &old.inset() == &cur.inset();
1154         bool const same_par = same_inset && old.pit() == cur.pit();
1155         bool const same_par_pos = same_par && old.pos() == cur.pos();
1156
1157         // If the chars around the old cursor were spaces, delete one of them.
1158         if (!same_par_pos) {
1159                 // Only if the cursor has really moved.
1160                 if (old.pos() > 0
1161                     && old.pos() < oldpar.size()
1162                     && oldpar.isLineSeparator(old.pos())
1163                     && oldpar.isLineSeparator(old.pos() - 1)
1164                     && !oldpar.isDeleted(old.pos() - 1)) {
1165                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1166 #ifdef WITH_WARNINGS
1167 #warning This will not work anymore when we have multiple views of the same buffer
1168 // In this case, we will have to correct also the cursors held by
1169 // other bufferviews. It will probably be easier to do that in a more
1170 // automated way in CursorSlice code. (JMarc 26/09/2001)
1171 #endif
1172                         // correct all cursor parts
1173                         if (same_par) {
1174                                 fixCursorAfterDelete(cur.top(), old.top());
1175                                 need_anchor_change = true;
1176                         }
1177                         return true;
1178                 }
1179         }
1180
1181         // only do our magic if we changed paragraph
1182         if (same_par)
1183                 return false;
1184
1185         // don't delete anything if this is the ONLY paragraph!
1186         if (old.lastpit() == 0)
1187                 return false;
1188
1189         // Do not delete empty paragraphs with keepempty set.
1190         if (oldpar.allowEmpty())
1191                 return false;
1192
1193         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1194                 // Delete old par.
1195                 recordUndo(old, Undo::ATOMIC,
1196                            max(old.pit() - 1, pit_type(0)),
1197                            min(old.pit() + 1, old.lastpit()));
1198                 ParagraphList & plist = old.text()->paragraphs();
1199                 plist.erase(boost::next(plist.begin(), old.pit()));
1200
1201                 // see #warning above
1202                 if (cur.depth() >= old.depth()) {
1203                         CursorSlice & curslice = cur[old.depth() - 1];
1204                         if (&curslice.inset() == &old.inset()
1205                             && curslice.pit() > old.pit()) {
1206                                 --curslice.pit();
1207                                 // since a paragraph has been deleted, all the
1208                                 // insets after `old' have been copied and
1209                                 // their address has changed. Therefore we
1210                                 // need to `regenerate' cur. (JMarc)
1211                                 cur.updateInsets(&(cur.bottom().inset()));
1212                                 need_anchor_change = true;
1213                         }
1214                 }
1215                 return true;
1216         }
1217
1218         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1219                 need_anchor_change = true;
1220                 // We return true here because the Paragraph contents changed and
1221                 // we need a redraw before further action is processed.
1222                 return true;
1223         }
1224
1225         return false;
1226 }
1227
1228
1229 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1230 {
1231         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1232
1233         for (pit_type pit = first; pit <= last; ++pit) {
1234                 Paragraph & par = pars_[pit];
1235
1236                 // We allow all kinds of "mumbo-jumbo" when freespacing.
1237                 if (par.isFreeSpacing())
1238                         continue;
1239
1240                 for (pos_type pos = 1; pos < par.size(); ++pos) {
1241                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1242                             && !par.isDeleted(pos - 1)) {
1243                                 if (par.eraseChar(pos - 1, trackChanges)) {
1244                                         --pos;
1245                                 }
1246                         }
1247                 }
1248
1249                 // don't delete anything if this is the only remaining paragraph within the given range
1250                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1251                 if (first == last)
1252                         continue;
1253
1254                 // don't delete empty paragraphs with keepempty set
1255                 if (par.allowEmpty())
1256                         continue;
1257
1258                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1259                         pars_.erase(boost::next(pars_.begin(), pit));
1260                         --pit;
1261                         --last;
1262                         continue;
1263                 }
1264
1265                 par.stripLeadingSpaces(trackChanges);
1266         }
1267 }
1268
1269
1270 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1271 {
1272         recordUndo(cur, Undo::ATOMIC, first, last);
1273 }
1274
1275
1276 void Text::recUndo(Cursor & cur, pit_type par) const
1277 {
1278         recordUndo(cur, Undo::ATOMIC, par, par);
1279 }
1280
1281 } // namespace lyx