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