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