]> git.lyx.org Git - lyx.git/blob - src/text2.C
Fix cursor navigation with UP arrows. I don't know why this return was not there...
[lyx.git] / src / text2.C
1 /**
2  * \file text2.C
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 "lyxtext.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 "LColor.h"
41 #include "lyxfunc.h"
42 #include "lyxrc.h"
43 #include "lyxrow.h"
44 #include "paragraph.h"
45 #include "TextMetrics.h"
46 #include "paragraph_funcs.h"
47 #include "ParagraphParameters.h"
48 #include "pariterator.h"
49 #include "lyxserver.h"
50 #include "lyxsocket.h"
51 #include "undo.h"
52 #include "vspace.h"
53
54 #include "frontends/FontMetrics.h"
55
56 #include "insets/insetenv.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 LyXText::LyXText()
77         : current_font(LyXFont::ALL_INHERIT),
78           background_color_(LColor::background),
79           autoBreakRows_(false)
80 {}
81
82
83 bool LyXText::isMainText(Buffer const & buffer) const
84 {
85         return &buffer.text() == this;
86 }
87
88
89 //takes screen x,y coordinates
90 InsetBase * LyXText::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                 InsetBase * 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.C.
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 LyXFont LyXText::getFont(Buffer const & buffer, Paragraph const & par,
147                 pos_type const pos) const
148 {
149         BOOST_ASSERT(pos >= 0);
150
151         LyXLayout_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                 LyXFont f = par.getFontSettings(params, pos);
161                 if (!isMainText(buffer))
162                         applyOuterFont(buffer, f);
163                 LyXFont lf;
164                 LyXFont 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() == LyXFont::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         LyXFont layoutfont;
180         if (pos < body_pos)
181                 layoutfont = layout->labelfont;
182         else
183                 layoutfont = layout->font;
184
185         LyXFont 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 LyXText::applyOuterFont(Buffer const & buffer, LyXFont & font) const {
221         LyXFont lf(font_);
222         lf.reduce(buffer.params().getFont());
223         lf.realize(font);
224         lf.setLanguage(font.language());
225         font = lf;
226 }
227
228
229 LyXFont LyXText::getLayoutFont(Buffer const & buffer, pit_type const pit) const
230 {
231         LyXLayout_ptr const & layout = pars_[pit].layout();
232
233         if (!pars_[pit].getDepth())  {
234                 LyXFont lf = layout->resfont;
235                 // In case the default family has been customized
236                 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
237                         lf.setFamily(buffer.params().getFont().family());
238                 return lf;
239         }
240
241         LyXFont 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 LyXFont LyXText::getLabelFont(Buffer const & buffer, Paragraph const & par) const
251 {
252         LyXLayout_ptr const & layout = par.layout();
253
254         if (!par.getDepth()) {
255                 LyXFont lf = layout->reslabelfont;
256                 // In case the default family has been customized
257                 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
258                         lf.setFamily(buffer.params().getFont().family());
259                 return lf;
260         }
261
262         LyXFont 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 LyXText::setCharFont(Buffer const & buffer, pit_type pit,
271                 pos_type pos, LyXFont const & fnt)
272 {
273         LyXFont font = fnt;
274         LyXLayout_ptr const & layout = pars_[pit].layout();
275
276         // Get concrete layout font to reduce against
277         LyXFont 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 LyXText::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 LyXText::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         LyXLayout_ptr const & lyxlayout = bufparams.getLyXTextClass()[layout];
336
337         for (pit_type pit = start; pit != end; ++pit) {
338                 pars_[pit].applyLayout(lyxlayout);
339                 if (lyxlayout->margintype == MARGIN_MANUAL)
340                         pars_[pit].setLabelWidthString(buffer.translateLabel(lyxlayout->labelstring()));
341         }
342 }
343
344
345 // set layout over selection and make a total rebreak of those paragraphs
346 void LyXText::setLayout(LCursor & cur, string const & layout)
347 {
348         BOOST_ASSERT(this == cur.text());
349         // special handling of new environment insets
350         BufferView & bv = cur.bv();
351         BufferParams const & params = bv.buffer()->params();
352         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
353         if (lyxlayout->is_environment) {
354                 // move everything in a new environment inset
355                 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
356                 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
357                 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
358                 lyx::dispatch(FuncRequest(LFUN_CUT));
359                 InsetBase * inset = new InsetEnvironment(params, layout);
360                 insertInset(cur, inset);
361                 //inset->edit(cur, true);
362                 //lyx::dispatch(FuncRequest(LFUN_PASTE));
363                 return;
364         }
365
366         pit_type start = cur.selBegin().pit();
367         pit_type end = cur.selEnd().pit() + 1;
368         pit_type undopit = undoSpan(end - 1);
369         recUndo(cur, start, undopit - 1);
370         setLayout(cur.buffer(), start, end, layout);
371         updateLabels(cur.buffer());
372 }
373
374
375 static bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
376                         Paragraph const & par, int max_depth)
377 {
378         if (par.layout()->labeltype == LABEL_BIBLIO)
379                 return false;
380         int const depth = par.params().depth();
381         if (type == LyXText::INC_DEPTH && depth < max_depth)
382                 return true;
383         if (type == LyXText::DEC_DEPTH && depth > 0)
384                 return true;
385         return false;
386 }
387
388
389 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
390 {
391         BOOST_ASSERT(this == cur.text());
392         // this happens when selecting several cells in tabular (bug 2630)
393         if (cur.selBegin().idx() != cur.selEnd().idx())
394                 return false;
395
396         pit_type const beg = cur.selBegin().pit();
397         pit_type const end = cur.selEnd().pit() + 1;
398         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
399
400         for (pit_type pit = beg; pit != end; ++pit) {
401                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
402                         return true;
403                 max_depth = pars_[pit].getMaxDepthAfter();
404         }
405         return false;
406 }
407
408
409 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
410 {
411         BOOST_ASSERT(this == cur.text());
412         pit_type const beg = cur.selBegin().pit();
413         pit_type const end = cur.selEnd().pit() + 1;
414         recordUndoSelection(cur);
415         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
416
417         for (pit_type pit = beg; pit != end; ++pit) {
418                 Paragraph & par = pars_[pit];
419                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
420                         int const depth = par.params().depth();
421                         if (type == INC_DEPTH)
422                                 par.params().depth(depth + 1);
423                         else
424                                 par.params().depth(depth - 1);
425                 }
426                 max_depth = par.getMaxDepthAfter();
427         }
428         // this handles the counter labels, and also fixes up
429         // depth values for follow-on (child) paragraphs
430         updateLabels(cur.buffer());
431 }
432
433
434 // set font over selection
435 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
436 {
437         BOOST_ASSERT(this == cur.text());
438         // if there is no selection just set the current_font
439         if (!cur.selection()) {
440                 // Determine basis font
441                 LyXFont layoutfont;
442                 pit_type pit = cur.pit();
443                 if (cur.pos() < pars_[pit].beginOfBody())
444                         layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
445                 else
446                         layoutfont = getLayoutFont(cur.buffer(), pit);
447
448                 // Update current font
449                 real_current_font.update(font,
450                                          cur.buffer().params().language,
451                                          toggleall);
452
453                 // Reduce to implicit settings
454                 current_font = real_current_font;
455                 current_font.reduce(layoutfont);
456                 // And resolve it completely
457                 real_current_font.realize(layoutfont);
458
459                 return;
460         }
461
462         // Ok, we have a selection.
463         recordUndoSelection(cur);
464
465         DocIterator dit = cur.selectionBegin();
466         DocIterator ditend = cur.selectionEnd();
467
468         BufferParams const & params = cur.buffer().params();
469
470         // Don't use forwardChar here as ditend might have
471         // pos() == lastpos() and forwardChar would miss it.
472         // Can't use forwardPos either as this descends into
473         // nested insets.
474         for (; dit != ditend; dit.forwardPosNoDescend()) {
475                 if (dit.pos() != dit.lastpos()) {
476                         LyXFont f = getFont(cur.buffer(), dit.paragraph(), dit.pos());
477                         f.update(font, params.language, toggleall);
478                         setCharFont(cur.buffer(), dit.pit(), dit.pos(), f);
479                 }
480         }
481 }
482
483
484 // the cursor set functions have a special mechanism. When they
485 // realize you left an empty paragraph, they will delete it.
486
487 bool LyXText::cursorHome(LCursor & cur)
488 {
489         BOOST_ASSERT(this == cur.text());
490         ParagraphMetrics const & pm = cur.bv().parMetrics(this, cur.pit());
491         Row const & row = pm.getRow(cur.pos(),cur.boundary());
492         return setCursor(cur, cur.pit(), row.pos());
493 }
494
495
496 bool LyXText::cursorEnd(LCursor & cur)
497 {
498         BOOST_ASSERT(this == cur.text());
499         // if not on the last row of the par, put the cursor before
500         // the final space exept if I have a spanning inset or one string
501         // is so long that we force a break.
502         pos_type end = cur.textRow().endpos();
503         if (end == 0)
504                 // empty text, end-1 is no valid position
505                 return false;
506         bool boundary = false;
507         if (end != cur.lastpos()) {
508                 if (!cur.paragraph().isLineSeparator(end-1)
509                     && !cur.paragraph().isNewline(end-1))
510                         boundary = true;
511                 else
512                         --end;
513         }
514         return setCursor(cur, cur.pit(), end, true, boundary);
515 }
516
517
518 bool LyXText::cursorTop(LCursor & cur)
519 {
520         BOOST_ASSERT(this == cur.text());
521         return setCursor(cur, 0, 0);
522 }
523
524
525 bool LyXText::cursorBottom(LCursor & cur)
526 {
527         BOOST_ASSERT(this == cur.text());
528         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
529 }
530
531
532 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
533 {
534         BOOST_ASSERT(this == cur.text());
535         // If the mask is completely neutral, tell user
536         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
537                 // Could only happen with user style
538                 cur.message(_("No font change defined. "
539                                            "Use Character under the Layout menu to define font change."));
540                 return;
541         }
542
543         // Try implicit word selection
544         // If there is a change in the language the implicit word selection
545         // is disabled.
546         CursorSlice resetCursor = cur.top();
547         bool implicitSelection =
548                 font.language() == ignore_language
549                 && font.number() == LyXFont::IGNORE
550                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
551
552         // Set font
553         setFont(cur, font, toggleall);
554
555         // Implicit selections are cleared afterwards
556         // and cursor is set to the original position.
557         if (implicitSelection) {
558                 cur.clearSelection();
559                 cur.top() = resetCursor;
560                 cur.resetAnchor();
561         }
562 }
563
564
565 docstring LyXText::getStringToIndex(LCursor const & cur)
566 {
567         BOOST_ASSERT(this == cur.text());
568
569         docstring idxstring;
570         if (cur.selection())
571                 idxstring = cur.selectionAsString(false);
572         else {
573                 // Try implicit word selection. If there is a change
574                 // in the language the implicit word selection is
575                 // disabled.
576                 LCursor tmpcur = cur;
577                 selectWord(tmpcur, PREVIOUS_WORD);
578
579                 if (!tmpcur.selection())
580                         cur.message(_("Nothing to index!"));
581                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
582                         cur.message(_("Cannot index more than one paragraph!"));
583                 else
584                         idxstring = tmpcur.selectionAsString(false);
585         }
586
587         return idxstring;
588 }
589
590
591 void LyXText::setParagraph(LCursor & cur,
592                            Spacing const & spacing, LyXAlignment align,
593                            docstring const & labelwidthstring, bool noindent)
594 {
595         BOOST_ASSERT(cur.text());
596         // make sure that the depth behind the selection are restored, too
597         pit_type undopit = undoSpan(cur.selEnd().pit());
598         recUndo(cur, cur.selBegin().pit(), undopit - 1);
599
600         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
601              pit <= end; ++pit) {
602                 Paragraph & par = pars_[pit];
603                 ParagraphParameters & params = par.params();
604                 params.spacing(spacing);
605
606                 // does the layout allow the new alignment?
607                 LyXLayout_ptr const & layout = par.layout();
608
609                 if (align == LYX_ALIGN_LAYOUT)
610                         align = layout->align;
611                 if (align & layout->alignpossible) {
612                         if (align == layout->align)
613                                 params.align(LYX_ALIGN_LAYOUT);
614                         else
615                                 params.align(align);
616                 }
617                 par.setLabelWidthString(labelwidthstring);
618                 params.noindent(noindent);
619         }
620 }
621
622
623 // this really should just insert the inset and not move the cursor.
624 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
625 {
626         BOOST_ASSERT(this == cur.text());
627         BOOST_ASSERT(inset);
628         cur.paragraph().insertInset(cur.pos(), inset, 
629                                     Change(cur.buffer().params().trackChanges ?
630                                            Change::INSERTED : Change::UNCHANGED));
631 }
632
633
634 // needed to insert the selection
635 void LyXText::insertStringAsLines(LCursor & cur, docstring const & str)
636 {
637         cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
638                                          current_font, str, autoBreakRows_);
639 }
640
641
642 // turn double CR to single CR, others are converted into one
643 // blank. Then insertStringAsLines is called
644 void LyXText::insertStringAsParagraphs(LCursor & cur, docstring const & str)
645 {
646         docstring linestr = str;
647         bool newline_inserted = false;
648
649         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
650                 if (linestr[i] == '\n') {
651                         if (newline_inserted) {
652                                 // we know that \r will be ignored by
653                                 // insertStringAsLines. Of course, it is a dirty
654                                 // trick, but it works...
655                                 linestr[i - 1] = '\r';
656                                 linestr[i] = '\n';
657                         } else {
658                                 linestr[i] = ' ';
659                                 newline_inserted = true;
660                         }
661                 } else if (isPrintable(linestr[i])) {
662                         newline_inserted = false;
663                 }
664         }
665         insertStringAsLines(cur, linestr);
666 }
667
668
669 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
670                         bool setfont, bool boundary)
671 {
672         LCursor old = cur;
673         setCursorIntern(cur, par, pos, setfont, boundary);
674         return cur.bv().checkDepm(cur, old);
675 }
676
677
678 void LyXText::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
679 {
680         BOOST_ASSERT(par != int(paragraphs().size()));
681         cur.pit() = par;
682         cur.pos() = pos;
683
684         // now some strict checking
685         Paragraph & para = getPar(par);
686
687         // None of these should happen, but we're scaredy-cats
688         if (pos < 0) {
689                 lyxerr << "dont like -1" << endl;
690                 BOOST_ASSERT(false);
691         }
692
693         if (pos > para.size()) {
694                 lyxerr << "dont like 1, pos: " << pos
695                        << " size: " << para.size()
696                        << " par: " << par << endl;
697                 BOOST_ASSERT(false);
698         }
699 }
700
701
702 void LyXText::setCursorIntern(LCursor & cur,
703                               pit_type par, pos_type pos, bool setfont, bool boundary)
704 {
705         BOOST_ASSERT(this == cur.text());
706         cur.boundary(boundary);
707         setCursor(cur.top(), par, pos);
708         if (setfont)
709                 setCurrentFont(cur);
710 }
711
712
713 void LyXText::setCurrentFont(LCursor & cur)
714 {
715         BOOST_ASSERT(this == cur.text());
716         pos_type pos = cur.pos();
717         Paragraph & par = cur.paragraph();
718
719         if (cur.boundary() && pos > 0)
720                 --pos;
721
722         if (pos > 0) {
723                 if (pos == cur.lastpos())
724                         --pos;
725                 else // potentional bug... BUG (Lgb)
726                         if (par.isSeparator(pos)) {
727                                 if (pos > cur.textRow().pos() &&
728                                     bidi.level(pos) % 2 ==
729                                     bidi.level(pos - 1) % 2)
730                                         --pos;
731                                 else if (pos + 1 < cur.lastpos())
732                                         ++pos;
733                         }
734         }
735
736         BufferParams const & bufparams = cur.buffer().params();
737         current_font = par.getFontSettings(bufparams, pos);
738         real_current_font = getFont(cur.buffer(), par, pos);
739
740         if (cur.pos() == cur.lastpos()
741             && bidi.isBoundary(cur.buffer(), par, cur.pos())
742             && !cur.boundary()) {
743                 Language const * lang = par.getParLanguage(bufparams);
744                 current_font.setLanguage(lang);
745                 current_font.setNumber(LyXFont::OFF);
746                 real_current_font.setLanguage(lang);
747                 real_current_font.setNumber(LyXFont::OFF);
748         }
749 }
750
751 // y is screen coordinate
752 pit_type LyXText::getPitNearY(BufferView & bv, int y)
753 {
754         BOOST_ASSERT(!paragraphs().empty());
755         BOOST_ASSERT(bv.coordCache().getParPos().find(this) != bv.coordCache().getParPos().end());
756         CoordCache::InnerParPosCache const & cc = bv.coordCache().getParPos().find(this)->second;
757         lyxerr[Debug::DEBUG]
758                 << BOOST_CURRENT_FUNCTION
759                 << ": y: " << y << " cache size: " << cc.size()
760                 << endl;
761
762         // look for highest numbered paragraph with y coordinate less than given y
763         pit_type pit = 0;
764         int yy = -1;
765         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
766         CoordCache::InnerParPosCache::const_iterator et = cc.end();
767         CoordCache::InnerParPosCache::const_iterator last = et; last--;
768
769         TextMetrics & tm = bv.textMetrics(this);
770         ParagraphMetrics const & pm = tm.parMetrics(it->first);
771
772         // If we are off-screen (before the visible part)
773         if (y < 0
774                 // and even before the first paragraph in the cache.
775                 && y < it->second.y_ - int(pm.ascent())) {
776                 //  and we are not at the first paragraph in the inset.
777                 if (it->first == 0)
778                         return 0;
779                 // then this is the paragraph we are looking for.
780                 pit = it->first - 1;
781                 // rebreak it and update the CoordCache.
782                 tm.redoParagraph(pit);
783                 bv.coordCache().parPos()[this][pit] =
784                         Point(0, it->second.y_ - pm.descent());
785                 return pit;
786         }
787
788         ParagraphMetrics const & pm_last = bv.parMetrics(this, last->first);
789
790         // If we are off-screen (after the visible part)
791         if (y > bv.workHeight()
792                 // and even after the first paragraph in the cache.
793                 && y >= last->second.y_ + int(pm_last.descent())) {
794                 pit = last->first + 1;
795                 //  and we are not at the last paragraph in the inset.
796                 if (pit == int(pars_.size()))
797                         return last->first;
798                 // then this is the paragraph we are looking for.
799                 // rebreak it and update the CoordCache.
800                 tm.redoParagraph(pit);
801                 bv.coordCache().parPos()[this][pit] =
802                         Point(0, last->second.y_ + pm_last.ascent());
803                 return pit;
804         }
805
806         for (; it != et; ++it) {
807                 lyxerr[Debug::DEBUG]
808                         << BOOST_CURRENT_FUNCTION
809                         << "  examining: pit: " << it->first
810                         << " y: " << it->second.y_
811                         << endl;
812
813                 ParagraphMetrics const & pm = bv.parMetrics(this, it->first);
814
815                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
816                         pit = it->first;
817                         yy = it->second.y_;
818                 }
819         }
820
821         lyxerr[Debug::DEBUG]
822                 << BOOST_CURRENT_FUNCTION
823                 << ": found best y: " << yy << " for pit: " << pit
824                 << endl;
825
826         return pit;
827 }
828
829
830 Row const & LyXText::getRowNearY(BufferView const & bv, int y, pit_type pit) const
831 {
832         ParagraphMetrics const & pm = bv.parMetrics(this, pit);
833
834         int yy = bv.coordCache().get(this, pit).y_ - pm.ascent();
835         BOOST_ASSERT(!pm.rows().empty());
836         RowList::const_iterator rit = pm.rows().begin();
837         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
838         for (; rit != rlast; yy += rit->height(), ++rit)
839                 if (yy + rit->height() > y)
840                         break;
841         return *rit;
842 }
843
844
845 // x,y are absolute screen coordinates
846 // sets cursor recursively descending into nested editable insets
847 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
848 {
849         if (lyxerr.debugging(Debug::WORKAREA)) {
850                 lyxerr << "LyXText::editXY(cur, " << x << ", " << y << ")" << std::endl;
851                 cur.bv().coordCache().dump();
852         }
853         pit_type pit = getPitNearY(cur.bv(), y);
854         BOOST_ASSERT(pit != -1);
855
856         Row const & row = getRowNearY(cur.bv(), y, pit);
857         bool bound = false;
858
859         TextMetrics const & tm = cur.bv().textMetrics(this);
860         int xx = x; // is modified by getColumnNearX
861         pos_type const pos = row.pos()
862                 + tm.getColumnNearX(pit, row, xx, bound);
863         cur.pit() = pit;
864         cur.pos() = pos;
865         cur.boundary(bound);
866         cur.x_target() = x;
867
868         // try to descend into nested insets
869         InsetBase * inset = checkInsetHit(cur.bv(), x, y);
870         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
871         if (!inset) {
872                 // Either we deconst editXY or better we move current_font
873                 // and real_current_font to LCursor
874                 setCurrentFont(cur);
875                 return 0;
876         }
877
878         InsetBase * insetBefore = pos? pars_[pit].getInset(pos - 1): 0;
879         //InsetBase * insetBehind = pars_[pit].getInset(pos);
880
881         // This should be just before or just behind the
882         // cursor position set above.
883         BOOST_ASSERT((pos != 0 && inset == insetBefore)
884                 || inset == pars_[pit].getInset(pos));
885
886         // Make sure the cursor points to the position before
887         // this inset.
888         if (inset == insetBefore)
889                 --cur.pos();
890
891         // Try to descend recursively inside the inset.
892         inset = inset->editXY(cur, x, y);
893
894         if (cur.top().text() == this)
895                 setCurrentFont(cur);
896         return inset;
897 }
898
899
900 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
901 {
902         if (cur.selection())
903                 return false;
904         if (cur.pos() == cur.lastpos())
905                 return false;
906         InsetBase * inset = cur.nextInset();
907         if (!isHighlyEditableInset(inset))
908                 return false;
909         inset->edit(cur, front);
910         return true;
911 }
912
913
914 bool LyXText::cursorLeft(LCursor & cur)
915 {
916         // Tell BufferView to test for FitCursor in any case!
917         cur.updateFlags(Update::FitCursor);
918
919         if (!cur.boundary() && cur.pos() > 0 &&
920             cur.textRow().pos() == cur.pos() &&
921             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
922             !cur.paragraph().isNewline(cur.pos()-1)) {
923                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
924         }
925         if (cur.pos() != 0) {
926                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
927                 if (!checkAndActivateInset(cur, false)) {
928                         /** FIXME: What's this cause purpose???
929                         bool boundary = cur.boundary();
930                         if (false && !boundary &&
931                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
932                                 updateNeeded |=
933                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
934                         */
935                 }
936                 return updateNeeded;
937         }
938
939         if (cur.pit() != 0) {
940                 // Steps into the paragraph above
941                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
942         }
943         return false;
944 }
945
946
947 bool LyXText::cursorRight(LCursor & cur)
948 {
949         // Tell BufferView to test for FitCursor in any case!
950         cur.updateFlags(Update::FitCursor);
951
952         if (cur.pos() != cur.lastpos()) {
953                 if (cur.boundary())
954                         return setCursor(cur, cur.pit(), cur.pos(),
955                                          true, false);
956
957                 bool updateNeeded = false;
958                 if (!checkAndActivateInset(cur, true)) {
959                         if (cur.textRow().endpos() == cur.pos() + 1 &&
960                             cur.textRow().endpos() != cur.lastpos() &&
961                             !cur.paragraph().isLineSeparator(cur.pos()) &&
962                             !cur.paragraph().isNewline(cur.pos())) {
963                                 cur.boundary(true);
964                         }
965                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
966                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
967                                                      cur.pos()))
968                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
969                 }
970                 return updateNeeded;
971         }
972
973         if (cur.pit() != cur.lastpit())
974                 return setCursor(cur, cur.pit() + 1, 0);
975         return false;
976 }
977
978
979 bool LyXText::cursorUp(LCursor & cur)
980 {
981         // Tell BufferView to test for FitCursor in any case!
982         cur.updateFlags(Update::FitCursor);
983
984         TextMetrics const & tm = cur.bv().textMetrics(this);
985         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
986
987         int row;
988         int const x = cur.targetX();
989
990         if (cur.pos() && cur.boundary())
991                 row = pm.pos2row(cur.pos()-1);
992         else
993                 row = pm.pos2row(cur.pos());
994
995         if (!cur.selection()) {
996                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
997                 LCursor old = cur;
998                 // Go to middle of previous row. 16 found to work OK;
999                 // 12 = top/bottom margin of display math
1000                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1001                 editXY(cur, x, y - pm.rows()[row].ascent() - margin);
1002                 cur.clearSelection();
1003
1004                 // This happens when you move out of an inset.
1005                 // And to give the DEPM the possibility of doing
1006                 // something we must provide it with two different
1007                 // cursors. (Lgb)
1008                 LCursor dummy = cur;
1009                 if (dummy == old)
1010                         ++dummy.pos();
1011
1012                 cur.bv().checkDepm(dummy, old);
1013                 return false;
1014         }
1015
1016         bool updateNeeded = false;
1017
1018         if (row > 0) {
1019                 updateNeeded |= setCursor(cur, cur.pit(),
1020                         tm.x2pos(cur.pit(), row - 1, x));
1021         } else if (cur.pit() > 0) {
1022                 --cur.pit();
1023                 //cannot use 'par' now
1024                 ParagraphMetrics const & pmcur = cur.bv().parMetrics(this, cur.pit());
1025                 updateNeeded |= setCursor(cur, cur.pit(),
1026                         tm.x2pos(cur.pit(), pmcur.rows().size() - 1, x));
1027         }
1028
1029         cur.x_target() = x;
1030
1031         return updateNeeded;
1032 }
1033
1034
1035 bool LyXText::cursorDown(LCursor & cur)
1036 {
1037         // Tell BufferView to test for FitCursor in any case!
1038         cur.updateFlags(Update::FitCursor);
1039
1040         TextMetrics const & tm = cur.bv().textMetrics(this);
1041         ParagraphMetrics const & pm = tm.parMetrics(cur.pit());
1042
1043         int row;
1044         int const x = cur.targetX();
1045
1046         if (cur.pos() && cur.boundary())
1047                 row = pm.pos2row(cur.pos()-1);
1048         else
1049                 row = pm.pos2row(cur.pos());
1050
1051         if (!cur.selection()) {
1052                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1053                 LCursor old = cur;
1054                 // To middle of next row
1055                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1056                 editXY(cur, x, y + pm.rows()[row].descent() + margin);
1057                 cur.clearSelection();
1058
1059                 // This happens when you move out of an inset.
1060                 // And to give the DEPM the possibility of doing
1061                 // something we must provide it with two different
1062                 // cursors. (Lgb)
1063                 LCursor dummy = cur;
1064                 if (dummy == old)
1065                         ++dummy.pos();
1066                 
1067                 bool const changed = cur.bv().checkDepm(dummy, old);
1068
1069                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1070                 if (changed)
1071                         cur = dummy;
1072
1073                 return false;
1074         }
1075
1076         bool updateNeeded = false;
1077
1078         if (row + 1 < int(pm.rows().size())) {
1079                 updateNeeded |= setCursor(cur, cur.pit(),
1080                         tm.x2pos(cur.pit(), row + 1, x));
1081         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1082                 ++cur.pit();
1083                 updateNeeded |= setCursor(cur, cur.pit(),
1084                         tm.x2pos(cur.pit(), 0, x));
1085         }
1086
1087         cur.x_target() = x;
1088
1089         return updateNeeded;
1090 }
1091
1092
1093 bool LyXText::cursorUpParagraph(LCursor & cur)
1094 {
1095         bool updated = false;
1096         if (cur.pos() > 0)
1097                 updated = setCursor(cur, cur.pit(), 0);
1098         else if (cur.pit() != 0)
1099                 updated = setCursor(cur, cur.pit() - 1, 0);
1100         return updated;
1101 }
1102
1103
1104 bool LyXText::cursorDownParagraph(LCursor & cur)
1105 {
1106         bool updated = false;
1107         if (cur.pit() != cur.lastpit())
1108                 updated = setCursor(cur, cur.pit() + 1, 0);
1109         else
1110                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1111         return updated;
1112 }
1113
1114
1115 // fix the cursor `cur' after a characters has been deleted at `where'
1116 // position. Called by deleteEmptyParagraphMechanism
1117 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1118 {
1119         // Do nothing if cursor is not in the paragraph where the
1120         // deletion occured,
1121         if (cur.pit() != where.pit())
1122                 return;
1123
1124         // If cursor position is after the deletion place update it
1125         if (cur.pos() > where.pos())
1126                 --cur.pos();
1127
1128         // Check also if we don't want to set the cursor on a spot behind the
1129         // pagragraph because we erased the last character.
1130         if (cur.pos() > cur.lastpos())
1131                 cur.pos() = cur.lastpos();
1132 }
1133
1134
1135 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur,
1136                 LCursor & old, bool & need_anchor_change)
1137 {
1138         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1139
1140         Paragraph & oldpar = old.paragraph();
1141
1142         // We allow all kinds of "mumbo-jumbo" when freespacing.
1143         if (oldpar.isFreeSpacing())
1144                 return false;
1145
1146         /* Ok I'll put some comments here about what is missing.
1147            There are still some small problems that can lead to
1148            double spaces stored in the document file or space at
1149            the beginning of paragraphs(). This happens if you have
1150            the cursor between to spaces and then save. Or if you
1151            cut and paste and the selection have a space at the
1152            beginning and then save right after the paste. (Lgb)
1153         */
1154
1155         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1156         // delete the LineSeparator.
1157         // MISSING
1158
1159         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1160         // delete the LineSeparator.
1161         // MISSING
1162
1163         bool const same_inset = &old.inset() == &cur.inset();
1164         bool const same_par = same_inset && old.pit() == cur.pit();
1165         bool const same_par_pos = same_par && old.pos() == cur.pos();
1166
1167         // If the chars around the old cursor were spaces, delete one of them.
1168         if (!same_par_pos) {
1169                 // Only if the cursor has really moved.
1170                 if (old.pos() > 0
1171                     && old.pos() < oldpar.size()
1172                     && oldpar.isLineSeparator(old.pos())
1173                     && oldpar.isLineSeparator(old.pos() - 1)
1174                     && !oldpar.isDeleted(old.pos() - 1)) {
1175                         oldpar.eraseChar(old.pos() - 1, false); // do not track changes in DEPM
1176 #ifdef WITH_WARNINGS
1177 #warning This will not work anymore when we have multiple views of the same buffer
1178 // In this case, we will have to correct also the cursors held by
1179 // other bufferviews. It will probably be easier to do that in a more
1180 // automated way in CursorSlice code. (JMarc 26/09/2001)
1181 #endif
1182                         // correct all cursor parts
1183                         if (same_par) {
1184                                 fixCursorAfterDelete(cur.top(), old.top());
1185                                 need_anchor_change = true;
1186                         }
1187                         return true;
1188                 }
1189         }
1190
1191         // only do our magic if we changed paragraph
1192         if (same_par)
1193                 return false;
1194
1195         // don't delete anything if this is the ONLY paragraph!
1196         if (old.lastpit() == 0)
1197                 return false;
1198
1199         // Do not delete empty paragraphs with keepempty set.
1200         if (oldpar.allowEmpty())
1201                 return false;
1202
1203         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1204                 // Delete old par.
1205                 recordUndo(old, Undo::ATOMIC,
1206                            max(old.pit() - 1, pit_type(0)),
1207                            min(old.pit() + 1, old.lastpit()));
1208                 ParagraphList & plist = old.text()->paragraphs();
1209                 plist.erase(boost::next(plist.begin(), old.pit()));
1210
1211                 // see #warning above
1212                 if (cur.depth() >= old.depth()) {
1213                         CursorSlice & curslice = cur[old.depth() - 1];
1214                         if (&curslice.inset() == &old.inset()
1215                             && curslice.pit() > old.pit()) {
1216                                 --curslice.pit();
1217                                 // since a paragraph has been deleted, all the
1218                                 // insets after `old' have been copied and
1219                                 // their address has changed. Therefore we
1220                                 // need to `regenerate' cur. (JMarc)
1221                                 cur.updateInsets(&(cur.bottom().inset()));
1222                                 need_anchor_change = true;
1223                         }
1224                 }
1225                 return true;
1226         }
1227
1228         if (oldpar.stripLeadingSpaces())
1229                 need_anchor_change = true;
1230
1231         return false;
1232 }
1233
1234
1235 void LyXText::recUndo(LCursor & cur, pit_type first, pit_type last) const
1236 {
1237         recordUndo(cur, Undo::ATOMIC, first, last);
1238 }
1239
1240
1241 void LyXText::recUndo(LCursor & cur, pit_type par) const
1242 {
1243         recordUndo(cur, Undo::ATOMIC, par, par);
1244 }
1245
1246 } // namespace lyx