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