]> git.lyx.org Git - lyx.git/blob - src/text2.C
Really fix start_of_appendix output
[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         // The following code is necessary because the cursor position past
807         // the last char in a row is logically equivalent to that before
808         // the first char in the next row. That's why insets causing row
809         // divisions -- Newline and display-style insets -- must be treated
810         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
811         // Newline inset, air gap below:
812         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
813                 if (bidi.level(end -1) % 2 == 0)
814                         tmpx -= singleWidth(par, end - 1);
815                 else
816                         tmpx += singleWidth(par, end - 1);
817                 c = end - 1;
818         }
819         // Air gap above display inset:
820         if (row.pos() < end && c >= end && end < par.size()
821             && par.isInset(end) && par.getInset(end)->display()) {
822                 c = end - 1;
823         }
824         // Air gap below display inset:
825         if (row.pos() < end && c >= end && par.isInset(end - 1) 
826             && par.getInset(end - 1)->display()) {
827                 c = end - 1;
828         }
829
830         x = int(tmpx) + xo;
831         return c - row.pos();
832 }
833
834
835 // y is screen coordinate
836 pit_type LyXText::getPitNearY(int y) const
837 {
838         BOOST_ASSERT(!paragraphs().empty());
839         BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
840         CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
841         lyxerr << "LyXText::getPitNearY: y: " << y << " cache size: "
842                 << cc.size() << endl;
843
844         // look for highest numbered paragraph with y coordinate less than given y
845         pit_type pit = 0;
846         int yy = -1;
847         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
848         CoordCache::InnerParPosCache::const_iterator et = cc.end();
849         for (; it != et; ++it) {
850                 lyxerr << "  examining: pit: " << it->first << " y: "
851                         << it->second.y_ << endl;
852                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
853                         pit = it->first;
854                         yy = it->second.y_;
855                 }
856         }
857
858         lyxerr << " found best y: " << yy << " for pit: " << pit << endl;
859         return pit;
860 }
861
862
863 Row const & LyXText::getRowNearY(int y, pit_type pit) const
864 {
865         Paragraph const & par = pars_[pit];
866         int yy = theCoords.get(this, pit).y_ - par.ascent();
867         BOOST_ASSERT(!par.rows().empty());
868         RowList::const_iterator rit = par.rows().begin();
869         RowList::const_iterator const rlast = boost::prior(par.rows().end());
870         for (; rit != rlast; yy += rit->height(), ++rit)
871                 if (yy + rit->height() > y)
872                         break;
873         return *rit;
874 }
875
876
877 // x,y are absolute screen coordinates
878 // sets cursor recursively descending into nested editable insets
879 InsetBase * LyXText::editXY(LCursor & cur, int x, int y) const
880 {
881         pit_type pit = getPitNearY(y);
882         BOOST_ASSERT(pit != -1);
883         Row const & row = getRowNearY(y, pit);
884         bool bound = false;
885
886         int xx = x; // is modified by getColumnNearX
887         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
888         cur.pit() = pit;
889         cur.pos() = pos;
890         cur.boundary() = bound;
891         cur.x_target() = x;
892
893         // try to descend into nested insets
894         InsetBase * inset = checkInsetHit(x, y);
895         lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
896         if (!inset) {
897                 // Either we deconst editXY or better we move current_font
898                 // and real_current_font to LCursor
899                 const_cast<LyXText *>(this)->setCurrentFont(cur);
900                 return 0;
901         }
902
903         // This should be just before or just behind the
904         // cursor position set above.
905         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
906                      || inset == pars_[pit].getInset(pos));
907         // Make sure the cursor points to the position before
908         // this inset.
909         if (inset == pars_[pit].getInset(pos - 1))
910                 --cur.pos();
911         inset = inset->editXY(cur, x, y);
912         if (cur.top().text() == this)
913                 const_cast<LyXText *>(this)->setCurrentFont(cur);
914         return inset;
915 }
916
917
918 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
919 {
920         if (cur.selection())
921                 return false;
922         if (cur.pos() == cur.lastpos())
923                 return false;
924         InsetBase * inset = cur.nextInset();
925         if (!isHighlyEditableInset(inset))
926                 return false;
927         inset->edit(cur, front);
928         return true;
929 }
930
931
932 bool LyXText::cursorLeft(LCursor & cur)
933 {
934         if (cur.pos() != 0) {
935                 bool boundary = cur.boundary();
936                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
937                 if (!checkAndActivateInset(cur, false)) {
938                         if (false && !boundary &&
939                                         bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
940                                 updateNeeded |=
941                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
942                 }
943                 return updateNeeded;
944         }
945
946         if (cur.pit() != 0) {
947                 // Steps into the paragraph above
948                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
949         }
950         return false;
951 }
952
953
954 bool LyXText::cursorRight(LCursor & cur)
955 {
956         if (false && cur.boundary()) {
957                 return setCursor(cur, cur.pit(), cur.pos(), true, false);
958         }
959
960         if (cur.pos() != cur.lastpos()) {
961                 bool updateNeeded = false;
962                 if (!checkAndActivateInset(cur, true)) {
963                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
964                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
965                                                          cur.pos()))
966                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
967                 }
968                 return updateNeeded;
969         }
970
971         if (cur.pit() != cur.lastpit())
972                 return setCursor(cur, cur.pit() + 1, 0);
973         return false;
974 }
975
976
977 bool LyXText::cursorUp(LCursor & cur)
978 {
979         Paragraph const & par = cur.paragraph();
980         int const row = par.pos2row(cur.pos());
981         int const x = cur.targetX();
982
983         if (!cur.selection()) {
984                 int const y = bv_funcs::getPos(cur).y_;
985                 LCursor old = cur;
986                 editXY(cur, x, y - par.rows()[row].ascent() - 1);
987
988                 // This happens when you move out of an inset.
989                 // And to give the DEPM the possibility of doing
990                 // something we must provide it with two different
991                 // cursors. (Lgb)
992                 LCursor dummy = cur;
993                 if (dummy == old)
994                         ++dummy.pos();
995
996                 return deleteEmptyParagraphMechanism(dummy, old);
997         }
998
999         bool updateNeeded = false;
1000
1001         if (row > 0) {
1002                 updateNeeded |= setCursor(cur, cur.pit(),
1003                                           x2pos(cur.pit(), row - 1, x));
1004         } else if (cur.pit() > 0) {
1005                 --cur.pit();
1006                 //cannot use 'par' now
1007                 updateNeeded |= setCursor(cur, cur.pit(), x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1008         }
1009
1010         cur.x_target() = x;
1011
1012         return updateNeeded;
1013 }
1014
1015
1016 bool LyXText::cursorDown(LCursor & cur)
1017 {
1018         Paragraph const & par = cur.paragraph();
1019         int const row = par.pos2row(cur.pos());
1020         int const x = cur.targetX();
1021
1022         if (!cur.selection()) {
1023                 int const y = bv_funcs::getPos(cur).y_;
1024                 LCursor old = cur;
1025                 editXY(cur, x, y + par.rows()[row].descent() + 1);
1026
1027                 // This happens when you move out of an inset.
1028                 // And to give the DEPM the possibility of doing
1029                 // something we must provide it with two different
1030                 // cursors. (Lgb)
1031                 LCursor dummy = cur;
1032                 if (dummy == old)
1033                         ++dummy.pos();
1034
1035                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1036
1037                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1038                 if (changed)
1039                         cur = dummy;
1040
1041                 return changed;
1042
1043         }
1044
1045         bool updateNeeded = false;
1046
1047         if (row + 1 < int(par.rows().size())) {
1048                 updateNeeded |= setCursor(cur, cur.pit(),
1049                                           x2pos(cur.pit(), row + 1, x));
1050         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1051                 ++cur.pit();
1052                 updateNeeded |= setCursor(cur, cur.pit(),
1053                                           x2pos(cur.pit(), 0, x));
1054         }
1055
1056         cur.x_target() = x;
1057
1058         return updateNeeded;
1059 }
1060
1061
1062 bool LyXText::cursorUpParagraph(LCursor & cur)
1063 {
1064         bool updated = false;
1065         if (cur.pos() > 0)
1066                 updated = setCursor(cur, cur.pit(), 0);
1067         else if (cur.pit() != 0)
1068                 updated = setCursor(cur, cur.pit() - 1, 0);
1069         return updated;
1070 }
1071
1072
1073 bool LyXText::cursorDownParagraph(LCursor & cur)
1074 {
1075         bool updated = false;
1076         if (cur.pit() != cur.lastpit())
1077                 updated = setCursor(cur, cur.pit() + 1, 0);
1078         else
1079                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1080         return updated;
1081 }
1082
1083
1084 // fix the cursor `cur' after a characters has been deleted at `where'
1085 // position. Called by deleteEmptyParagraphMechanism
1086 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1087 {
1088         // Do nothing if cursor is not in the paragraph where the
1089         // deletion occured,
1090         if (cur.pit() != where.pit())
1091                 return;
1092
1093         // If cursor position is after the deletion place update it
1094         if (cur.pos() > where.pos())
1095                 --cur.pos();
1096
1097         // Check also if we don't want to set the cursor on a spot behind the
1098         // pagragraph because we erased the last character.
1099         if (cur.pos() > cur.lastpos())
1100                 cur.pos() = cur.lastpos();
1101 }
1102
1103
1104 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1105 {
1106         // Would be wrong to delete anything if we have a selection.
1107         if (cur.selection())
1108                 return false;
1109
1110         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1111         Paragraph const & oldpar = pars_[old.pit()];
1112
1113         // We allow all kinds of "mumbo-jumbo" when freespacing.
1114         if (oldpar.isFreeSpacing())
1115                 return false;
1116
1117         /* Ok I'll put some comments here about what is missing.
1118            I have fixed BackSpace (and thus Delete) to not delete
1119            double-spaces automagically. I have also changed Cut,
1120            Copy and Paste to hopefully do some sensible things.
1121            There are still some small problems that can lead to
1122            double spaces stored in the document file or space at
1123            the beginning of paragraphs(). This happens if you have
1124            the cursor between to spaces and then save. Or if you
1125            cut and paste and the selection have a space at the
1126            beginning and then save right after the paste. I am
1127            sure none of these are very hard to fix, but I will
1128            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1129            that I can get some feedback. (Lgb)
1130         */
1131
1132         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1133         // delete the LineSeparator.
1134         // MISSING
1135
1136         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1137         // delete the LineSeparator.
1138         // MISSING
1139
1140         // If the chars around the old cursor were spaces, delete one of them.
1141         if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1142
1143                 // Only if the cursor has really moved.
1144                 if (old.pos() > 0
1145                     && old.pos() < oldpar.size()
1146                     && oldpar.isLineSeparator(old.pos())
1147                     && oldpar.isLineSeparator(old.pos() - 1)) {
1148                         // We need to set the text to Change::INSERTED to
1149                         // get it erased properly
1150                         pars_[old.pit()].setChange(old.pos() -1,
1151                                 Change::INSERTED);
1152                         pars_[old.pit()].erase(old.pos() - 1);
1153 #ifdef WITH_WARNINGS
1154 #warning This will not work anymore when we have multiple views of the same buffer
1155 // In this case, we will have to correct also the cursors held by
1156 // other bufferviews. It will probably be easier to do that in a more
1157 // automated way in CursorSlice code. (JMarc 26/09/2001)
1158 #endif
1159                         // correct all cursor parts
1160                         fixCursorAfterDelete(cur.top(), old.top());
1161 #ifdef WITH_WARNINGS
1162 #warning DEPM, look here
1163 #endif
1164                         //fixCursorAfterDelete(cur.anchor(), old.top());
1165                         return true;
1166                 }
1167         }
1168
1169         // only do our magic if we changed paragraph
1170         if (old.pit() == cur.pit())
1171                 return false;
1172
1173         // don't delete anything if this is the ONLY paragraph!
1174         if (pars_.size() == 1)
1175                 return false;
1176
1177         // Do not delete empty paragraphs with keepempty set.
1178         if (oldpar.allowEmpty())
1179                 return false;
1180
1181         // record if we have deleted a paragraph
1182         // we can't possibly have deleted a paragraph before this point
1183         bool deleted = false;
1184
1185         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1186                 // ok, we will delete something
1187                 deleted = true;
1188
1189                 bool selection_position_was_oldcursor_position =
1190                         cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1191
1192                 // This is a bit of a overkill. We change the old and the cur par
1193                 // at max, certainly not everything in between...
1194                 recUndo(old.pit(), cur.pit());
1195
1196                 // Delete old par.
1197                 pars_.erase(pars_.begin() + old.pit());
1198
1199                 // Update cursor par offset if necessary.
1200                 // Some 'iterator registration' would be nice that takes care of
1201                 // such events. Maybe even signal/slot?
1202                 if (cur.pit() > old.pit())
1203                         --cur.pit();
1204 #ifdef WITH_WARNINGS
1205 #warning DEPM, look here
1206 #endif
1207 //              if (cur.anchor().pit() > old.pit())
1208 //                      --cur.anchor().pit();
1209
1210                 if (selection_position_was_oldcursor_position) {
1211                         // correct selection
1212                         cur.resetAnchor();
1213                 }
1214         }
1215
1216         if (deleted)
1217                 return true;
1218
1219         if (pars_[old.pit()].stripLeadingSpaces())
1220                 cur.resetAnchor();
1221
1222         return false;
1223 }
1224
1225
1226 ParagraphList & LyXText::paragraphs() const
1227 {
1228         return const_cast<ParagraphList &>(pars_);
1229 }
1230
1231
1232 void LyXText::recUndo(pit_type first, pit_type last) const
1233 {
1234         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1235 }
1236
1237
1238 void LyXText::recUndo(pit_type par) const
1239 {
1240         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1241 }
1242
1243
1244 int defaultRowHeight()
1245 {
1246         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1247 }