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