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