]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
64da0621630f513e8ea33111abe8c6f3855f65eb
[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         // if there is no selection just set the current_font
441         if (!cur.selection()) {
442                 // Determine basis font
443                 Font layoutfont;
444                 pit_type pit = cur.pit();
445                 if (cur.pos() < pars_[pit].beginOfBody())
446                         layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
447                 else
448                         layoutfont = getLayoutFont(cur.buffer(), pit);
449
450                 // Update current font
451                 real_current_font.update(font,
452                                          cur.buffer().params().language,
453                                          toggleall);
454
455                 // Reduce to implicit settings
456                 current_font = real_current_font;
457                 current_font.reduce(layoutfont);
458                 // And resolve it completely
459                 real_current_font.realize(layoutfont);
460
461                 return;
462         }
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)
721                 --pos;
722
723         if (pos > 0) {
724                 if (pos == cur.lastpos())
725                         --pos;
726                 else // potentional bug... BUG (Lgb)
727                         if (par.isSeparator(pos)) {
728                                 if (pos > cur.textRow().pos() &&
729                                     bidi.level(pos) % 2 ==
730                                     bidi.level(pos - 1) % 2)
731                                         --pos;
732                                 else if (pos + 1 < cur.lastpos())
733                                         ++pos;
734                         }
735         }
736
737         BufferParams const & bufparams = cur.buffer().params();
738         current_font = par.getFontSettings(bufparams, pos);
739         real_current_font = getFont(cur.buffer(), par, pos);
740
741         if (cur.pos() == cur.lastpos()
742             && bidi.isBoundary(cur.buffer(), par, cur.pos())
743             && !cur.boundary()) {
744                 Language const * lang = par.getParLanguage(bufparams);
745                 current_font.setLanguage(lang);
746                 current_font.setNumber(Font::OFF);
747                 real_current_font.setLanguage(lang);
748                 real_current_font.setNumber(Font::OFF);
749         }
750 }
751
752 // y is screen coordinate
753 pit_type Text::getPitNearY(BufferView & bv, int y) const
754 {
755         BOOST_ASSERT(!paragraphs().empty());
756         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
757         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
758         LYXERR(Debug::DEBUG)
759                 << BOOST_CURRENT_FUNCTION
760                 << ": y: " << y << " cache size: " << cc.size()
761                 << endl;
762
763         // look for highest numbered paragraph with y coordinate less than given y
764         pit_type pit = 0;
765         int yy = -1;
766         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
767         CoordCache::InnerParPosCache::const_iterator et = cc.end();
768         CoordCache::InnerParPosCache::const_iterator last = et; last--;
769
770         TextMetrics & tm = bv.textMetrics(this);
771         ParagraphMetrics const & pm = tm.parMetrics(it->first);
772
773         // If we are off-screen (before the visible part)
774         if (y < 0
775                 // and even before the first paragraph in the cache.
776                 && y < it->second.y_ - int(pm.ascent())) {
777                 //  and we are not at the first paragraph in the inset.
778                 if (it->first == 0)
779                         return 0;
780                 // then this is the paragraph we are looking for.
781                 pit = it->first - 1;
782                 // rebreak it and update the CoordCache.
783                 tm.redoParagraph(pit);
784                 bv.coordCache().parPos()[this][pit] =
785                         Point(0, it->second.y_ - pm.descent());
786                 return pit;
787         }
788
789         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
790
791         // If we are off-screen (after the visible part)
792         if (y > bv.workHeight()
793                 // and even after the first paragraph in the cache.
794                 && y >= last->second.y_ + int(pm_last.descent())) {
795                 pit = last->first + 1;
796                 //  and we are not at the last paragraph in the inset.
797                 if (pit == int(pars_.size()))
798                         return last->first;
799                 // then this is the paragraph we are looking for.
800                 // rebreak it and update the CoordCache.
801                 tm.redoParagraph(pit);
802                 bv.coordCache().parPos()[this][pit] =
803                         Point(0, last->second.y_ + pm_last.ascent());
804                 return pit;
805         }
806
807         for (; it != et; ++it) {
808                 LYXERR(Debug::DEBUG)
809                         << BOOST_CURRENT_FUNCTION
810                         << "  examining: pit: " << it->first
811                         << " y: " << it->second.y_
812                         << endl;
813
814                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
815
816                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
817                         pit = it->first;
818                         yy = it->second.y_;
819                 }
820         }
821
822         LYXERR(Debug::DEBUG)
823                 << BOOST_CURRENT_FUNCTION
824                 << ": found best y: " << yy << " for pit: " << pit
825                 << endl;
826
827         return pit;
828 }
829
830
831 Row const & Text::getRowNearY(BufferView const & bv, int y, pit_type pit) const
832 {
833         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
834
835         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
836         BOOST_ASSERT(!pm.rows().empty());
837         RowList::const_iterator rit = pm.rows().begin();
838         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
839         for (; rit != rlast; yy += rit->height(), ++rit)
840                 if (yy + rit->height() > y)
841                         break;
842         return *rit;
843 }
844
845
846 // x,y are absolute screen coordinates
847 // sets cursor recursively descending into nested editable insets
848 Inset * Text::editXY(Cursor & cur, int x, int y)
849 {
850         if (lyxerr.debugging(Debug::WORKAREA)) {
851                 lyxerr << "Text::editXY(cur, " << x << ", " << y << ")" << std::endl;
852                 cur.bv().coordCache().dump();
853         }
854         pit_type pit = getPitNearY(cur.bv(), y);
855         BOOST_ASSERT(pit != -1);
856
857         Row const & row = getRowNearY(cur.bv(), y, pit);
858         bool bound = false;
859
860         TextMetrics const & tm = cur.bv().textMetrics(this);
861         int xx = x; // is modified by getColumnNearX
862         pos_type const pos = row.pos()
863                 + tm.getColumnNearX(pit, row, xx, bound);
864         cur.pit() = pit;
865         cur.pos() = pos;
866         cur.boundary(bound);
867         cur.x_target() = x;
868
869         // try to descend into nested insets
870         Inset * inset = checkInsetHit(cur.bv(), x, y);
871         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
872         if (!inset) {
873                 // Either we deconst editXY or better we move current_font
874                 // and real_current_font to Cursor
875                 setCurrentFont(cur);
876                 return 0;
877         }
878
879         Inset * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
880         //Inset * insetBehind = pars_[pit].getInset(pos);
881
882         // This should be just before or just behind the
883         // cursor position set above.
884         BOOST_ASSERT((pos != 0 && inset == insetBefore)
885                 || inset == pars_[pit].getInset(pos));
886
887         // Make sure the cursor points to the position before
888         // this inset.
889         if (inset == insetBefore)
890                 --cur.pos();
891
892         // Try to descend recursively inside the inset.
893         inset = inset->editXY(cur, x, y);
894
895         if (cur.top().text() == this)
896                 setCurrentFont(cur);
897         return inset;
898 }
899
900
901 bool Text::checkAndActivateInset(Cursor & cur, bool front)
902 {
903         if (cur.selection())
904                 return false;
905         if (cur.pos() == cur.lastpos())
906                 return false;
907         Inset * inset = cur.nextInset();
908         if (!isHighlyEditableInset(inset))
909                 return false;
910         inset->edit(cur, front);
911         return true;
912 }
913
914
915 bool Text::cursorLeft(Cursor & cur)
916 {
917         // Tell BufferView to test for FitCursor in any case!
918         cur.updateFlags(Update::FitCursor);
919
920         if (!cur.boundary() && cur.pos() > 0 &&
921             cur.textRow().pos() == cur.pos() &&
922             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
923             !cur.paragraph().isNewline(cur.pos()-1)) {
924                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
925         }
926         if (cur.pos() != 0) {
927                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
928                 if (!checkAndActivateInset(cur, false)) {
929                         /** FIXME: What's this cause purpose???
930                         bool boundary = cur.boundary();
931                         if (false && !boundary &&
932                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
933                                 updateNeeded |=
934                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
935                         */
936                 }
937                 return updateNeeded;
938         }
939
940         if (cur.pit() != 0) {
941                 // Steps into the paragraph above
942                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
943         }
944         return false;
945 }
946
947
948 bool Text::cursorRight(Cursor & cur)
949 {
950         // Tell BufferView to test for FitCursor in any case!
951         cur.updateFlags(Update::FitCursor);
952
953         if (cur.pos() != cur.lastpos()) {
954                 if (cur.boundary())
955                         return setCursor(cur, cur.pit(), cur.pos(),
956                                          true, false);
957
958                 bool updateNeeded = false;
959                 if (!checkAndActivateInset(cur, true)) {
960                         if (cur.textRow().endpos() == cur.pos() + 1 &&
961                             cur.textRow().endpos() != cur.lastpos() &&
962                             !cur.paragraph().isLineSeparator(cur.pos()) &&
963                             !cur.paragraph().isNewline(cur.pos())) {
964                                 cur.boundary(true);
965                         }
966                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
967                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
968                                                      cur.pos()))
969                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
970                 }
971                 return updateNeeded;
972         }
973
974         if (cur.pit() != cur.lastpit())
975                 return setCursor(cur, cur.pit() + 1, 0);
976         return false;
977 }
978
979
980 bool Text::cursorUp(Cursor & cur)
981 {
982         // Tell BufferView to test for FitCursor in any case!
983         cur.updateFlags(Update::FitCursor);
984
985         TextMetrics const & tm = cur.bv().textMetrics(this);
986         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
987
988         int row;
989         if (cur.pos() && cur.boundary())
990                 row = pm.pos2row(cur.pos()-1);
991         else
992                 row = pm.pos2row(cur.pos());
993
994         int x = cur.targetX();
995         cur.setTargetX();
996         // We want to keep the x-target on subsequent up movements
997         // that cross beyond the end of short lines. Thus a special
998         // handling when the cursor is at the end of line: Use the new 
999         // x-target only if the old one was before the end of line.
1000         if (cur.pos() != pm.rows()[row].endpos() 
1001                 || (!reverseDirectionNeeded(cur) && x < cur.targetX())
1002                 || (reverseDirectionNeeded(cur) && x > cur.targetX())) {
1003
1004                 x = cur.targetX();
1005         }
1006
1007         if (!cur.selection()) {
1008                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1009                 Cursor old = cur;
1010                 // Go to middle of previous row. 16 found to work OK;
1011                 // 12 = top/bottom margin of display math
1012                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1013                 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1014                 cur.clearSelection();
1015
1016                 // This happens when you move out of an inset.
1017                 // And to give the DEPM the possibility of doing
1018                 // something we must provide it with two different
1019                 // cursors. (Lgb)
1020                 Cursor dummy = cur;
1021                 if (dummy == old)
1022                         ++dummy.pos();
1023
1024                 cur.bv().checkDepm(dummy, old);
1025                 return false;
1026         }
1027
1028         bool updateNeeded = false;
1029
1030         if (row > 0) {
1031                 updateNeeded |= setCursor(cur, cur.pit(),
1032                         tm.x2pos(cur.pit(), row - 1, x));
1033         } else if (cur.pit() > 0) {
1034                 --cur.pit();
1035                 //cannot use 'par' now
1036                 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1037                 updateNeeded |= setCursor(cur, cur.pit(),
1038                         tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1039         }
1040
1041         cur.x_target() = x;
1042
1043         return updateNeeded;
1044 }
1045
1046
1047 bool Text::cursorDown(Cursor & cur)
1048 {
1049         // Tell BufferView to test for FitCursor in any case!
1050         cur.updateFlags(Update::FitCursor);
1051
1052         TextMetrics const & tm = cur.bv().textMetrics(this);
1053         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1054
1055         int row;
1056         if (cur.pos() && cur.boundary())
1057                 row = pm.pos2row(cur.pos()-1);
1058         else
1059                 row = pm.pos2row(cur.pos());
1060
1061         int x = cur.targetX();
1062         cur.setTargetX();
1063         // We want to keep the x-target on subsequent down movements
1064         // that cross beyond the end of short lines. Thus a special
1065         // handling when the cursor is at the end of line: Use the new 
1066         // x-target only if the old one was before the end of line.
1067         if (cur.pos() != pm.rows()[row].endpos() 
1068                 || (!cur.isRTL() && x < cur.targetX())
1069                 || (cur.isRTL() && x > cur.targetX())) {
1070
1071                 x = cur.targetX();
1072         }
1073
1074         if (!cur.selection()) {
1075                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1076                 Cursor old = cur;
1077                 // To middle of next row
1078                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1079                 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1080                 cur.clearSelection();
1081
1082                 // This happens when you move out of an inset.
1083                 // And to give the DEPM the possibility of doing
1084                 // something we must provide it with two different
1085                 // cursors. (Lgb)
1086                 Cursor dummy = cur;
1087                 if (dummy == old)
1088                         ++dummy.pos();
1089                 
1090                 bool const changed = cur.bv().checkDepm(dummy, old);
1091
1092                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1093                 if (changed)
1094                         cur = dummy;
1095
1096                 return false;
1097         }
1098
1099         bool updateNeeded = false;
1100
1101         if (row + 1 < int(pm.rows().size())) {
1102                 updateNeeded |= setCursor(cur, cur.pit(),
1103                         tm.x2pos(cur.pit(), row + 1, x));
1104         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1105                 ++cur.pit();
1106                 updateNeeded |= setCursor(cur, cur.pit(),
1107                         tm.x2pos(cur.pit(), 0, x));
1108         }
1109
1110         cur.x_target() = x;
1111
1112         return updateNeeded;
1113 }
1114
1115
1116 bool Text::cursorUpParagraph(Cursor & cur)
1117 {
1118         bool updated = false;
1119         if (cur.pos() > 0)
1120                 updated = setCursor(cur, cur.pit(), 0);
1121         else if (cur.pit() != 0)
1122                 updated = setCursor(cur, cur.pit() - 1, 0);
1123         return updated;
1124 }
1125
1126
1127 bool Text::cursorDownParagraph(Cursor & cur)
1128 {
1129         bool updated = false;
1130         if (cur.pit() != cur.lastpit())
1131                 updated = setCursor(cur, cur.pit() + 1, 0);
1132         else
1133                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1134         return updated;
1135 }
1136
1137
1138 // fix the cursor `cur' after a characters has been deleted at `where'
1139 // position. Called by deleteEmptyParagraphMechanism
1140 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1141 {
1142         // Do nothing if cursor is not in the paragraph where the
1143         // deletion occured,
1144         if (cur.pit() != where.pit())
1145                 return;
1146
1147         // If cursor position is after the deletion place update it
1148         if (cur.pos() > where.pos())
1149                 --cur.pos();
1150
1151         // Check also if we don't want to set the cursor on a spot behind the
1152         // pagragraph because we erased the last character.
1153         if (cur.pos() > cur.lastpos())
1154                 cur.pos() = cur.lastpos();
1155 }
1156
1157
1158 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1159                 Cursor & old, bool & need_anchor_change)
1160 {
1161         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1162
1163         Paragraph & oldpar = old.paragraph();
1164
1165         // We allow all kinds of "mumbo-jumbo" when freespacing.
1166         if (oldpar.isFreeSpacing())
1167                 return false;
1168
1169         /* Ok I'll put some comments here about what is missing.
1170            There are still some small problems that can lead to
1171            double spaces stored in the document file or space at
1172            the beginning of paragraphs(). This happens if you have
1173            the cursor between to spaces and then save. Or if you
1174            cut and paste and the selection have a space at the
1175            beginning and then save right after the paste. (Lgb)
1176         */
1177
1178         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1179         // delete the LineSeparator.
1180         // MISSING
1181
1182         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1183         // delete the LineSeparator.
1184         // MISSING
1185
1186         bool const same_inset = &old.inset() == &cur.inset();
1187         bool const same_par = same_inset && old.pit() == cur.pit();
1188         bool const same_par_pos = same_par && old.pos() == cur.pos();
1189
1190         // If the chars around the old cursor were spaces, delete one of them.
1191         if (!same_par_pos) {
1192                 // Only if the cursor has really moved.
1193                 if (old.pos() > 0
1194                     && old.pos() < oldpar.size()
1195                     && oldpar.isLineSeparator(old.pos())
1196                     && oldpar.isLineSeparator(old.pos() - 1)
1197                     && !oldpar.isDeleted(old.pos() - 1)) {
1198                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1199 #ifdef WITH_WARNINGS
1200 #warning This will not work anymore when we have multiple views of the same buffer
1201 // In this case, we will have to correct also the cursors held by
1202 // other bufferviews. It will probably be easier to do that in a more
1203 // automated way in CursorSlice code. (JMarc 26/09/2001)
1204 #endif
1205                         // correct all cursor parts
1206                         if (same_par) {
1207                                 fixCursorAfterDelete(cur.top(), old.top());
1208                                 need_anchor_change = true;
1209                         }
1210                         return true;
1211                 }
1212         }
1213
1214         // only do our magic if we changed paragraph
1215         if (same_par)
1216                 return false;
1217
1218         // don't delete anything if this is the ONLY paragraph!
1219         if (old.lastpit() == 0)
1220                 return false;
1221
1222         // Do not delete empty paragraphs with keepempty set.
1223         if (oldpar.allowEmpty())
1224                 return false;
1225
1226         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1227                 // Delete old par.
1228                 recordUndo(old, Undo::ATOMIC,
1229                            max(old.pit() - 1, pit_type(0)),
1230                            min(old.pit() + 1, old.lastpit()));
1231                 ParagraphList & plist = old.text()->paragraphs();
1232                 plist.erase(boost::next(plist.begin(), old.pit()));
1233
1234                 // see #warning above
1235                 if (cur.depth() >= old.depth()) {
1236                         CursorSlice & curslice = cur[old.depth() - 1];
1237                         if (&curslice.inset() == &old.inset()
1238                             && curslice.pit() > old.pit()) {
1239                                 --curslice.pit();
1240                                 // since a paragraph has been deleted, all the
1241                                 // insets after `old' have been copied and
1242                                 // their address has changed. Therefore we
1243                                 // need to `regenerate' cur. (JMarc)
1244                                 cur.updateInsets(&(cur.bottom().inset()));
1245                                 need_anchor_change = true;
1246                         }
1247                 }
1248                 return true;
1249         }
1250
1251         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1252                 need_anchor_change = true;
1253                 // We return true here because the Paragraph contents changed and
1254                 // we need a redraw before further action is processed.
1255                 return true;
1256         }
1257
1258         return false;
1259 }
1260
1261
1262 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1263 {
1264         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1265
1266         for (pit_type pit = first; pit <= last; ++pit) {
1267                 Paragraph & par = pars_[pit];
1268
1269                 // We allow all kinds of "mumbo-jumbo" when freespacing.
1270                 if (par.isFreeSpacing())
1271                         continue;
1272
1273                 for (pos_type pos = 1; pos < par.size(); ++pos) {
1274                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1275                             && !par.isDeleted(pos - 1)) {
1276                                 if (par.eraseChar(pos - 1, trackChanges)) {
1277                                         --pos;
1278                                 }
1279                         }
1280                 }
1281
1282                 // don't delete anything if this is the only remaining paragraph within the given range
1283                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM 
1284                 if (first == last)
1285                         continue;
1286
1287                 // don't delete empty paragraphs with keepempty set
1288                 if (par.allowEmpty())
1289                         continue;
1290
1291                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1292                         pars_.erase(boost::next(pars_.begin(), pit));
1293                         --pit;
1294                         --last;
1295                         continue;
1296                 }
1297
1298                 par.stripLeadingSpaces(trackChanges);
1299         }
1300 }
1301
1302
1303 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1304 {
1305         recordUndo(cur, Undo::ATOMIC, first, last);
1306 }
1307
1308
1309 void Text::recUndo(Cursor & cur, pit_type par) const
1310 {
1311         recordUndo(cur, Undo::ATOMIC, par, par);
1312 }
1313
1314 } // namespace lyx