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