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