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