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