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