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