]> git.lyx.org Git - lyx.git/blob - src/text2.C
move definition of variables to the main cmake file
[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/InsetMathHull.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         BOOST_ASSERT(this == cur.text());
723         cur.boundary(boundary);
724         setCursor(cur.top(), par, pos);
725         cur.setTargetX();
726         if (setfont)
727                 setCurrentFont(cur);
728 }
729
730
731 void LyXText::setCurrentFont(LCursor & cur)
732 {
733         BOOST_ASSERT(this == cur.text());
734         pos_type pos = cur.pos();
735         Paragraph & par = cur.paragraph();
736
737         if (cur.boundary() && pos > 0)
738                 --pos;
739
740         if (pos > 0) {
741                 if (pos == cur.lastpos())
742                         --pos;
743                 else // potentional bug... BUG (Lgb)
744                         if (par.isSeparator(pos)) {
745                                 if (pos > cur.textRow().pos() &&
746                                     bidi.level(pos) % 2 ==
747                                     bidi.level(pos - 1) % 2)
748                                         --pos;
749                                 else if (pos + 1 < cur.lastpos())
750                                         ++pos;
751                         }
752         }
753
754         BufferParams const & bufparams = cur.buffer().params();
755         current_font = par.getFontSettings(bufparams, pos);
756         real_current_font = getFont(par, pos);
757
758         if (cur.pos() == cur.lastpos()
759             && bidi.isBoundary(cur.buffer(), par, cur.pos())
760             && !cur.boundary()) {
761                 Language const * lang = par.getParLanguage(bufparams);
762                 current_font.setLanguage(lang);
763                 current_font.setNumber(LyXFont::OFF);
764                 real_current_font.setLanguage(lang);
765                 real_current_font.setNumber(LyXFont::OFF);
766         }
767 }
768
769
770 // x is an absolute screen coord
771 // returns the column near the specified x-coordinate of the row
772 // x is set to the real beginning of this column
773 pos_type LyXText::getColumnNearX(pit_type const pit,
774                                  Row const & row, int & x, bool & boundary) const
775 {
776         int const xo = theCoords.get(this, pit).x_;
777         x -= xo;
778         RowMetrics const r = computeRowMetrics(pit, row);
779         Paragraph const & par = pars_[pit];
780
781         pos_type vc = row.pos();
782         pos_type end = row.endpos();
783         pos_type c = 0;
784         LyXLayout_ptr const & layout = par.layout();
785
786         bool left_side = false;
787
788         pos_type body_pos = par.beginOfBody();
789
790         double tmpx = r.x;
791         double last_tmpx = tmpx;
792
793         if (body_pos > 0 &&
794             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
795                 body_pos = 0;
796
797         // check for empty row
798         if (vc == end) {
799                 x = int(tmpx) + xo;
800                 return 0;
801         }
802
803         while (vc < end && tmpx <= x) {
804                 c = bidi.vis2log(vc);
805                 last_tmpx = tmpx;
806                 if (body_pos > 0 && c == body_pos - 1) {
807                         string lsep = layout->labelsep;
808                         docstring dlsep(lsep.begin(), lsep.end());
809                         tmpx += r.label_hfill +
810                                 font_metrics::width(dlsep, getLabelFont(par));
811                         if (par.isLineSeparator(body_pos - 1))
812                                 tmpx -= singleWidth(par, body_pos - 1);
813                 }
814
815                 if (hfillExpansion(par, row, c)) {
816                         tmpx += singleWidth(par, c);
817                         if (c >= body_pos)
818                                 tmpx += r.hfill;
819                         else
820                                 tmpx += r.label_hfill;
821                 } else if (par.isSeparator(c)) {
822                         tmpx += singleWidth(par, c);
823                         if (c >= body_pos)
824                                 tmpx += r.separator;
825                 } else {
826                         tmpx += singleWidth(par, c);
827                 }
828                 ++vc;
829         }
830
831         if ((tmpx + last_tmpx) / 2 > x) {
832                 tmpx = last_tmpx;
833                 left_side = true;
834         }
835
836         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
837
838         boundary = false;
839         // This (rtl_support test) is not needed, but gives
840         // some speedup if rtl_support == false
841         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
842
843         // If lastrow is false, we don't need to compute
844         // the value of rtl.
845         bool const rtl = lastrow ? isRTL(par) : false;
846         if (lastrow &&
847             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
848              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
849                 c = end;
850         else if (vc == row.pos()) {
851                 c = bidi.vis2log(vc);
852                 if (bidi.level(c) % 2 == 1)
853                         ++c;
854         } else {
855                 c = bidi.vis2log(vc - 1);
856                 bool const rtl = (bidi.level(c) % 2 == 1);
857                 if (left_side == rtl) {
858                         ++c;
859                         boundary = bidi.isBoundary(*bv()->buffer(), par, c);
860                 }
861         }
862
863 // I believe this code is not needed anymore (Jug 20050717)
864 #if 0
865         // The following code is necessary because the cursor position past
866         // the last char in a row is logically equivalent to that before
867         // the first char in the next row. That's why insets causing row
868         // divisions -- Newline and display-style insets -- must be treated
869         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
870         // Newline inset, air gap below:
871         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
872                 if (bidi.level(end -1) % 2 == 0)
873                         tmpx -= singleWidth(par, end - 1);
874                 else
875                         tmpx += singleWidth(par, end - 1);
876                 c = end - 1;
877         }
878
879         // Air gap above display inset:
880         if (row.pos() < end && c >= end && end < par.size()
881             && par.isInset(end) && par.getInset(end)->display()) {
882                 c = end - 1;
883         }
884         // Air gap below display inset:
885         if (row.pos() < end && c >= end && par.isInset(end - 1)
886             && par.getInset(end - 1)->display()) {
887                 c = end - 1;
888         }
889 #endif
890
891         x = int(tmpx) + xo;
892         pos_type const col = c - row.pos();
893
894         if (!c || end == par.size())
895                 return col;
896
897         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
898                 boundary = true;
899                 return col;
900         }
901
902         return min(col, end - 1 - row.pos());
903 }
904
905
906 // y is screen coordinate
907 pit_type LyXText::getPitNearY(int y) const
908 {
909         BOOST_ASSERT(!paragraphs().empty());
910         BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
911         CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
912         lyxerr[Debug::DEBUG]
913                 << BOOST_CURRENT_FUNCTION
914                 << ": y: " << y << " cache size: " << cc.size()
915                 << endl;
916
917         // look for highest numbered paragraph with y coordinate less than given y
918         pit_type pit = 0;
919         int yy = -1;
920         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
921         CoordCache::InnerParPosCache::const_iterator et = cc.end();
922         for (; it != et; ++it) {
923                 lyxerr[Debug::DEBUG]
924                         << BOOST_CURRENT_FUNCTION
925                         << "  examining: pit: " << it->first
926                         << " y: " << it->second.y_
927                         << endl;
928
929                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
930                         pit = it->first;
931                         yy = it->second.y_;
932                 }
933         }
934
935         lyxerr[Debug::DEBUG]
936                 << BOOST_CURRENT_FUNCTION
937                 << ": found best y: " << yy << " for pit: " << pit
938                 << endl;
939
940         return pit;
941 }
942
943
944 Row const & LyXText::getRowNearY(int y, pit_type pit) const
945 {
946         Paragraph const & par = pars_[pit];
947         int yy = theCoords.get(this, pit).y_ - par.ascent();
948         BOOST_ASSERT(!par.rows().empty());
949         RowList::const_iterator rit = par.rows().begin();
950         RowList::const_iterator const rlast = boost::prior(par.rows().end());
951         for (; rit != rlast; yy += rit->height(), ++rit)
952                 if (yy + rit->height() > y)
953                         break;
954         return *rit;
955 }
956
957
958 // x,y are absolute screen coordinates
959 // sets cursor recursively descending into nested editable insets
960 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
961 {
962         pit_type pit = getPitNearY(y);
963         BOOST_ASSERT(pit != -1);
964         Row const & row = getRowNearY(y, pit);
965         bool bound = false;
966
967         int xx = x; // is modified by getColumnNearX
968         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
969         cur.pit() = pit;
970         cur.pos() = pos;
971         cur.boundary(bound);
972         cur.x_target() = x;
973
974         // try to descend into nested insets
975         InsetBase * inset = checkInsetHit(x, y);
976         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
977         if (!inset) {
978                 // Either we deconst editXY or better we move current_font
979                 // and real_current_font to LCursor
980                 setCurrentFont(cur);
981                 return 0;
982         }
983
984         // This should be just before or just behind the
985         // cursor position set above.
986         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
987                      || inset == pars_[pit].getInset(pos));
988         // Make sure the cursor points to the position before
989         // this inset.
990         if (inset == pars_[pit].getInset(pos - 1))
991                 --cur.pos();
992         inset = inset->editXY(cur, x, y);
993         if (cur.top().text() == this)
994                 setCurrentFont(cur);
995         return inset;
996 }
997
998
999 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1000 {
1001         if (cur.selection())
1002                 return false;
1003         if (cur.pos() == cur.lastpos())
1004                 return false;
1005         InsetBase * inset = cur.nextInset();
1006         if (!isHighlyEditableInset(inset))
1007                 return false;
1008         inset->edit(cur, front);
1009         return true;
1010 }
1011
1012
1013 bool LyXText::cursorLeft(LCursor & cur)
1014 {
1015         if (!cur.boundary() && cur.pos() > 0 &&
1016             cur.textRow().pos() == cur.pos() &&
1017             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1018             !cur.paragraph().isNewline(cur.pos()-1)) {
1019                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1020         }
1021         if (cur.pos() != 0) {
1022                 bool boundary = cur.boundary();
1023                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1024                 if (!checkAndActivateInset(cur, false)) {
1025                         if (false && !boundary &&
1026                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1027                                 updateNeeded |=
1028                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1029                 }
1030                 return updateNeeded;
1031         }
1032
1033         if (cur.pit() != 0) {
1034                 // Steps into the paragraph above
1035                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1036         }
1037         return false;
1038 }
1039
1040
1041 bool LyXText::cursorRight(LCursor & cur)
1042 {
1043         if (cur.pos() != cur.lastpos()) {
1044                 if (cur.boundary())
1045                         return setCursor(cur, cur.pit(), cur.pos(),
1046                                          true, false);
1047
1048                 bool updateNeeded = false;
1049                 if (!checkAndActivateInset(cur, true)) {
1050                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1051                             cur.textRow().endpos() != cur.lastpos() &&
1052                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1053                             !cur.paragraph().isNewline(cur.pos())) {
1054                                 cur.boundary(true);
1055                         }
1056                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1057                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1058                                                      cur.pos()))
1059                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1060                 }
1061                 return updateNeeded;
1062         }
1063
1064         if (cur.pit() != cur.lastpit())
1065                 return setCursor(cur, cur.pit() + 1, 0);
1066         return false;
1067 }
1068
1069
1070 bool LyXText::cursorUp(LCursor & cur)
1071 {
1072         Paragraph const & par = cur.paragraph();
1073         int row;
1074         int const x = cur.targetX();
1075
1076         if (cur.pos() && cur.boundary())
1077                 row = par.pos2row(cur.pos()-1);
1078         else
1079                 row = par.pos2row(cur.pos());
1080
1081         if (!cur.selection()) {
1082                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1083                 LCursor old = cur;
1084                 // Go to middle of previous row. 16 found to work OK;
1085                 // 12 = top/bottom margin of display math
1086                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1087                 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1088                 cur.clearSelection();
1089
1090                 // This happens when you move out of an inset.
1091                 // And to give the DEPM the possibility of doing
1092                 // something we must provide it with two different
1093                 // cursors. (Lgb)
1094                 LCursor dummy = cur;
1095                 if (dummy == old)
1096                         ++dummy.pos();
1097
1098                 return deleteEmptyParagraphMechanism(dummy, old);
1099         }
1100
1101         bool updateNeeded = false;
1102
1103         if (row > 0) {
1104                 updateNeeded |= setCursor(cur, cur.pit(),
1105                                           x2pos(cur.pit(), row - 1, x));
1106         } else if (cur.pit() > 0) {
1107                 --cur.pit();
1108                 //cannot use 'par' now
1109                 updateNeeded |= setCursor(cur, cur.pit(),
1110                                           x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1111         }
1112
1113         cur.x_target() = x;
1114
1115         return updateNeeded;
1116 }
1117
1118
1119 bool LyXText::cursorDown(LCursor & cur)
1120 {
1121         Paragraph const & par = cur.paragraph();
1122         int row;
1123         int const x = cur.targetX();
1124
1125         if (cur.pos() && cur.boundary())
1126                 row = par.pos2row(cur.pos()-1);
1127         else
1128                 row = par.pos2row(cur.pos());
1129
1130         if (!cur.selection()) {
1131                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1132                 LCursor old = cur;
1133                 // To middle of next row
1134                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1135                 editXY(cur, x, y + par.rows()[row].descent() + margin);
1136                 cur.clearSelection();
1137
1138                 // This happens when you move out of an inset.
1139                 // And to give the DEPM the possibility of doing
1140                 // something we must provide it with two different
1141                 // cursors. (Lgb)
1142                 LCursor dummy = cur;
1143                 if (dummy == old)
1144                         ++dummy.pos();
1145
1146                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1147
1148                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1149                 if (changed)
1150                         cur = dummy;
1151
1152                 return changed;
1153         }
1154
1155         bool updateNeeded = false;
1156
1157         if (row + 1 < int(par.rows().size())) {
1158                 updateNeeded |= setCursor(cur, cur.pit(),
1159                                           x2pos(cur.pit(), row + 1, x));
1160         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1161                 ++cur.pit();
1162                 updateNeeded |= setCursor(cur, cur.pit(),
1163                                           x2pos(cur.pit(), 0, x));
1164         }
1165
1166         cur.x_target() = x;
1167
1168         return updateNeeded;
1169 }
1170
1171
1172 bool LyXText::cursorUpParagraph(LCursor & cur)
1173 {
1174         bool updated = false;
1175         if (cur.pos() > 0)
1176                 updated = setCursor(cur, cur.pit(), 0);
1177         else if (cur.pit() != 0)
1178                 updated = setCursor(cur, cur.pit() - 1, 0);
1179         return updated;
1180 }
1181
1182
1183 bool LyXText::cursorDownParagraph(LCursor & cur)
1184 {
1185         bool updated = false;
1186         if (cur.pit() != cur.lastpit())
1187                 updated = setCursor(cur, cur.pit() + 1, 0);
1188         else
1189                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1190         return updated;
1191 }
1192
1193
1194 // fix the cursor `cur' after a characters has been deleted at `where'
1195 // position. Called by deleteEmptyParagraphMechanism
1196 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1197 {
1198         // Do nothing if cursor is not in the paragraph where the
1199         // deletion occured,
1200         if (cur.pit() != where.pit())
1201                 return;
1202
1203         // If cursor position is after the deletion place update it
1204         if (cur.pos() > where.pos())
1205                 --cur.pos();
1206
1207         // Check also if we don't want to set the cursor on a spot behind the
1208         // pagragraph because we erased the last character.
1209         if (cur.pos() > cur.lastpos())
1210                 cur.pos() = cur.lastpos();
1211 }
1212
1213
1214 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1215 {
1216         // Would be wrong to delete anything if we have a selection.
1217         if (cur.selection())
1218                 return false;
1219
1220         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1221         // old should point to us
1222         BOOST_ASSERT(old.text() == this);
1223
1224         Paragraph & oldpar = old.paragraph();
1225
1226         // We allow all kinds of "mumbo-jumbo" when freespacing.
1227         if (oldpar.isFreeSpacing())
1228                 return false;
1229
1230         /* Ok I'll put some comments here about what is missing.
1231            There are still some small problems that can lead to
1232            double spaces stored in the document file or space at
1233            the beginning of paragraphs(). This happens if you have
1234            the cursor between to spaces and then save. Or if you
1235            cut and paste and the selection have a space at the
1236            beginning and then save right after the paste. (Lgb)
1237         */
1238
1239         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1240         // delete the LineSeparator.
1241         // MISSING
1242
1243         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1244         // delete the LineSeparator.
1245         // MISSING
1246
1247         bool const same_inset = &old.inset() == &cur.inset();
1248         bool const same_par = same_inset && old.pit() == cur.pit();
1249         bool const same_par_pos = same_par && old.pos() == cur.pos();
1250
1251         // If the chars around the old cursor were spaces, delete one of them.
1252         if (!same_par_pos) {
1253                 // Only if the cursor has really moved.
1254                 if (old.pos() > 0
1255                     && old.pos() < oldpar.size()
1256                     && oldpar.isLineSeparator(old.pos())
1257                     && oldpar.isLineSeparator(old.pos() - 1)
1258                     && oldpar.lookupChange(old.pos() - 1) != Change::DELETED) {
1259                         // We need to set the text to Change::INSERTED to
1260                         // get it erased properly
1261                         oldpar.setChange(old.pos() -1, Change::INSERTED);
1262                         oldpar.erase(old.pos() - 1);
1263 #ifdef WITH_WARNINGS
1264 #warning This will not work anymore when we have multiple views of the same buffer
1265 // In this case, we will have to correct also the cursors held by
1266 // other bufferviews. It will probably be easier to do that in a more
1267 // automated way in CursorSlice code. (JMarc 26/09/2001)
1268 #endif
1269                         // correct all cursor parts
1270                         if (same_par) {
1271                                 fixCursorAfterDelete(cur.top(), old.top());
1272                                 cur.resetAnchor();
1273                         }
1274                         return true;
1275                 }
1276         }
1277
1278         // only do our magic if we changed paragraph
1279         if (same_par)
1280                 return false;
1281
1282         // don't delete anything if this is the ONLY paragraph!
1283         if (old.lastpit() == 0)
1284                 return false;
1285
1286         // Do not delete empty paragraphs with keepempty set.
1287         if (oldpar.allowEmpty())
1288                 return false;
1289
1290         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1291                 // Delete old par.
1292                 recordUndo(old, Undo::ATOMIC,
1293                            max(old.pit() - 1, pit_type(0)),
1294                            min(old.pit() + 1, old.lastpit()));
1295                 ParagraphList & plist = old.text()->paragraphs();
1296                 plist.erase(boost::next(plist.begin(), old.pit()));
1297
1298                 // see #warning above
1299                 if (cur.depth() >= old.depth()) {
1300                         CursorSlice & curslice = cur[old.depth() - 1];
1301                         if (&curslice.inset() == &old.inset()
1302                             && curslice.pit() > old.pit()) {
1303                                 --curslice.pit();
1304                                 // since a paragraph has been deleted, all the
1305                                 // insets after `old' have been copied and
1306                                 // their address has changed. Therefore we
1307                                 // need to `regenerate' cur. (JMarc)
1308                                 cur.updateInsets(&(cur.bottom().inset()));
1309                                 cur.resetAnchor();
1310                         }
1311                 }
1312                 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1313                 //ParIterator par_it(old);
1314                 //updateLabels(old.buffer(), par_it);
1315                 // So for now we do the full update:
1316                 updateLabels(old.buffer());
1317                 return true;
1318         }
1319
1320         if (oldpar.stripLeadingSpaces())
1321                 cur.resetAnchor();
1322
1323         return false;
1324 }
1325
1326
1327 void LyXText::recUndo(pit_type first, pit_type last) const
1328 {
1329         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1330 }
1331
1332
1333 void LyXText::recUndo(pit_type par) const
1334 {
1335         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1336 }
1337
1338
1339 int defaultRowHeight()
1340 {
1341         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1342 }