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