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