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