]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
* do not lookup the same macro all the time
[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         BOOST_ASSERT(!pars_[pit].isInset(pos) ||
274                      !pars_[pit].getInset(pos)->noFontChange());
275
276         Font font = fnt;
277         Layout_ptr const & layout = pars_[pit].layout();
278
279         // Get concrete layout font to reduce against
280         Font layoutfont;
281
282         if (pos < pars_[pit].beginOfBody())
283                 layoutfont = layout->labelfont;
284         else
285                 layoutfont = layout->font;
286
287         // Realize against environment font information
288         if (pars_[pit].getDepth()) {
289                 pit_type tp = pit;
290                 while (!layoutfont.resolved() &&
291                        tp != pit_type(paragraphs().size()) &&
292                        pars_[tp].getDepth()) {
293                         tp = outerHook(tp, paragraphs());
294                         if (tp != pit_type(paragraphs().size()))
295                                 layoutfont.realize(pars_[tp].layout()->font);
296                 }
297         }
298
299         // Inside inset, apply the inset's font attributes if any
300         // (charstyle!)
301         if (!isMainText(buffer))
302                 layoutfont.realize(font_);
303
304         layoutfont.realize(buffer.params().getFont());
305
306         // Now, reduce font against full layout font
307         font.reduce(layoutfont);
308
309         pars_[pit].setFont(pos, font);
310 }
311
312
313 void Text::setInsetFont(Buffer const & buffer, pit_type pit,
314                 pos_type pos, Font const & font, bool toggleall)
315 {
316         BOOST_ASSERT(pars_[pit].isInset(pos) &&
317                      pars_[pit].getInset(pos)->noFontChange());
318
319         Inset * const inset = pars_[pit].getInset(pos);
320         DocIterator dit = doc_iterator_begin(*inset);
321         // start of the last cell
322         DocIterator end = dit;
323         end.idx() = end.lastidx();
324
325         while (true) {
326                 Text * text = dit.text();
327                 Inset * cell = dit.realInset();
328                 if (text && cell) {
329                         DocIterator cellbegin = doc_iterator_begin(*cell);
330                         // last position of the cell
331                         DocIterator cellend = cellbegin;
332                         cellend.pit() = cellend.lastpit();
333                         cellend.pos() = cellend.lastpos();
334                         text->setFont(buffer, cellbegin, cellend, font, toggleall);
335                 }
336                 if (dit == end)
337                         break;
338                 dit.forwardIdx();
339         }
340 }
341
342
343 // return past-the-last paragraph influenced by a layout change on pit
344 pit_type Text::undoSpan(pit_type pit)
345 {
346         pit_type end = paragraphs().size();
347         pit_type nextpit = pit + 1;
348         if (nextpit == end)
349                 return nextpit;
350         //because of parindents
351         if (!pars_[pit].getDepth())
352                 return boost::next(nextpit);
353         //because of depth constrains
354         for (; nextpit != end; ++pit, ++nextpit) {
355                 if (!pars_[pit].getDepth())
356                         break;
357         }
358         return nextpit;
359 }
360
361
362 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
363                 string const & layout)
364 {
365         BOOST_ASSERT(start != end);
366
367         BufferParams const & bufparams = buffer.params();
368         Layout_ptr const & lyxlayout = bufparams.getTextClass()[layout];
369
370         for (pit_type pit = start; pit != end; ++pit) {
371                 Paragraph & par = pars_[pit];
372                 par.applyLayout(lyxlayout);
373                 if (lyxlayout->margintype == MARGIN_MANUAL)
374                         par.setLabelWidthString(par.translateIfPossible(
375                                 lyxlayout->labelstring(), buffer.params()));
376         }
377 }
378
379
380 // set layout over selection and make a total rebreak of those paragraphs
381 void Text::setLayout(Cursor & cur, string const & layout)
382 {
383         BOOST_ASSERT(this == cur.text());
384         // special handling of new environment insets
385         BufferView & bv = cur.bv();
386         BufferParams const & params = bv.buffer()->params();
387         Layout_ptr const & lyxlayout = params.getTextClass()[layout];
388         if (lyxlayout->is_environment) {
389                 // move everything in a new environment inset
390                 LYXERR(Debug::DEBUG) << "setting layout " << layout << endl;
391                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
392                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
393                 lyx::dispatch(FuncRequest(LFUN_CUT));
394                 Inset * inset = new InsetEnvironment(params, layout);
395                 insertInset(cur, inset);
396                 //inset->edit(cur, true);
397                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
398                 return;
399         }
400
401         pit_type start = cur.selBegin().pit();
402         pit_type end = cur.selEnd().pit() + 1;
403         pit_type undopit = undoSpan(end - 1);
404         recUndo(cur, start, undopit - 1);
405         setLayout(cur.buffer(), start, end, layout);
406         updateLabels(cur.buffer());
407 }
408
409
410 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
411                         Paragraph const & par, int max_depth)
412 {
413         if (par.layout()->labeltype == LABEL_BIBLIO)
414                 return false;
415         int const depth = par.params().depth();
416         if (type == Text::INC_DEPTH && depth < max_depth)
417                 return true;
418         if (type == Text::DEC_DEPTH && depth > 0)
419                 return true;
420         return false;
421 }
422
423
424 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
425 {
426         BOOST_ASSERT(this == cur.text());
427         // this happens when selecting several cells in tabular (bug 2630)
428         if (cur.selBegin().idx() != cur.selEnd().idx())
429                 return false;
430
431         pit_type const beg = cur.selBegin().pit();
432         pit_type const end = cur.selEnd().pit() + 1;
433         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
434
435         for (pit_type pit = beg; pit != end; ++pit) {
436                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
437                         return true;
438                 max_depth = pars_[pit].getMaxDepthAfter();
439         }
440         return false;
441 }
442
443
444 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
445 {
446         BOOST_ASSERT(this == cur.text());
447         pit_type const beg = cur.selBegin().pit();
448         pit_type const end = cur.selEnd().pit() + 1;
449         recordUndoSelection(cur);
450         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
451
452         for (pit_type pit = beg; pit != end; ++pit) {
453                 Paragraph & par = pars_[pit];
454                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
455                         int const depth = par.params().depth();
456                         if (type == INC_DEPTH)
457                                 par.params().depth(depth + 1);
458                         else
459                                 par.params().depth(depth - 1);
460                 }
461                 max_depth = par.getMaxDepthAfter();
462         }
463         // this handles the counter labels, and also fixes up
464         // depth values for follow-on (child) paragraphs
465         updateLabels(cur.buffer());
466 }
467
468
469 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
470 {
471         BOOST_ASSERT(this == cur.text());
472         // Set the current_font
473         // Determine basis font
474         Font layoutfont;
475         pit_type pit = cur.pit();
476         if (cur.pos() < pars_[pit].beginOfBody())
477                 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
478         else
479                 layoutfont = getLayoutFont(cur.buffer(), pit);
480
481         // Update current font
482         real_current_font.update(font,
483                                         cur.buffer().params().language,
484                                         toggleall);
485
486         // Reduce to implicit settings
487         current_font = real_current_font;
488         current_font.reduce(layoutfont);
489         // And resolve it completely
490         real_current_font.realize(layoutfont);
491
492         // if there is no selection that's all we need to do 
493         if (!cur.selection())
494                 return;
495
496         // Ok, we have a selection.
497         recordUndoSelection(cur);
498
499         setFont(cur.buffer(), cur.selectionBegin(), cur.selectionEnd(), font,
500                 toggleall);
501 }
502
503
504 void Text::setFont(Buffer const & buffer, DocIterator const & begin,
505                 DocIterator const & end, Font const & font,
506                 bool toggleall)
507 {
508         // Don't use forwardChar here as ditend might have
509         // pos() == lastpos() and forwardChar would miss it.
510         // Can't use forwardPos either as this descends into
511         // nested insets.
512         Language const * language = buffer.params().language;
513         for (DocIterator dit = begin; dit != end; dit.forwardPosNoDescend()) {
514                 if (dit.pos() != dit.lastpos()) {
515                         pit_type const pit = dit.pit();
516                         pos_type const pos = dit.pos();
517                         if (pars_[pit].isInset(pos) &&
518                             pars_[pit].getInset(pos)->noFontChange())
519                                 // We need to propagate the font change to all
520                                 // text cells of the inset (bug 1973).
521                                 // FIXME: This should change, see documentation
522                                 // of noFontChange in insetbase.h
523                                 setInsetFont(buffer, pit, pos, font, toggleall);
524                         else {
525                                 Font f = getFont(buffer, dit.paragraph(), pos);
526                                 f.update(font, language, toggleall);
527                                 setCharFont(buffer, pit, pos, f);
528                         }
529                 }
530         }
531 }
532
533
534 // the cursor set functions have a special mechanism. When they
535 // realize you left an empty paragraph, they will delete it.
536
537 bool Text::cursorHome(Cursor & cur)
538 {
539         BOOST_ASSERT(this == cur.text());
540         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
541         Row const & row = pm.getRow(cur.pos(),cur.boundary());
542         return setCursor(cur, cur.pit(), row.pos());
543 }
544
545
546 bool Text::cursorEnd(Cursor & cur)
547 {
548         BOOST_ASSERT(this == cur.text());
549         // if not on the last row of the par, put the cursor before
550         // the final space exept if I have a spanning inset or one string
551         // is so long that we force a break.
552         pos_type end = cur.textRow().endpos();
553         if (end == 0)
554                 // empty text, end-1 is no valid position
555                 return false;
556         bool boundary = false;
557         if (end != cur.lastpos()) {
558                 if (!cur.paragraph().isLineSeparator(end-1)
559                     && !cur.paragraph().isNewline(end-1))
560                         boundary = true;
561                 else
562                         --end;
563         }
564         return setCursor(cur, cur.pit(), end, true, boundary);
565 }
566
567
568 bool Text::cursorTop(Cursor & cur)
569 {
570         BOOST_ASSERT(this == cur.text());
571         return setCursor(cur, 0, 0);
572 }
573
574
575 bool Text::cursorBottom(Cursor & cur)
576 {
577         BOOST_ASSERT(this == cur.text());
578         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
579 }
580
581
582 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
583 {
584         BOOST_ASSERT(this == cur.text());
585         // If the mask is completely neutral, tell user
586         if (font == Font(Font::ALL_IGNORE)) {
587                 // Could only happen with user style
588                 cur.message(_("No font change defined."));
589                 return;
590         }
591
592         // Try implicit word selection
593         // If there is a change in the language the implicit word selection
594         // is disabled.
595         CursorSlice resetCursor = cur.top();
596         bool implicitSelection =
597                 font.language() == ignore_language
598                 && font.number() == Font::IGNORE
599                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
600
601         // Set font
602         setFont(cur, font, toggleall);
603
604         // Implicit selections are cleared afterwards
605         // and cursor is set to the original position.
606         if (implicitSelection) {
607                 cur.clearSelection();
608                 cur.top() = resetCursor;
609                 cur.resetAnchor();
610         }
611 }
612
613
614 docstring Text::getStringToIndex(Cursor const & cur)
615 {
616         BOOST_ASSERT(this == cur.text());
617
618         docstring idxstring;
619         if (cur.selection())
620                 idxstring = cur.selectionAsString(false);
621         else {
622                 // Try implicit word selection. If there is a change
623                 // in the language the implicit word selection is
624                 // disabled.
625                 Cursor tmpcur = cur;
626                 selectWord(tmpcur, PREVIOUS_WORD);
627
628                 if (!tmpcur.selection())
629                         cur.message(_("Nothing to index!"));
630                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
631                         cur.message(_("Cannot index more than one paragraph!"));
632                 else
633                         idxstring = tmpcur.selectionAsString(false);
634         }
635
636         return idxstring;
637 }
638
639
640 void Text::setParagraph(Cursor & cur,
641                            Spacing const & spacing, LyXAlignment align,
642                            docstring const & labelwidthstring, bool noindent)
643 {
644         BOOST_ASSERT(cur.text());
645         // make sure that the depth behind the selection are restored, too
646         pit_type undopit = undoSpan(cur.selEnd().pit());
647         recUndo(cur, cur.selBegin().pit(), undopit - 1);
648
649         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
650              pit <= end; ++pit) {
651                 Paragraph & par = pars_[pit];
652                 ParagraphParameters & params = par.params();
653                 params.spacing(spacing);
654
655                 // does the layout allow the new alignment?
656                 Layout_ptr const & layout = par.layout();
657
658                 if (align == LYX_ALIGN_LAYOUT)
659                         align = layout->align;
660                 if (align & layout->alignpossible) {
661                         if (align == layout->align)
662                                 params.align(LYX_ALIGN_LAYOUT);
663                         else
664                                 params.align(align);
665                 }
666                 par.setLabelWidthString(labelwidthstring);
667                 params.noindent(noindent);
668         }
669 }
670
671
672 // this really should just insert the inset and not move the cursor.
673 void Text::insertInset(Cursor & cur, Inset * inset)
674 {
675         BOOST_ASSERT(this == cur.text());
676         BOOST_ASSERT(inset);
677         cur.paragraph().insertInset(cur.pos(), inset, 
678                                     Change(cur.buffer().params().trackChanges ?
679                                            Change::INSERTED : Change::UNCHANGED));
680 }
681
682
683 // needed to insert the selection
684 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
685 {
686         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
687                                          current_font, str, autoBreakRows_);
688 }
689
690
691 // turn double CR to single CR, others are converted into one
692 // blank. Then insertStringAsLines is called
693 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
694 {
695         docstring linestr = str;
696         bool newline_inserted = false;
697
698         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
699                 if (linestr[i] == '\n') {
700                         if (newline_inserted) {
701                                 // we know that \r will be ignored by
702                                 // insertStringAsLines. Of course, it is a dirty
703                                 // trick, but it works...
704                                 linestr[i - 1] = '\r';
705                                 linestr[i] = '\n';
706                         } else {
707                                 linestr[i] = ' ';
708                                 newline_inserted = true;
709                         }
710                 } else if (isPrintable(linestr[i])) {
711                         newline_inserted = false;
712                 }
713         }
714         insertStringAsLines(cur, linestr);
715 }
716
717
718 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
719                         bool setfont, bool boundary)
720 {
721         Cursor old = cur;
722         setCursorIntern(cur, par, pos, setfont, boundary);
723         return cur.bv().checkDepm(cur, old);
724 }
725
726
727 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
728 {
729         BOOST_ASSERT(par != int(paragraphs().size()));
730         cur.pit() = par;
731         cur.pos() = pos;
732
733         // now some strict checking
734         Paragraph & para = getPar(par);
735
736         // None of these should happen, but we're scaredy-cats
737         if (pos < 0) {
738                 lyxerr << "dont like -1" << endl;
739                 BOOST_ASSERT(false);
740         }
741
742         if (pos > para.size()) {
743                 lyxerr << "dont like 1, pos: " << pos
744                        << " size: " << para.size()
745                        << " par: " << par << endl;
746                 BOOST_ASSERT(false);
747         }
748 }
749
750
751 void Text::setCursorIntern(Cursor & cur,
752                               pit_type par, pos_type pos, bool setfont, bool boundary)
753 {
754         BOOST_ASSERT(this == cur.text());
755         cur.boundary(boundary);
756         setCursor(cur.top(), par, pos);
757         if (setfont)
758                 setCurrentFont(cur);
759 }
760
761
762 void Text::setCurrentFont(Cursor & cur)
763 {
764         BOOST_ASSERT(this == cur.text());
765         pos_type pos = cur.pos();
766         Paragraph & par = cur.paragraph();
767
768         if (cur.boundary() && pos > 0 && pos < cur.lastpos()) {
769                 --pos;
770                 // We may have just moved to the previous row --- 
771                 // we're going to be needing its bidi tables!
772                 bidi.computeTables(par, cur.buffer(), cur.textRow());
773         }
774
775         if (pos > 0) {
776                 if (pos == cur.lastpos())
777                         --pos;
778                 else // potentional bug... BUG (Lgb)
779                         if (par.isSeparator(pos)) {
780                                 if (pos > cur.textRow().pos() &&
781                                     bidi.level(pos) % 2 ==
782                                     bidi.level(pos - 1) % 2)
783                                         --pos;
784                                 else if (pos + 1 < cur.lastpos())
785                                         ++pos;
786                         }
787         }
788
789         BufferParams const & bufparams = cur.buffer().params();
790         current_font = par.getFontSettings(bufparams, pos);
791         real_current_font = getFont(cur.buffer(), par, pos);
792
793         if (cur.pos() == cur.lastpos()
794             && bidi.isBoundary(cur.buffer(), par, cur.pos())
795             && !cur.boundary()) {
796                 Language const * lang = par.getParLanguage(bufparams);
797                 current_font.setLanguage(lang);
798                 current_font.setNumber(Font::OFF);
799                 real_current_font.setLanguage(lang);
800                 real_current_font.setNumber(Font::OFF);
801         }
802 }
803
804 // y is screen coordinate
805 pit_type Text::getPitNearY(BufferView & bv, int y) const
806 {
807         BOOST_ASSERT(!paragraphs().empty());
808         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
809         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
810         LYXERR(Debug::DEBUG)
811                 << BOOST_CURRENT_FUNCTION
812                 << ": y: " << y << " cache size: " << cc.size()
813                 << endl;
814
815         // look for highest numbered paragraph with y coordinate less than given y
816         pit_type pit = 0;
817         int yy = -1;
818         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
819         CoordCache::InnerParPosCache::const_iterator et = cc.end();
820         CoordCache::InnerParPosCache::const_iterator last = et; last--;
821
822         TextMetrics & tm = bv.textMetrics(this);
823         ParagraphMetrics const & pm = tm.parMetrics(it->first);
824
825         // If we are off-screen (before the visible part)
826         if (y < 0
827                 // and even before the first paragraph in the cache.
828                 && y < it->second.y_ - int(pm.ascent())) {
829                 //  and we are not at the first paragraph in the inset.
830                 if (it->first == 0)
831                         return 0;
832                 // then this is the paragraph we are looking for.
833                 pit = it->first - 1;
834                 // rebreak it and update the CoordCache.
835                 tm.redoParagraph(pit);
836                 bv.coordCache().parPos()[this][pit] =
837                         Point(0, it->second.y_ - pm.descent());
838                 return pit;
839         }
840
841         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
842
843         // If we are off-screen (after the visible part)
844         if (y > bv.workHeight()
845                 // and even after the first paragraph in the cache.
846                 && y >= last->second.y_ + int(pm_last.descent())) {
847                 pit = last->first + 1;
848                 //  and we are not at the last paragraph in the inset.
849                 if (pit == int(pars_.size()))
850                         return last->first;
851                 // then this is the paragraph we are looking for.
852                 // rebreak it and update the CoordCache.
853                 tm.redoParagraph(pit);
854                 bv.coordCache().parPos()[this][pit] =
855                         Point(0, last->second.y_ + pm_last.ascent());
856                 return pit;
857         }
858
859         for (; it != et; ++it) {
860                 LYXERR(Debug::DEBUG)
861                         << BOOST_CURRENT_FUNCTION
862                         << "  examining: pit: " << it->first
863                         << " y: " << it->second.y_
864                         << endl;
865
866                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
867
868                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
869                         pit = it->first;
870                         yy = it->second.y_;
871                 }
872         }
873
874         LYXERR(Debug::DEBUG)
875                 << BOOST_CURRENT_FUNCTION
876                 << ": found best y: " << yy << " for pit: " << pit
877                 << endl;
878
879         return pit;
880 }
881
882
883 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
884 {
885         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
886
887         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
888         BOOST_ASSERT(!pm.rows().empty());
889         RowList::const_iterator rit = pm.rows().begin();
890         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
891         for (; rit != rlast; yy += rit->height(), ++rit)
892                 if (yy + rit->height() > y)
893                         break;
894         return *rit;
895 }
896
897
898 // x,y are absolute screen coordinates
899 // sets cursor recursively descending into nested editable insets
900 Inset * Text::editXY(Cursor & cur, int x, int y)
901 {
902         if (lyxerr.debugging(Debug::WORKAREA)) {
903                 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
904                 cur.bv().coordCache().dump();
905         }
906         pit_type pit = getPitNearY(cur.bv(), y);
907         BOOST_ASSERT(pit != -1);
908
909         Row const & row = getRowNearY(cur.bv(), y, pit);
910         bool bound = false;
911
912         TextMetrics const & tm = cur.bv().textMetrics(this);
913         int xx = x; // is modified by getColumnNearX
914         pos_type const pos = row.pos()
915                 + tm.getColumnNearX(pit, row, xx, bound);
916         cur.pit() = pit;
917         cur.pos() = pos;
918         cur.boundary(bound);
919         cur.x_target() = x;
920
921         // try to descend into nested insets
922         Inset * inset = checkInsetHit(cur.bv(), x, y);
923         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
924         if (!inset) {
925                 // Either we deconst editXY or better we move current_font
926                 // and real_current_font to Cursor
927                 setCurrentFont(cur);
928                 return 0;
929         }
930
931         Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
932         //Inset * insetBehind = pars_[pit].getInset(pos);
933
934         // This should be just before or just behind the
935         // cursor position set above.
936         BOOST_ASSERT((pos != 0 && inset == insetBefore)
937                 || inset == pars_[pit].getInset(pos));
938
939         // Make sure the cursor points to the position before
940         // this inset.
941         if (inset == insetBefore)
942                 --cur.pos();
943
944         // Try to descend recursively inside the inset.
945         inset = inset->editXY(cur, x, y);
946
947         if (cur.top().text() == this)
948                 setCurrentFont(cur);
949         return inset;
950 }
951
952
953 bool Text::checkAndActivateInset(Cursor & cur, bool front)
954 {
955         if (cur.selection())
956                 return false;
957         if (cur.pos() == cur.lastpos())
958                 return false;
959         Inset * inset = front ? cur.nextInset() : cur.prevInset();
960         if (!isHighlyEditableInset(inset))
961                 return false;
962         /*
963          * Apparently, when entering an inset we are expected to be positioned
964          * *before* it in the containing paragraph, regardless of the direction
965          * from which we are entering. Otherwise, cursor placement goes awry,
966          * and when we exit from the beginning, we'll be placed *after* the
967          * inset.
968          */ 
969         if (!front)
970                 --cur.pos();
971         inset->edit(cur, front);
972         return true;
973 }
974
975
976 bool Text::cursorLeft(Cursor & cur)
977 {
978         // Tell BufferView to test for FitCursor in any case!
979         cur.updateFlags(Update::FitCursor);
980
981         if (cur.pos() > 0) {
982                 if (cur.boundary())
983                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
984
985                 bool updateNeeded = false;
986                 // If checkAndActivateInset returns true, that means that
987                 // the cursor was placed inside it, so we're done
988                 if (!checkAndActivateInset(cur, false)) {
989                         if (!cur.boundary() && 
990                             cur.textRow().pos() == cur.pos() 
991                             // FIXME: the following two conditions are copied
992                             // from cursorRight; however, isLineSeparator()
993                             // is definitely wrong here, isNewline I'm not sure
994                             // about. I'm leaving them as comments for now,
995                             // until we understand why they should or shouldn't
996                             // be here.
997                             /*&& 
998                             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
999                             !cur.paragraph().isNewline(cur.pos() - 1)*/) {
1000                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), 
1001                                                                                   true, true);
1002                         }
1003                         updateNeeded |= setCursor(cur, cur.pit(),cur.pos() - 1, 
1004                                                                           true, false);
1005                 }
1006                 return updateNeeded;
1007         }
1008
1009         if (cur.pit() > 0) {
1010                 // Steps into the paragraph above
1011                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1012         }
1013         return false;
1014 }
1015
1016
1017 bool Text::cursorRight(Cursor & cur)
1018 {
1019         // Tell BufferView to test for FitCursor in any case!
1020         cur.updateFlags(Update::FitCursor);
1021
1022         if (cur.pos() != cur.lastpos()) {
1023                 if (cur.boundary())
1024                         return setCursor(cur, cur.pit(), cur.pos(),
1025                                          true, false);
1026
1027                 bool updateNeeded = false;
1028                 // If checkAndActivateInset returns true, that means that 
1029                 // the cursor was placed inside it, so we're done
1030                 if (!checkAndActivateInset(cur, true)) {
1031                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1032                             cur.textRow().endpos() != cur.lastpos() &&
1033                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1034                             !cur.paragraph().isNewline(cur.pos())) {
1035                                 cur.boundary(true);
1036                         }
1037                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1038                 }
1039                 return updateNeeded;
1040         }
1041
1042         if (cur.pit() != cur.lastpit())
1043                 return setCursor(cur, cur.pit() + 1, 0);
1044         return false;
1045 }
1046
1047
1048 bool Text::cursorUp(Cursor & cur)
1049 {
1050         // Tell BufferView to test for FitCursor in any case!
1051         cur.updateFlags(Update::FitCursor);
1052
1053         TextMetrics const & tm = cur.bv().textMetrics(this);
1054         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1055
1056         int row;
1057         if (cur.pos() && cur.boundary())
1058                 row = pm.pos2row(cur.pos()-1);
1059         else
1060                 row = pm.pos2row(cur.pos());
1061
1062         int x = cur.targetX();
1063         cur.setTargetX();
1064         // We want to keep the x-target on subsequent up movements
1065         // that cross beyond the end of short lines. Thus a special
1066         // handling when the cursor is at the end of line: Use the new 
1067         // x-target only if the old one was before the end of line.
1068         if (cur.pos() != pm.rows()[row].endpos() 
1069                 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1070                 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1071
1072                 x = cur.targetX();
1073         }
1074
1075         if (!cur.selection()) {
1076                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1077                 Cursor old = cur;
1078                 // Go to middle of previous row. 16 found to work OK;
1079                 // 12 = top/bottom margin of display math
1080                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1081                 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1082                 cur.clearSelection();
1083
1084                 // This happens when you move out of an inset.
1085                 // And to give the DEPM the possibility of doing
1086                 // something we must provide it with two different
1087                 // cursors. (Lgb)
1088                 Cursor dummy = cur;
1089                 if (dummy == old)
1090                         ++dummy.pos();
1091
1092                 cur.bv().checkDepm(dummy, old);
1093                 return false;
1094         }
1095
1096         bool updateNeeded = false;
1097
1098         if (row > 0) {
1099                 updateNeeded |= setCursor(cur, cur.pit(),
1100                         tm.x2pos(cur.pit(), row - 1, x));
1101         } else if (cur.pit() > 0) {
1102                 --cur.pit();
1103                 //cannot use 'par' now
1104                 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1105                 updateNeeded |= setCursor(cur, cur.pit(),
1106                         tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1107         }
1108
1109         cur.x_target() = x;
1110
1111         return updateNeeded;
1112 }
1113
1114
1115 bool Text::cursorDown(Cursor & cur)
1116 {
1117         // Tell BufferView to test for FitCursor in any case!
1118         cur.updateFlags(Update::FitCursor);
1119
1120         TextMetrics const & tm = cur.bv().textMetrics(this);
1121         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1122
1123         int row;
1124         if (cur.pos() && cur.boundary())
1125                 row = pm.pos2row(cur.pos()-1);
1126         else
1127                 row = pm.pos2row(cur.pos());
1128
1129         int x = cur.targetX();
1130         cur.setTargetX();
1131         // We want to keep the x-target on subsequent down movements
1132         // that cross beyond the end of short lines. Thus a special
1133         // handling when the cursor is at the end of line: Use the new 
1134         // x-target only if the old one was before the end of line.
1135         if (cur.pos() != pm.rows()[row].endpos() 
1136                 || (!isWithinRtlParagraph(cur) && x < cur.targetX())
1137                 || (isWithinRtlParagraph(cur) && x > cur.targetX())) {
1138
1139                 x = cur.targetX();
1140         }
1141
1142         if (!cur.selection()) {
1143                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1144                 Cursor old = cur;
1145                 // To middle of next row
1146                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1147                 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1148                 cur.clearSelection();
1149
1150                 // This happens when you move out of an inset.
1151                 // And to give the DEPM the possibility of doing
1152                 // something we must provide it with two different
1153                 // cursors. (Lgb)
1154                 Cursor dummy = cur;
1155                 if (dummy == old)
1156                         ++dummy.pos();
1157                 
1158                 bool const changed = cur.bv().checkDepm(dummy, old);
1159
1160                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1161                 if (changed)
1162                         cur = dummy;
1163
1164                 return false;
1165         }
1166
1167         bool updateNeeded = false;
1168
1169         if (row + 1 < int(pm.rows().size())) {
1170                 updateNeeded |= setCursor(cur, cur.pit(),
1171                         tm.x2pos(cur.pit(), row + 1, x));
1172         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1173                 ++cur.pit();
1174                 updateNeeded |= setCursor(cur, cur.pit(),
1175                         tm.x2pos(cur.pit(), 0, x));
1176         }
1177
1178         cur.x_target() = x;
1179
1180         return updateNeeded;
1181 }
1182
1183
1184 bool Text::cursorUpParagraph(Cursor & cur)
1185 {
1186         bool updated = false;
1187         if (cur.pos() > 0)
1188                 updated = setCursor(cur, cur.pit(), 0);
1189         else if (cur.pit() != 0)
1190                 updated = setCursor(cur, cur.pit() - 1, 0);
1191         return updated;
1192 }
1193
1194
1195 bool Text::cursorDownParagraph(Cursor & cur)
1196 {
1197         bool updated = false;
1198         if (cur.pit() != cur.lastpit())
1199                 updated = setCursor(cur, cur.pit() + 1, 0);
1200         else
1201                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1202         return updated;
1203 }
1204
1205
1206 // fix the cursor `cur' after a characters has been deleted at `where'
1207 // position. Called by deleteEmptyParagraphMechanism
1208 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1209 {
1210         // Do nothing if cursor is not in the paragraph where the
1211         // deletion occured,
1212         if (cur.pit() != where.pit())
1213                 return;
1214
1215         // If cursor position is after the deletion place update it
1216         if (cur.pos() > where.pos())
1217                 --cur.pos();
1218
1219         // Check also if we don't want to set the cursor on a spot behind the
1220         // pagragraph because we erased the last character.
1221         if (cur.pos() > cur.lastpos())
1222                 cur.pos() = cur.lastpos();
1223 }
1224
1225
1226 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1227                 Cursor & old, bool & need_anchor_change)
1228 {
1229         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1230
1231         Paragraph & oldpar = old.paragraph();
1232
1233         // We allow all kinds of "mumbo-jumbo" when freespacing.
1234         if (oldpar.isFreeSpacing())
1235                 return false;
1236
1237         /* Ok I'll put some comments here about what is missing.
1238            There are still some small problems that can lead to
1239            double spaces stored in the document file or space at
1240            the beginning of paragraphs(). This happens if you have
1241            the cursor between to spaces and then save. Or if you
1242            cut and paste and the selection have a space at the
1243            beginning and then save right after the paste. (Lgb)
1244         */
1245
1246         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1247         // delete the LineSeparator.
1248         // MISSING
1249
1250         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1251         // delete the LineSeparator.
1252         // MISSING
1253
1254         bool const same_inset = &old.inset() == &cur.inset();
1255         bool const same_par = same_inset && old.pit() == cur.pit();
1256         bool const same_par_pos = same_par && old.pos() == cur.pos();
1257
1258         // If the chars around the old cursor were spaces, delete one of them.
1259         if (!same_par_pos) {
1260                 // Only if the cursor has really moved.
1261                 if (old.pos() > 0
1262                     && old.pos() < oldpar.size()
1263                     && oldpar.isLineSeparator(old.pos())
1264                     && oldpar.isLineSeparator(old.pos() - 1)
1265                     && !oldpar.isDeleted(old.pos() - 1)) {
1266                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1267 #ifdef WITH_WARNINGS
1268 #warning This will not work anymore when we have multiple views of the same buffer
1269 // In this case, we will have to correct also the cursors held by
1270 // other bufferviews. It will probably be easier to do that in a more
1271 // automated way in CursorSlice code. (JMarc 26/09/2001)
1272 #endif
1273                         // correct all cursor parts
1274                         if (same_par) {
1275                                 fixCursorAfterDelete(cur.top(), old.top());
1276                                 need_anchor_change = true;
1277                         }
1278                         return true;
1279                 }
1280         }
1281
1282         // only do our magic if we changed paragraph
1283         if (same_par)
1284                 return false;
1285
1286         // don't delete anything if this is the ONLY paragraph!
1287         if (old.lastpit() == 0)
1288                 return false;
1289
1290         // Do not delete empty paragraphs with keepempty set.
1291         if (oldpar.allowEmpty())
1292                 return false;
1293
1294         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1295                 // Delete old par.
1296                 recordUndo(old, Undo::ATOMIC,
1297                            max(old.pit() - 1, pit_type(0)),
1298                            min(old.pit() + 1, old.lastpit()));
1299                 ParagraphList & plist = old.text()->paragraphs();
1300                 plist.erase(boost::next(plist.begin(), old.pit()));
1301
1302                 // see #warning above
1303                 if (cur.depth() >= old.depth()) {
1304                         CursorSlice & curslice = cur[old.depth() - 1];
1305                         if (&curslice.inset() == &old.inset()
1306                             && curslice.pit() > old.pit()) {
1307                                 --curslice.pit();
1308                                 // since a paragraph has been deleted, all the
1309                                 // insets after `old' have been copied and
1310                                 // their address has changed. Therefore we
1311                                 // need to `regenerate' cur. (JMarc)
1312                                 cur.updateInsets(&(cur.bottom().inset()));
1313                                 need_anchor_change = true;
1314                         }
1315                 }
1316                 return true;
1317         }
1318
1319         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1320                 need_anchor_change = true;
1321                 // We return true here because the Paragraph contents changed and
1322                 // we need a redraw before further action is processed.
1323                 return true;
1324         }
1325
1326         return false;
1327 }
1328
1329
1330 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1331 {
1332         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1333
1334         for (pit_type pit = first; pit <= last; ++pit) {
1335                 Paragraph & par = pars_[pit];
1336
1337                 // We allow all kinds of "mumbo-jumbo" when freespacing.
1338                 if (par.isFreeSpacing())
1339                         continue;
1340
1341                 for (pos_type pos = 1; pos < par.size(); ++pos) {
1342                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1343                             && !par.isDeleted(pos - 1)) {
1344                                 if (par.eraseChar(pos - 1, trackChanges)) {
1345                                         --pos;
1346                                 }
1347                         }
1348                 }
1349
1350                 // don't delete anything if this is the only remaining paragraph within the given range
1351                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM 
1352                 if (first == last)
1353                         continue;
1354
1355                 // don't delete empty paragraphs with keepempty set
1356                 if (par.allowEmpty())
1357                         continue;
1358
1359                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1360                         pars_.erase(boost::next(pars_.begin(), pit));
1361                         --pit;
1362                         --last;
1363                         continue;
1364                 }
1365
1366                 par.stripLeadingSpaces(trackChanges);
1367         }
1368 }
1369
1370
1371 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1372 {
1373         recordUndo(cur, Undo::ATOMIC, first, last);
1374 }
1375
1376
1377 void Text::recUndo(Cursor & cur, pit_type par) const
1378 {
1379         recordUndo(cur, Undo::ATOMIC, par, par);
1380 }
1381
1382 } // namespace lyx