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