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