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