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