]> git.lyx.org Git - lyx.git/blob - src/text2.C
updated format to 243, fix bug 1382, bug 22
[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         Row const & row = cur.paragraph().getRow(cur.pos(),cur.boundary());
485
486         setCursor(cur, cur.pit(), row.pos());
487 }
488
489
490 void LyXText::cursorEnd(LCursor & cur)
491 {
492         BOOST_ASSERT(this == cur.text());
493         // if not on the last row of the par, put the cursor before
494         // the final space exept if I have a spanning inset or one string
495         // is so long that we force a break.
496         pos_type end = cur.textRow().endpos();
497         bool boundary = false;
498         if (!cur.paragraph().isLineSeparator(end-1) &&
499             !cur.paragraph().isNewline(end-1))
500         {
501                 boundary = true;
502         } else if (end != cur.lastpos())
503                 --end;
504         setCursor(cur, cur.pit(), end, true, boundary);
505 }
506
507
508 void LyXText::cursorTop(LCursor & cur)
509 {
510         BOOST_ASSERT(this == cur.text());
511         setCursor(cur, 0, 0);
512 }
513
514
515 void LyXText::cursorBottom(LCursor & cur)
516 {
517         BOOST_ASSERT(this == cur.text());
518         setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
519 }
520
521
522 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
523 {
524         BOOST_ASSERT(this == cur.text());
525         // If the mask is completely neutral, tell user
526         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
527                 // Could only happen with user style
528                 cur.message(_("No font change defined. "
529                         "Use Character under the Layout menu to define font change."));
530                 return;
531         }
532
533         // Try implicit word selection
534         // If there is a change in the language the implicit word selection
535         // is disabled.
536         CursorSlice resetCursor = cur.top();
537         bool implicitSelection =
538                 font.language() == ignore_language
539                 && font.number() == LyXFont::IGNORE
540                 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
541
542         // Set font
543         setFont(cur, font, toggleall);
544
545         // Implicit selections are cleared afterwards
546         // and cursor is set to the original position.
547         if (implicitSelection) {
548                 cur.clearSelection();
549                 cur.top() = resetCursor;
550                 cur.resetAnchor();
551         }
552 }
553
554
555 string LyXText::getStringToIndex(LCursor const & cur)
556 {
557         BOOST_ASSERT(this == cur.text());
558
559         string idxstring;
560         if (cur.selection()) {
561                 idxstring = cur.selectionAsString(false);
562         } else {
563                 // Try implicit word selection. If there is a change
564                 // in the language the implicit word selection is
565                 // disabled.
566                 LCursor tmpcur = cur;
567                 selectWord(tmpcur, lyx::PREVIOUS_WORD);
568
569                 if (!tmpcur.selection())
570                         cur.message(_("Nothing to index!"));
571                 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
572                         cur.message(_("Cannot index more than one paragraph!"));
573                 else
574                         idxstring = tmpcur.selectionAsString(false);
575         }
576
577         return idxstring;
578 }
579
580
581 void LyXText::setParagraph(LCursor & cur,
582         Spacing const & spacing, LyXAlignment align,
583         string const & labelwidthstring, bool noindent)
584 {
585         BOOST_ASSERT(cur.text());
586         // make sure that the depth behind the selection are restored, too
587         pit_type undopit = undoSpan(cur.selEnd().pit());
588         recUndo(cur.selBegin().pit(), undopit - 1);
589
590         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
591                         pit <= end; ++pit) {
592                 Paragraph & par = pars_[pit];
593                 ParagraphParameters & params = par.params();
594                 params.spacing(spacing);
595
596                 // does the layout allow the new alignment?
597                 LyXLayout_ptr const & layout = par.layout();
598
599                 if (align == LYX_ALIGN_LAYOUT)
600                         align = layout->align;
601                 if (align & layout->alignpossible) {
602                         if (align == layout->align)
603                                 params.align(LYX_ALIGN_LAYOUT);
604                         else
605                                 params.align(align);
606                 }
607                 par.setLabelWidthString(labelwidthstring);
608                 params.noindent(noindent);
609         }
610 }
611
612
613 // this really should just insert the inset and not move the cursor.
614 void LyXText::insertInset(LCursor & cur, InsetBase * inset)
615 {
616         BOOST_ASSERT(this == cur.text());
617         BOOST_ASSERT(inset);
618         cur.paragraph().insertInset(cur.pos(), inset);
619 }
620
621
622 // needed to insert the selection
623 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
624 {
625         pit_type pit = cur.pit();
626         pos_type pos = cur.pos();
627         recordUndo(cur);
628
629         // only to be sure, should not be neccessary
630         cur.clearSelection();
631         cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str,
632                 autoBreakRows_);
633
634         cur.resetAnchor();
635         setCursor(cur, cur.pit(), pos);
636         cur.setSelection();
637 }
638
639
640 // turn double CR to single CR, others are converted into one
641 // blank. Then insertStringAsLines is called
642 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
643 {
644         string linestr = str;
645         bool newline_inserted = false;
646
647         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
648                 if (linestr[i] == '\n') {
649                         if (newline_inserted) {
650                                 // we know that \r will be ignored by
651                                 // insertStringAsLines. Of course, it is a dirty
652                                 // trick, but it works...
653                                 linestr[i - 1] = '\r';
654                                 linestr[i] = '\n';
655                         } else {
656                                 linestr[i] = ' ';
657                                 newline_inserted = true;
658                         }
659                 } else if (IsPrintable(linestr[i])) {
660                         newline_inserted = false;
661                 }
662         }
663         insertStringAsLines(cur, linestr);
664 }
665
666
667 bool LyXText::setCursor(LCursor & cur, pit_type par, pos_type pos,
668         bool setfont, bool boundary)
669 {
670         LCursor old = cur;
671         setCursorIntern(cur, par, pos, setfont, boundary);
672         return deleteEmptyParagraphMechanism(cur, old);
673 }
674
675
676 void LyXText::setCursor(CursorSlice & cur, pit_type par,
677         pos_type pos, bool boundary)
678 {
679         BOOST_ASSERT(par != int(paragraphs().size()));
680         cur.pit() = par;
681         cur.pos() = pos;
682
683         // now some strict checking
684         Paragraph & para = getPar(par);
685
686         // None of these should happen, but we're scaredy-cats
687         if (pos < 0) {
688                 lyxerr << "dont like -1" << endl;
689                 BOOST_ASSERT(false);
690         }
691
692         if (pos > para.size()) {
693                 lyxerr << "dont like 1, pos: " << pos
694                        << " size: " << para.size()
695                        << " par: " << par << endl;
696                 BOOST_ASSERT(false);
697         }
698 }
699
700
701 void LyXText::setCursorIntern(LCursor & cur,
702         pit_type par, pos_type pos, bool setfont, bool boundary)
703 {
704         cur.boundary(boundary);
705         setCursor(cur.top(), par, pos, boundary);
706         cur.setTargetX();
707         if (setfont)
708                 setCurrentFont(cur);
709 }
710
711
712 void LyXText::setCurrentFont(LCursor & cur)
713 {
714         BOOST_ASSERT(this == cur.text());
715         pos_type pos = cur.pos();
716         Paragraph & par = cur.paragraph();
717
718         if (cur.boundary() && pos > 0)
719                 --pos;
720
721         if (pos > 0) {
722                 if (pos == cur.lastpos())
723                         --pos;
724                 else // potentional bug... BUG (Lgb)
725                         if (par.isSeparator(pos)) {
726                                 if (pos > cur.textRow().pos() &&
727                                     bidi.level(pos) % 2 ==
728                                     bidi.level(pos - 1) % 2)
729                                         --pos;
730                                 else if (pos + 1 < cur.lastpos())
731                                         ++pos;
732                         }
733         }
734
735         BufferParams const & bufparams = cur.buffer().params();
736         current_font = par.getFontSettings(bufparams, pos);
737         real_current_font = getFont(par, pos);
738
739         if (cur.pos() == cur.lastpos()
740             && bidi.isBoundary(cur.buffer(), par, cur.pos())
741             && !cur.boundary()) {
742                 Language const * lang = par.getParLanguage(bufparams);
743                 current_font.setLanguage(lang);
744                 current_font.setNumber(LyXFont::OFF);
745                 real_current_font.setLanguage(lang);
746                 real_current_font.setNumber(LyXFont::OFF);
747         }
748 }
749
750
751 // x is an absolute screen coord
752 // returns the column near the specified x-coordinate of the row
753 // x is set to the real beginning of this column
754 pos_type LyXText::getColumnNearX(pit_type const pit,
755         Row const & row, int & x, bool & boundary) const
756 {
757         int const xo = theCoords.get(this, pit).x_;
758         x -= xo;
759         RowMetrics const r = computeRowMetrics(pit, row);
760         Paragraph const & par = pars_[pit];
761
762         pos_type vc = row.pos();
763         pos_type end = row.endpos();
764         pos_type c = 0;
765         LyXLayout_ptr const & layout = par.layout();
766
767         bool left_side = false;
768
769         pos_type body_pos = par.beginOfBody();
770
771         double tmpx = r.x;
772         double last_tmpx = tmpx;
773
774         if (body_pos > 0 &&
775             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
776                 body_pos = 0;
777
778         // check for empty row
779         if (vc == end) {
780                 x = int(tmpx) + xo;
781                 return 0;
782         }
783
784         while (vc < end && tmpx <= x) {
785                 c = bidi.vis2log(vc);
786                 last_tmpx = tmpx;
787                 if (body_pos > 0 && c == body_pos - 1) {
788                         tmpx += r.label_hfill +
789                                 font_metrics::width(layout->labelsep, getLabelFont(par));
790                         if (par.isLineSeparator(body_pos - 1))
791                                 tmpx -= singleWidth(par, body_pos - 1);
792                 }
793
794                 if (hfillExpansion(par, row, c)) {
795                         tmpx += singleWidth(par, c);
796                         if (c >= body_pos)
797                                 tmpx += r.hfill;
798                         else
799                                 tmpx += r.label_hfill;
800                 } else if (par.isSeparator(c)) {
801                         tmpx += singleWidth(par, c);
802                         if (c >= body_pos)
803                                 tmpx += r.separator;
804                 } else {
805                         tmpx += singleWidth(par, c);
806                 }
807                 ++vc;
808         }
809
810         if ((tmpx + last_tmpx) / 2 > x) {
811                 tmpx = last_tmpx;
812                 left_side = true;
813         }
814
815         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
816
817         boundary = false;
818         // This (rtl_support test) is not needed, but gives
819         // some speedup if rtl_support == false
820         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
821
822         // If lastrow is false, we don't need to compute
823         // the value of rtl.
824         bool const rtl = lastrow ? isRTL(par) : false;
825         if (lastrow &&
826                  ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
827                   (!rtl && !left_side && vc == end  && x > tmpx + 5)))
828                 c = end;
829         else if (vc == row.pos()) {
830                 c = bidi.vis2log(vc);
831                 if (bidi.level(c) % 2 == 1)
832                         ++c;
833         } else {
834                 c = bidi.vis2log(vc - 1);
835                 bool const rtl = (bidi.level(c) % 2 == 1);
836                 if (left_side == rtl) {
837                         ++c;
838                         boundary = bidi.isBoundary(*bv()->buffer(), par, c);
839                 }
840         }
841
842 // I believe this code is not needed anymore (Jug 20050717)
843 #if 0
844         // The following code is necessary because the cursor position past
845         // the last char in a row is logically equivalent to that before
846         // the first char in the next row. That's why insets causing row
847         // divisions -- Newline and display-style insets -- must be treated
848         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
849         // Newline inset, air gap below:
850         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
851                 if (bidi.level(end -1) % 2 == 0)
852                         tmpx -= singleWidth(par, end - 1);
853                 else
854                         tmpx += singleWidth(par, end - 1);
855                 c = end - 1;
856         }
857
858         // Air gap above display inset:
859         if (row.pos() < end && c >= end && end < par.size()
860             && par.isInset(end) && par.getInset(end)->display()) {
861                 c = end - 1;
862         }
863         // Air gap below display inset:
864         if (row.pos() < end && c >= end && par.isInset(end - 1)
865             && par.getInset(end - 1)->display()) {
866                 c = end - 1;
867         }
868 #endif
869
870         x = int(tmpx) + xo;
871         int const col = c - row.pos();
872
873         if (!c || end == par.size())
874                 return col;
875
876         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
877                 boundary = true;
878                 return col;
879         }
880
881         return min(col, end - 1 - row.pos());
882 }
883
884
885 // y is screen coordinate
886 pit_type LyXText::getPitNearY(int y) const
887 {
888         BOOST_ASSERT(!paragraphs().empty());
889         BOOST_ASSERT(theCoords.getParPos().find(this) != theCoords.getParPos().end());
890         CoordCache::InnerParPosCache const & cc = theCoords.getParPos().find(this)->second;
891         lyxerr[Debug::DEBUG]
892                 << BOOST_CURRENT_FUNCTION
893                 << ": y: " << y << " cache size: " << cc.size()
894                 << endl;
895
896         // look for highest numbered paragraph with y coordinate less than given y
897         pit_type pit = 0;
898         int yy = -1;
899         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
900         CoordCache::InnerParPosCache::const_iterator et = cc.end();
901         for (; it != et; ++it) {
902                 lyxerr[Debug::DEBUG]
903                         << BOOST_CURRENT_FUNCTION
904                         << "  examining: pit: " << it->first
905                         << " y: " << it->second.y_
906                         << endl;
907                 
908                 if (it->first >= pit && int(it->second.y_) - int(pars_[it->first].ascent()) <= y) {
909                         pit = it->first;
910                         yy = it->second.y_;
911                 }
912         }
913
914         lyxerr[Debug::DEBUG]
915                 << BOOST_CURRENT_FUNCTION
916                 << ": found best y: " << yy << " for pit: " << pit
917                 << endl;
918         
919         return pit;
920 }
921
922
923 Row const & LyXText::getRowNearY(int y, pit_type pit) const
924 {
925         Paragraph const & par = pars_[pit];
926         int yy = theCoords.get(this, pit).y_ - par.ascent();
927         BOOST_ASSERT(!par.rows().empty());
928         RowList::const_iterator rit = par.rows().begin();
929         RowList::const_iterator const rlast = boost::prior(par.rows().end());
930         for (; rit != rlast; yy += rit->height(), ++rit)
931                 if (yy + rit->height() > y)
932                         break;
933         return *rit;
934 }
935
936
937 // x,y are absolute screen coordinates
938 // sets cursor recursively descending into nested editable insets
939 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
940 {
941         pit_type pit = getPitNearY(y);
942         BOOST_ASSERT(pit != -1);
943         Row const & row = getRowNearY(y, pit);
944         bool bound = false;
945
946         int xx = x; // is modified by getColumnNearX
947         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
948         cur.pit() = pit;
949         cur.pos() = pos;
950         cur.boundary(bound);
951         cur.x_target() = x;
952
953         // try to descend into nested insets
954         InsetBase * inset = checkInsetHit(x, y);
955         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
956         if (!inset) {
957                 // Either we deconst editXY or better we move current_font
958                 // and real_current_font to LCursor
959                 setCurrentFont(cur);
960                 return 0;
961         }
962
963         // This should be just before or just behind the
964         // cursor position set above.
965         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
966                      || inset == pars_[pit].getInset(pos));
967         // Make sure the cursor points to the position before
968         // this inset.
969         if (inset == pars_[pit].getInset(pos - 1))
970                 --cur.pos();
971         inset = inset->editXY(cur, x, y);
972         if (cur.top().text() == this)
973                 setCurrentFont(cur);
974         return inset;
975 }
976
977
978 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
979 {
980         if (cur.selection())
981                 return false;
982         if (cur.pos() == cur.lastpos())
983                 return false;
984         InsetBase * inset = cur.nextInset();
985         if (!isHighlyEditableInset(inset))
986                 return false;
987         inset->edit(cur, front);
988         return true;
989 }
990
991
992 bool LyXText::cursorLeft(LCursor & cur)
993 {
994         if (!cur.boundary() && cur.pos() > 0 &&
995             cur.textRow().pos() == cur.pos() &&
996             !cur.paragraph().isLineSeparator(cur.pos()-1) &&
997             !cur.paragraph().isNewline(cur.pos()-1))
998         {
999                 return setCursor(cur, cur.pit(), cur.pos(), true, true);
1000         }
1001         if (cur.pos() != 0) {
1002                 bool boundary = cur.boundary();
1003                 bool updateNeeded = setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1004                 if (!checkAndActivateInset(cur, false)) {
1005                         if (false && !boundary &&
1006                                         bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1007                                 updateNeeded |=
1008                                         setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1009                 }
1010                 return updateNeeded;
1011         }
1012
1013         if (cur.pit() != 0) {
1014                 // Steps into the paragraph above
1015                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1016         }
1017         return false;
1018 }
1019
1020
1021 bool LyXText::cursorRight(LCursor & cur)
1022 {
1023         if (cur.boundary()) {
1024                 return setCursor(cur, cur.pit(), cur.pos(), true, false);
1025         }
1026
1027         if (cur.pos() != cur.lastpos()) {
1028                 bool updateNeeded = false;
1029                 if (!checkAndActivateInset(cur, true)) {
1030                         if (cur.textRow().endpos() == (cur.pos() + 1) &&
1031                             !cur.paragraph().isLineSeparator(cur.pos()) &&
1032                             !cur.paragraph().isNewline(cur.pos()))
1033                         {
1034                                 cur.boundary(true);
1035                         }
1036                         updateNeeded |= setCursor(cur, cur.pit(), cur.pos() + 1, true, cur.boundary());
1037                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1038                                                          cur.pos()))
1039                                 updateNeeded |= setCursor(cur, cur.pit(), cur.pos(), true, true);
1040                 }
1041                 return updateNeeded;
1042         }
1043
1044         if (cur.pit() != cur.lastpit())
1045                 return setCursor(cur, cur.pit() + 1, 0);
1046         return false;
1047 }
1048
1049
1050 bool LyXText::cursorUp(LCursor & cur)
1051 {
1052         Paragraph const & par = cur.paragraph();
1053         int row;
1054         int const x = cur.targetX();
1055
1056         if (cur.pos() && cur.boundary())
1057                 row = par.pos2row(cur.pos()-1);
1058         else
1059                 row = par.pos2row(cur.pos());
1060
1061         if (!cur.selection()) {
1062                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1063                 LCursor old = cur;
1064                 editXY(cur, x, y - par.rows()[row].ascent() - 1);
1065
1066                 // This happens when you move out of an inset.
1067                 // And to give the DEPM the possibility of doing
1068                 // something we must provide it with two different
1069                 // cursors. (Lgb)
1070                 LCursor dummy = cur;
1071                 if (dummy == old)
1072                         ++dummy.pos();
1073
1074                 return deleteEmptyParagraphMechanism(dummy, old);
1075         }
1076
1077         bool updateNeeded = false;
1078
1079         if (row > 0) {
1080                 updateNeeded |= setCursor(cur, cur.pit(),
1081                                           x2pos(cur.pit(), row - 1, x));
1082         } else if (cur.pit() > 0) {
1083                 --cur.pit();
1084                 //cannot use 'par' now
1085                 updateNeeded |= setCursor(cur, cur.pit(),
1086       x2pos(cur.pit(), cur.paragraph().rows().size() - 1, x));
1087         }
1088
1089         cur.x_target() = x;
1090
1091         return updateNeeded;
1092 }
1093
1094
1095 bool LyXText::cursorDown(LCursor & cur)
1096 {
1097         Paragraph const & par = cur.paragraph();
1098         int row;
1099         int const x = cur.targetX();
1100
1101         if (cur.pos() && cur.boundary())
1102                 row = par.pos2row(cur.pos()-1);
1103         else
1104                 row = par.pos2row(cur.pos());
1105
1106         if (!cur.selection()) {
1107                 int const y = bv_funcs::getPos(cur, cur.boundary()).y_;
1108                 LCursor old = cur;
1109                 editXY(cur, x, y + par.rows()[row].descent() + 1);
1110
1111                 // This happens when you move out of an inset.
1112                 // And to give the DEPM the possibility of doing
1113                 // something we must provide it with two different
1114                 // cursors. (Lgb)
1115                 LCursor dummy = cur;
1116                 if (dummy == old)
1117                         ++dummy.pos();
1118
1119                 bool const changed = deleteEmptyParagraphMechanism(dummy, old);
1120
1121                 // Make sure that cur gets back whatever happened to dummy(Lgb)
1122                 if (changed)
1123                         cur = dummy;
1124
1125                 return changed;
1126
1127         }
1128
1129         bool updateNeeded = false;
1130
1131         if (row + 1 < int(par.rows().size())) {
1132                 updateNeeded |= setCursor(cur, cur.pit(),
1133                                           x2pos(cur.pit(), row + 1, x));
1134         } else if (cur.pit() + 1 < int(paragraphs().size())) {
1135                 ++cur.pit();
1136                 updateNeeded |= setCursor(cur, cur.pit(),
1137                                           x2pos(cur.pit(), 0, x));
1138         }
1139
1140         cur.x_target() = x;
1141
1142         return updateNeeded;
1143 }
1144
1145
1146 bool LyXText::cursorUpParagraph(LCursor & cur)
1147 {
1148         bool updated = false;
1149         if (cur.pos() > 0)
1150                 updated = setCursor(cur, cur.pit(), 0);
1151         else if (cur.pit() != 0)
1152                 updated = setCursor(cur, cur.pit() - 1, 0);
1153         return updated;
1154 }
1155
1156
1157 bool LyXText::cursorDownParagraph(LCursor & cur)
1158 {
1159         bool updated = false;
1160         if (cur.pit() != cur.lastpit())
1161                 updated = setCursor(cur, cur.pit() + 1, 0);
1162         else
1163                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1164         return updated;
1165 }
1166
1167
1168 // fix the cursor `cur' after a characters has been deleted at `where'
1169 // position. Called by deleteEmptyParagraphMechanism
1170 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1171 {
1172         // Do nothing if cursor is not in the paragraph where the
1173         // deletion occured,
1174         if (cur.pit() != where.pit())
1175                 return;
1176
1177         // If cursor position is after the deletion place update it
1178         if (cur.pos() > where.pos())
1179                 --cur.pos();
1180
1181         // Check also if we don't want to set the cursor on a spot behind the
1182         // pagragraph because we erased the last character.
1183         if (cur.pos() > cur.lastpos())
1184                 cur.pos() = cur.lastpos();
1185 }
1186
1187
1188 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1189 {
1190         // Would be wrong to delete anything if we have a selection.
1191         if (cur.selection())
1192                 return false;
1193
1194         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1195         Paragraph const & oldpar = pars_[old.pit()];
1196
1197         // We allow all kinds of "mumbo-jumbo" when freespacing.
1198         if (oldpar.isFreeSpacing())
1199                 return false;
1200
1201         /* Ok I'll put some comments here about what is missing.
1202            I have fixed BackSpace (and thus Delete) to not delete
1203            double-spaces automagically. I have also changed Cut,
1204            Copy and Paste to hopefully do some sensible things.
1205            There are still some small problems that can lead to
1206            double spaces stored in the document file or space at
1207            the beginning of paragraphs(). This happens if you have
1208            the cursor between to spaces and then save. Or if you
1209            cut and paste and the selection have a space at the
1210            beginning and then save right after the paste. I am
1211            sure none of these are very hard to fix, but I will
1212            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1213            that I can get some feedback. (Lgb)
1214         */
1215
1216         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1217         // delete the LineSeparator.
1218         // MISSING
1219
1220         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1221         // delete the LineSeparator.
1222         // MISSING
1223
1224         // If the chars around the old cursor were spaces, delete one of them.
1225         if (old.pit() != cur.pit() || old.pos() != cur.pos()) {
1226
1227                 // Only if the cursor has really moved.
1228                 if (old.pos() > 0
1229                     && old.pos() < oldpar.size()
1230                     && oldpar.isLineSeparator(old.pos())
1231                     && oldpar.isLineSeparator(old.pos() - 1)) {
1232                         // We need to set the text to Change::INSERTED to
1233                         // get it erased properly
1234                         pars_[old.pit()].setChange(old.pos() -1,
1235                                 Change::INSERTED);
1236                         pars_[old.pit()].erase(old.pos() - 1);
1237 #ifdef WITH_WARNINGS
1238 #warning This will not work anymore when we have multiple views of the same buffer
1239 // In this case, we will have to correct also the cursors held by
1240 // other bufferviews. It will probably be easier to do that in a more
1241 // automated way in CursorSlice code. (JMarc 26/09/2001)
1242 #endif
1243                         // correct all cursor parts
1244                         fixCursorAfterDelete(cur.top(), old.top());
1245 #ifdef WITH_WARNINGS
1246 #warning DEPM, look here
1247 #endif
1248                         //fixCursorAfterDelete(cur.anchor(), old.top());
1249                         return true;
1250                 }
1251         }
1252
1253         // only do our magic if we changed paragraph
1254         if (old.pit() == cur.pit())
1255                 return false;
1256
1257         // don't delete anything if this is the ONLY paragraph!
1258         if (pars_.size() == 1)
1259                 return false;
1260
1261         // Do not delete empty paragraphs with keepempty set.
1262         if (oldpar.allowEmpty())
1263                 return false;
1264
1265         // record if we have deleted a paragraph
1266         // we can't possibly have deleted a paragraph before this point
1267         bool deleted = false;
1268
1269         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1270                 // ok, we will delete something
1271                 deleted = true;
1272
1273                 bool selection_position_was_oldcursor_position =
1274                         cur.anchor().pit() == old.pit() && cur.anchor().pos() == old.pos();
1275
1276                 // This is a bit of a overkill. We change the old and the cur par
1277                 // at max, certainly not everything in between...
1278                 recUndo(old.pit(), cur.pit());
1279
1280                 // Delete old par.
1281                 pars_.erase(pars_.begin() + old.pit());
1282
1283                 // Update cursor par offset if necessary.
1284                 // Some 'iterator registration' would be nice that takes care of
1285                 // such events. Maybe even signal/slot?
1286                 if (cur.pit() > old.pit())
1287                         --cur.pit();
1288 #ifdef WITH_WARNINGS
1289 #warning DEPM, look here
1290 #endif
1291 //              if (cur.anchor().pit() > old.pit())
1292 //                      --cur.anchor().pit();
1293
1294                 if (selection_position_was_oldcursor_position) {
1295                         // correct selection
1296                         cur.resetAnchor();
1297                 }
1298         }
1299
1300         if (deleted) {
1301                 updateCounters(cur.buffer());
1302                 return true;
1303         }
1304
1305         if (pars_[old.pit()].stripLeadingSpaces())
1306                 cur.resetAnchor();
1307
1308         return false;
1309 }
1310
1311
1312 void LyXText::recUndo(pit_type first, pit_type last) const
1313 {
1314         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1315 }
1316
1317
1318 void LyXText::recUndo(pit_type par) const
1319 {
1320         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1321 }
1322
1323
1324 int defaultRowHeight()
1325 {
1326         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1327 }