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