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