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