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