]> git.lyx.org Git - lyx.git/blob - src/text2.C
353527462c52f995eca23aa1798945d3383e7d81
[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
66 namespace lyx {
67
68 using std::endl;
69 using std::ostringstream;
70 using std::string;
71 using std::max;
72 using std::min;
73
74
75 LyXText::LyXText(BufferView * bv)
76         : maxwidth_(bv ? bv->workWidth() : 100),
77           current_font(LyXFont::ALL_INHERIT),
78           background_color_(LColor::background),
79           bv_owner(bv),
80           autoBreakRows_(false)
81 {}
82
83
84 void LyXText::init(BufferView * bv)
85 {
86         BOOST_ASSERT(bv);
87         bv_owner = bv;
88         maxwidth_ = bv->workWidth();
89         dim_.wid = maxwidth_;
90         dim_.asc = 10;
91         dim_.des = 10;
92
93         pit_type const end = paragraphs().size();
94         for (pit_type pit = 0; pit != end; ++pit)
95                 pars_[pit].rows().clear();
96
97         updateLabels(*bv->buffer());
98 }
99
100
101 bool LyXText::isMainText() const
102 {
103         return &bv()->buffer()->text() == this;
104 }
105
106
107 //takes screen x,y coordinates
108 InsetBase * LyXText::checkInsetHit(int x, int y) const
109 {
110         pit_type pit = getPitNearY(y);
111         BOOST_ASSERT(pit != -1);
112
113         Paragraph const & par = pars_[pit];
114
115         lyxerr[Debug::DEBUG]
116                 << BOOST_CURRENT_FUNCTION
117                 << ": x: " << x
118                 << " y: " << y
119                 << "  pit: " << pit
120                 << endl;
121         InsetList::const_iterator iit = par.insetlist.begin();
122         InsetList::const_iterator iend = par.insetlist.end();
123         for (; iit != iend; ++iit) {
124                 InsetBase * inset = iit->inset;
125 #if 1
126                 lyxerr[Debug::DEBUG]
127                         << BOOST_CURRENT_FUNCTION
128                         << ": examining inset " << inset << endl;
129
130                 if (bv()->coordCache().getInsets().has(inset))
131                         lyxerr[Debug::DEBUG]
132                                 << BOOST_CURRENT_FUNCTION
133                                 << ": xo: " << inset->xo(*bv()) << "..."
134                                 << inset->xo(*bv()) + inset->width()
135                                 << " yo: " << inset->yo(*bv()) - inset->ascent()
136                                 << "..."
137                                 << inset->yo(*bv()) + inset->descent()
138                                 << endl;
139                 else
140                         lyxerr[Debug::DEBUG]
141                                 << BOOST_CURRENT_FUNCTION
142                                 << ": inset has no cached position" << endl;
143 #endif
144                 if (inset->covers(*bv(), x, y)) {
145                         lyxerr[Debug::DEBUG]
146                                 << BOOST_CURRENT_FUNCTION
147                                 << ": Hit inset: " << inset << endl;
148                         return inset;
149                 }
150         }
151         lyxerr[Debug::DEBUG]
152                 << BOOST_CURRENT_FUNCTION
153                 << ": No inset hit. " << endl;
154         return 0;
155 }
156
157
158
159 // Gets the fully instantiated font at a given position in a paragraph
160 // Basically the same routine as Paragraph::getFont() in paragraph.C.
161 // The difference is that this one is used for displaying, and thus we
162 // are allowed to make cosmetic improvements. For instance make footnotes
163 // smaller. (Asger)
164 LyXFont LyXText::getFont(Paragraph const & par, pos_type const pos) const
165 {
166         BOOST_ASSERT(pos >= 0);
167
168         LyXLayout_ptr const & layout = par.layout();
169 #ifdef WITH_WARNINGS
170 #warning broken?
171 #endif
172         BufferParams const & params = bv()->buffer()->params();
173         pos_type const body_pos = par.beginOfBody();
174
175         // We specialize the 95% common case:
176         if (!par.getDepth()) {
177                 LyXFont f = par.getFontSettings(params, pos);
178                 if (!isMainText())
179                         applyOuterFont(f);
180                 LyXFont lf;
181                 LyXFont rlf;
182                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
183                         lf = layout->labelfont;
184                         rlf = layout->reslabelfont;
185                 } else {
186                         lf = layout->font;
187                         rlf = layout->resfont;
188                 }
189                 // In case the default family has been customized
190                 if (lf.family() == LyXFont::INHERIT_FAMILY)
191                         rlf.setFamily(params.getFont().family());
192                 return f.realize(rlf);
193         }
194
195         // The uncommon case need not be optimized as much
196         LyXFont layoutfont;
197         if (pos < body_pos)
198                 layoutfont = layout->labelfont;
199         else
200                 layoutfont = layout->font;
201
202         LyXFont font = par.getFontSettings(params, pos);
203         font.realize(layoutfont);
204
205         if (!isMainText())
206                 applyOuterFont(font);
207
208         // Find the pit value belonging to paragraph. This will not break
209         // even if pars_ would not be a vector anymore.
210         // Performance appears acceptable.
211
212         pit_type pit = pars_.size();
213         for (pit_type it = 0; it < pit; ++it)
214                 if (&pars_[it] == &par) {
215                         pit = it;
216                         break;
217                 }
218         // Realize against environment font information
219         // NOTE: the cast to pit_type should be removed when pit_type
220         // changes to a unsigned integer.
221         if (pit < pit_type(pars_.size()))
222                 font.realize(outerFont(pit, pars_));
223
224         // Realize with the fonts of lesser depth.
225         font.realize(params.getFont());
226
227         return font;
228 }
229
230 // There are currently two font mechanisms in LyX:
231 // 1. The font attributes in a lyxtext, and
232 // 2. The inset-specific font properties, defined in an inset's
233 // metrics() and draw() methods and handed down the inset chain through
234 // the pi/mi parameters, and stored locally in a lyxtext in font_.
235 // This is where the two are integrated in the final fully realized
236 // font.
237 void LyXText::applyOuterFont(LyXFont & font) const {
238         LyXFont lf(font_);
239         lf.reduce(bv()->buffer()->params().getFont());
240         lf.realize(font);
241         lf.setLanguage(font.language());
242         font = lf;
243 }
244
245
246 LyXFont LyXText::getLayoutFont(pit_type const pit) const
247 {
248         LyXLayout_ptr const & layout = pars_[pit].layout();
249
250         if (!pars_[pit].getDepth())  {
251                 LyXFont lf = layout->resfont;
252                 // In case the default family has been customized
253                 if (layout->font.family() == LyXFont::INHERIT_FAMILY)
254                         lf.setFamily(bv()->buffer()->params().getFont().family());
255                 return lf;
256         }
257
258         LyXFont font = layout->font;
259         // Realize with the fonts of lesser depth.
260         //font.realize(outerFont(pit, paragraphs()));
261         font.realize(bv()->buffer()->params().getFont());
262
263         return font;
264 }
265
266
267 LyXFont LyXText::getLabelFont(Paragraph const & par) const
268 {
269         LyXLayout_ptr const & layout = par.layout();
270
271         if (!par.getDepth()) {
272                 LyXFont lf = layout->reslabelfont;
273                 // In case the default family has been customized
274                 if (layout->labelfont.family() == LyXFont::INHERIT_FAMILY)
275                         lf.setFamily(bv()->buffer()->params().getFont().family());
276                 return lf;
277         }
278
279         LyXFont font = layout->labelfont;
280         // Realize with the fonts of lesser depth.
281         font.realize(bv()->buffer()->params().getFont());
282
283         return font;
284 }
285
286
287 void LyXText::setCharFont(pit_type pit, pos_type pos, LyXFont const & fnt)
288 {
289         LyXFont font = fnt;
290         LyXLayout_ptr const & layout = pars_[pit].layout();
291
292         // Get concrete layout font to reduce against
293         LyXFont layoutfont;
294
295         if (pos < pars_[pit].beginOfBody())
296                 layoutfont = layout->labelfont;
297         else
298                 layoutfont = layout->font;
299
300         // Realize against environment font information
301         if (pars_[pit].getDepth()) {
302                 pit_type tp = pit;
303                 while (!layoutfont.resolved() &&
304                        tp != pit_type(paragraphs().size()) &&
305                        pars_[tp].getDepth()) {
306                         tp = outerHook(tp, paragraphs());
307                         if (tp != pit_type(paragraphs().size()))
308                                 layoutfont.realize(pars_[tp].layout()->font);
309                 }
310         }
311
312         // Inside inset, apply the inset's font attributes if any
313         // (charstyle!)
314         if (!isMainText())
315                 layoutfont.realize(font_);
316
317         layoutfont.realize(bv()->buffer()->params().getFont());
318
319         // Now, reduce font against full layout font
320         font.reduce(layoutfont);
321
322         pars_[pit].setFont(pos, font);
323 }
324
325
326 // return past-the-last paragraph influenced by a layout change on pit
327 pit_type LyXText::undoSpan(pit_type pit)
328 {
329         pit_type end = paragraphs().size();
330         pit_type nextpit = pit + 1;
331         if (nextpit == end)
332                 return nextpit;
333         //because of parindents
334         if (!pars_[pit].getDepth())
335                 return boost::next(nextpit);
336         //because of depth constrains
337         for (; nextpit != end; ++pit, ++nextpit) {
338                 if (!pars_[pit].getDepth())
339                         break;
340         }
341         return nextpit;
342 }
343
344
345 void LyXText::setLayout(pit_type start, pit_type end, string const & layout)
346 {
347         BOOST_ASSERT(start != end);
348
349         BufferParams const & bufparams = bv()->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(start, undopit - 1);
385         setLayout(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(pars_[pit]);
460                 else
461                         layoutfont = getLayoutFont(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(dit.paragraph(), dit.pos());
492                         f.update(font, params.language, toggleall);
493                         setCharFont(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.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(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(pit_type const pit,
772                                  Row const & row, int & x, bool & boundary) const
773 {
774         int const xo = bv()->coordCache().get(this, pit).x_;
775         x -= xo;
776         RowMetrics const r = computeRowMetrics(pit, row);
777         Paragraph const & par = pars_[pit];
778
779         pos_type vc = row.pos();
780         pos_type end = row.endpos();
781         pos_type c = 0;
782         LyXLayout_ptr const & layout = par.layout();
783
784         bool left_side = false;
785
786         pos_type body_pos = par.beginOfBody();
787
788         double tmpx = r.x;
789         double last_tmpx = tmpx;
790
791         if (body_pos > 0 &&
792             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
793                 body_pos = 0;
794
795         // check for empty row
796         if (vc == end) {
797                 x = int(tmpx) + xo;
798                 return 0;
799         }
800
801         frontend::FontMetrics const & fm 
802                 = theFontMetrics(getLabelFont(par));
803
804         while (vc < end && tmpx <= x) {
805                 c = bidi.vis2log(vc);
806                 last_tmpx = tmpx;
807                 if (body_pos > 0 && c == body_pos - 1) {
808                         // FIXME UNICODE
809                         docstring const lsep = from_utf8(layout->labelsep);
810                         tmpx += r.label_hfill + fm.width(lsep);
811                         if (par.isLineSeparator(body_pos - 1))
812                                 tmpx -= singleWidth(par, body_pos - 1);
813                 }
814
815                 if (hfillExpansion(par, row, c)) {
816                         tmpx += singleWidth(par, c);
817                         if (c >= body_pos)
818                                 tmpx += r.hfill;
819                         else
820                                 tmpx += r.label_hfill;
821                 } else if (par.isSeparator(c)) {
822                         tmpx += singleWidth(par, c);
823                         if (c >= body_pos)
824                                 tmpx += r.separator;
825                 } else {
826                         tmpx += singleWidth(par, c);
827                 }
828                 ++vc;
829         }
830
831         if ((tmpx + last_tmpx) / 2 > x) {
832                 tmpx = last_tmpx;
833                 left_side = true;
834         }
835
836         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
837
838         boundary = false;
839         // This (rtl_support test) is not needed, but gives
840         // some speedup if rtl_support == false
841         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
842
843         // If lastrow is false, we don't need to compute
844         // the value of rtl.
845         bool const rtl = lastrow ? isRTL(par) : false;
846         if (lastrow &&
847             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
848              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
849                 c = end;
850         else if (vc == row.pos()) {
851                 c = bidi.vis2log(vc);
852                 if (bidi.level(c) % 2 == 1)
853                         ++c;
854         } else {
855                 c = bidi.vis2log(vc - 1);
856                 bool const rtl = (bidi.level(c) % 2 == 1);
857                 if (left_side == rtl) {
858                         ++c;
859                         boundary = bidi.isBoundary(*bv()->buffer(), par, c);
860                 }
861         }
862
863 // I believe this code is not needed anymore (Jug 20050717)
864 #if 0
865         // The following code is necessary because the cursor position past
866         // the last char in a row is logically equivalent to that before
867         // the first char in the next row. That's why insets causing row
868         // divisions -- Newline and display-style insets -- must be treated
869         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
870         // Newline inset, air gap below:
871         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
872                 if (bidi.level(end -1) % 2 == 0)
873                         tmpx -= singleWidth(par, end - 1);
874                 else
875                         tmpx += singleWidth(par, end - 1);
876                 c = end - 1;
877         }
878
879         // Air gap above display inset:
880         if (row.pos() < end && c >= end && end < par.size()
881             && par.isInset(end) && par.getInset(end)->display()) {
882                 c = end - 1;
883         }
884         // Air gap below display inset:
885         if (row.pos() < end && c >= end && par.isInset(end - 1)
886             && par.getInset(end - 1)->display()) {
887                 c = end - 1;
888         }
889 #endif
890
891         x = int(tmpx) + xo;
892         pos_type const col = c - row.pos();
893
894         if (!c || end == par.size())
895                 return col;
896
897         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
898                 boundary = true;
899                 return col;
900         }
901
902         return min(col, end - 1 - row.pos());
903 }
904
905
906 // y is screen coordinate
907 pit_type LyXText::getPitNearY(int y) const
908 {
909         BOOST_ASSERT(!paragraphs().empty());
910         BOOST_ASSERT(bv()->coordCache().getParPos().find(this) != bv()->coordCache().getParPos().end());
911         CoordCache::InnerParPosCache const & cc = bv()->coordCache().getParPos().find(this)->second;
912         lyxerr[Debug::DEBUG]
913                 << BOOST_CURRENT_FUNCTION
914                 << ": y: " << y << " cache size: " << cc.size()
915                 << endl;
916
917         // look for highest numbered paragraph with y coordinate less than given y
918         pit_type pit = 0;
919         int yy = -1;
920         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
921         CoordCache::InnerParPosCache::const_iterator et = cc.end();
922         for (; it != et; ++it) {
923                 lyxerr[Debug::DEBUG]
924                         << BOOST_CURRENT_FUNCTION
925                         << "  examining: pit: " << it->first
926                         << " y: " << it->second.y_
927                         << endl;
928
929                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
930                         pit = it->first;
931                         yy = it->second.y_;
932                 }
933         }
934
935         lyxerr[Debug::DEBUG]
936                 << BOOST_CURRENT_FUNCTION
937                 << ": found best y: " << yy << " for pit: " << pit
938                 << endl;
939
940         return pit;
941 }
942
943
944 Row const & LyXText::getRowNearY(int y, pit_type pit) const
945 {
946         Paragraph const & par = pars_[pit];
947         int yy = bv()->coordCache().get(this, pit).y_ - par.ascent();
948         BOOST_ASSERT(!par.rows().empty());
949         RowList::const_iterator rit = par.rows().begin();
950         RowList::const_iterator const rlast = boost::prior(par.rows().end());
951         for (; rit != rlast; yy += rit->height(), ++rit)
952                 if (yy + rit->height() > y)
953                         break;
954         return *rit;
955 }
956
957
958 // x,y are absolute screen coordinates
959 // sets cursor recursively descending into nested editable insets
960 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
961 {
962         pit_type pit = getPitNearY(y);
963         BOOST_ASSERT(pit != -1);
964         Row const & row = getRowNearY(y, pit);
965         bool bound = false;
966
967         int xx = x; // is modified by getColumnNearX
968         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
969         cur.pit() = pit;
970         cur.pos() = pos;
971         cur.boundary(bound);
972         cur.x_target() = x;
973
974         // try to descend into nested insets
975         InsetBase * inset = checkInsetHit(x, y);
976         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
977         if (!inset) {
978                 // Either we deconst editXY or better we move current_font
979                 // and real_current_font to LCursor
980                 setCurrentFont(cur);
981                 return 0;
982         }
983
984         // This should be just before or just behind the
985         // cursor position set above.
986         InsetBase * inset2 = pars_[pit].getInset(pos - 1);
987         InsetBase * inset3 = pars_[pit].getInset(pos);
988         
989         BOOST_ASSERT((pos != 0 && inset == inset2)
990                      || inset == inset3);
991         // Make sure the cursor points to the position before
992         // this inset.
993         if (inset == pars_[pit].getInset(pos - 1))
994                 --cur.pos();
995         inset = inset->editXY(cur, x, y);
996         if (cur.top().text() == this)
997                 setCurrentFont(cur);
998         return inset;
999 }
1000
1001
1002 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1003 {
1004         if (cur.selection())
1005                 return false;
1006         if (cur.pos() == cur.lastpos())
1007                 return false;
1008         InsetBase * inset = cur.nextInset();
1009         if (!isHighlyEditableInset(inset))
1010                 return false;
1011         inset->edit(cur, front);
1012         return true;
1013 }
1014
1015
1016 bool LyXText::cursorLeft(LCursor & cur)
1017 {
1018         if (!cur.boundary() && cur.pos() > 0 &&
1019             cur.textRow().pos() == cur.pos() &&
1020             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
1021             !cur.paragraph().isNewline(cur.pos()-1)) {
1022                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1023         }
1024         if (cur.pos() != 0) {
1025                 bool boundary = cur.boundary();
1026                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1027                 if (!checkAndActivateInset(cur, false)) {
1028                         if (false && !boundary &&
1029                             bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1030                                 updateNeeded |=
1031                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1032                 }
1033                 return updateNeeded;
1034         }
1035
1036         if (cur.pit() != 0) {
1037                 // Steps into the paragraph above
1038                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1039         }
1040         return false;
1041 }
1042
1043
1044 bool LyXText::cursorRight(LCursor & cur)
1045 {
1046         if (cur.pos() != cur.lastpos()) {
1047                 if (cur.boundary())
1048                         return setCursor(cur, cur.pit(), cur.pos(),
1049                                          true, false);
1050
1051                 bool updateNeeded = false;
1052                 if (!checkAndActivateInset(cur, true)) {
1053                         if (cur.textRow().endpos() == cur.pos() + 1 &&
1054                             cur.textRow().endpos() != cur.lastpos() &&
1055                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1056                             !cur.paragraph().isNewline(cur.pos())) {
1057                                 cur.boundary(true);
1058                         }
1059                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1060                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1061                                                      cur.pos()))
1062                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1063                 }
1064                 return updateNeeded;
1065         }
1066
1067         if (cur.pit() != cur.lastpit())
1068                 return setCursor(cur, cur.pit() + 1, 0);
1069         return false;
1070 }
1071
1072
1073 bool LyXText::cursorUp(LCursor & cur)
1074 {
1075         Paragraph const & par = cur.paragraph();
1076         int row;
1077         int const x = cur.targetX();
1078
1079         if (cur.pos() && cur.boundary())
1080                 row = par.pos2row(cur.pos()-1);
1081         else
1082                 row = par.pos2row(cur.pos());
1083
1084         if (!cur.selection()) {
1085                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1086                 LCursor old = cur;
1087                 // Go to middle of previous row. 16 found to work OK;
1088                 // 12 = top/bottom margin of display math
1089                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1090                 editXY(cur, x, y - par.rows()[row].ascent() - margin);
1091                 cur.clearSelection();
1092
1093                 // This happens when you move out of an inset.
1094                 // And to give the DEPM the possibility of doing
1095                 // something we must provide it with two different
1096                 // cursors. (Lgb)
1097                 LCursor dummy = cur;
1098                 if (dummy == old)
1099                         ++dummy.pos();
1100
1101                 return deleteEmptyParagraphMechanism(dummy, old);
1102         }
1103
1104         bool updateNeeded = false;
1105
1106         if (row > 0) {
1107                 updateNeeded |= setCursor(cur, cur.pit(),
1108                                           x2pos(cur.pit(), row - 1, x));
1109         } else if (cur.pit() > 0) {
1110                 --cur.pit();
1111                 //cannot use 'par' now
1112                 updateNeeded |= setCursor(cur, cur.pit(),
1113                                           x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1114         }
1115
1116         cur.x_target() = x;
1117
1118         return updateNeeded;
1119 }
1120
1121
1122 bool LyXText::cursorDown(LCursor & cur)
1123 {
1124         Paragraph const & par = cur.paragraph();
1125         int row;
1126         int const x = cur.targetX();
1127
1128         if (cur.pos() && cur.boundary())
1129                 row = par.pos2row(cur.pos()-1);
1130         else
1131                 row = par.pos2row(cur.pos());
1132
1133         if (!cur.selection()) {
1134                 int const y = bv_funcs::getPos(cur.bv(), cur, cur.boundary()).y_;
1135                 LCursor old = cur;
1136                 // To middle of next row
1137                 int const margin = 3 * InsetMathHull::displayMargin() / 2;
1138                 editXY(cur, x, y + par.rows()[row].descent() + margin);
1139                 cur.clearSelection();
1140
1141                 // This happens when you move out of an inset.
1142                 // And to give the DEPM the possibility of doing
1143                 // something we must provide it with two different
1144                 // cursors. (Lgb)
1145                 LCursor dummy = cur;
1146                 if (dummy == old)
1147                         ++dummy.pos();
1148
1149                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1150
1151                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1152                 if (changed)
1153                         cur = dummy;
1154
1155                 return changed;
1156         }
1157
1158         bool updateNeeded = false;
1159
1160         if (row + 1 < int(par.rows().size())) {
1161                 updateNeeded |= setCursor(cur, cur.pit(),
1162                                           x2pos(cur.pit(), row + 1, x));
1163         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1164                 ++cur.pit();
1165                 updateNeeded |= setCursor(cur, cur.pit(),
1166                                           x2pos(cur.pit(), 0, x));
1167         }
1168
1169         cur.x_target() = x;
1170
1171         return updateNeeded;
1172 }
1173
1174
1175 bool LyXText::cursorUpParagraph(LCursor & cur)
1176 {
1177         bool updated = false;
1178         if (cur.pos() > 0)
1179                 updated = setCursor(cur, cur.pit(), 0);
1180         else if (cur.pit() != 0)
1181                 updated = setCursor(cur, cur.pit() - 1, 0);
1182         return updated;
1183 }
1184
1185
1186 bool LyXText::cursorDownParagraph(LCursor & cur)
1187 {
1188         bool updated = false;
1189         if (cur.pit() != cur.lastpit())
1190                 updated = setCursor(cur, cur.pit() + 1, 0);
1191         else
1192                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1193         return updated;
1194 }
1195
1196
1197 // fix the cursor `cur' after a characters has been deleted at `where'
1198 // position. Called by deleteEmptyParagraphMechanism
1199 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1200 {
1201         // Do nothing if cursor is not in the paragraph where the
1202         // deletion occured,
1203         if (cur.pit() != where.pit())
1204                 return;
1205
1206         // If cursor position is after the deletion place update it
1207         if (cur.pos() > where.pos())
1208                 --cur.pos();
1209
1210         // Check also if we don't want to set the cursor on a spot behind the
1211         // pagragraph because we erased the last character.
1212         if (cur.pos() > cur.lastpos())
1213                 cur.pos() = cur.lastpos();
1214 }
1215
1216
1217 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor & old)
1218 {
1219         // Would be wrong to delete anything if we have a selection.
1220         if (cur.selection())
1221                 return false;
1222
1223         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1224         // old should point to us
1225         BOOST_ASSERT(old.text() == this);
1226
1227         Paragraph & oldpar = old.paragraph();
1228
1229         // We allow all kinds of "mumbo-jumbo" when freespacing.
1230         if (oldpar.isFreeSpacing())
1231                 return false;
1232
1233         /* Ok I'll put some comments here about what is missing.
1234            There are still some small problems that can lead to
1235            double spaces stored in the document file or space at
1236            the beginning of paragraphs(). This happens if you have
1237            the cursor between to spaces and then save. Or if you
1238            cut and paste and the selection have a space at the
1239            beginning and then save right after the paste. (Lgb)
1240         */
1241
1242         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1243         // delete the LineSeparator.
1244         // MISSING
1245
1246         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1247         // delete the LineSeparator.
1248         // MISSING
1249
1250         bool const same_inset = &old.inset() == &cur.inset();
1251         bool const same_par = same_inset && old.pit() == cur.pit();
1252         bool const same_par_pos = same_par && old.pos() == cur.pos();
1253
1254         // If the chars around the old cursor were spaces, delete one of them.
1255         if (!same_par_pos) {
1256                 // Only if the cursor has really moved.
1257                 if (old.pos() > 0
1258                     && old.pos() < oldpar.size()
1259                     && oldpar.isLineSeparator(old.pos())
1260                     && oldpar.isLineSeparator(old.pos() - 1)
1261                     && oldpar.lookupChange(old.pos() - 1).type != Change::DELETED) {
1262                         oldpar.erase(old.pos() - 1, false); // do not track changes in DEPM
1263 #ifdef WITH_WARNINGS
1264 #warning This will not work anymore when we have multiple views of the same buffer
1265 // In this case, we will have to correct also the cursors held by
1266 // other bufferviews. It will probably be easier to do that in a more
1267 // automated way in CursorSlice code. (JMarc 26/09/2001)
1268 #endif
1269                         // correct all cursor parts
1270                         if (same_par) {
1271                                 fixCursorAfterDelete(cur.top(), old.top());
1272                                 cur.resetAnchor();
1273                         }
1274                         return true;
1275                 }
1276         }
1277
1278         // only do our magic if we changed paragraph
1279         if (same_par)
1280                 return false;
1281
1282         // don't delete anything if this is the ONLY paragraph!
1283         if (old.lastpit() == 0)
1284                 return false;
1285
1286         // Do not delete empty paragraphs with keepempty set.
1287         if (oldpar.allowEmpty())
1288                 return false;
1289
1290         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1291                 // Delete old par.
1292                 recordUndo(old, Undo::ATOMIC,
1293                            max(old.pit() - 1, pit_type(0)),
1294                            min(old.pit() + 1, old.lastpit()));
1295                 ParagraphList & plist = old.text()->paragraphs();
1296                 plist.erase(boost::next(plist.begin(), old.pit()));
1297
1298                 // see #warning above
1299                 if (cur.depth() >= old.depth()) {
1300                         CursorSlice & curslice = cur[old.depth() - 1];
1301                         if (&curslice.inset() == &old.inset()
1302                             && curslice.pit() > old.pit()) {
1303                                 --curslice.pit();
1304                                 // since a paragraph has been deleted, all the
1305                                 // insets after `old' have been copied and
1306                                 // their address has changed. Therefore we
1307                                 // need to `regenerate' cur. (JMarc)
1308                                 cur.updateInsets(&(cur.bottom().inset()));
1309                                 cur.resetAnchor();
1310                         }
1311                 }
1312                 // There is a crash reported by Edwin Leuven (16/04/2006) because of:
1313                 //ParIterator par_it(old);
1314                 //updateLabels(old.buffer(), par_it);
1315                 // So for now we do the full update:
1316                 updateLabels(old.buffer());
1317                 return true;
1318         }
1319
1320         if (oldpar.stripLeadingSpaces())
1321                 cur.resetAnchor();
1322
1323         return false;
1324 }
1325
1326
1327 void LyXText::recUndo(pit_type first, pit_type last) const
1328 {
1329         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1330 }
1331
1332
1333 void LyXText::recUndo(pit_type par) const
1334 {
1335         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1336 }
1337
1338
1339 int defaultRowHeight()
1340 {
1341         return int(theFontMetrics(LyXFont(LyXFont::ALL_SANE)).maxHeight() *  1.2);
1342 }
1343
1344
1345 } // namespace lyx