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