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