]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Fix bug 3363: mark buffer dirty after middle button paste in plain text and table...
[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 #if 0
995                 // some effectless debug code to see the values in the debugger
996                 bool bound = cur.boundary();
997                 int rowpos = cur.textRow().pos();
998                 int pos = cur.pos();
999                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
1000                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
1001                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
1002 #endif
1003                 if (!cur.boundary() &&
1004                                 cur.textRow().pos() == cur.pos() &&
1005                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
1006                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
1007                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
1008                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
1009                 }
1010                 
1011                 // go left and try to enter inset
1012                 if (checkAndActivateInset(cur, false))
1013                         return false;
1014                 
1015                 // normal character left
1016                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
1017         }
1018
1019         // move to the previous paragraph or do nothing
1020         if (cur.pit() > 0)
1021                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size());
1022         return false;
1023 }
1024
1025
1026 bool Text::cursorRight(Cursor & cur)
1027 {
1028         // Tell BufferView to test for FitCursor in any case!
1029         cur.updateFlags(Update::FitCursor);
1030
1031         // not at paragraph end?
1032         if (cur.pos() != cur.lastpos()) {
1033                 // if left of boundary -> just jump to right side 
1034                 if (cur.boundary())
1035                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
1036
1037                 // in front of editable inset, i.e. jump into it?
1038                 if (checkAndActivateInset(cur, true))
1039                         return false;
1040                 
1041                 // next position is left of boundary, 
1042                 // but go to next line for special cases like space, newline, linesep
1043 #if 0
1044                 // some effectless debug code to see the values in the debugger
1045                 int endpos = cur.textRow().endpos();
1046                 int lastpos = cur.lastpos();
1047                 int pos = cur.pos();
1048                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
1049                 bool newline = cur.paragraph().isNewline(cur.pos());
1050                 bool sep = cur.paragraph().isSeparator(cur.pos());
1051                 if (cur.pos() != cur.lastpos()) {
1052                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
1053                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
1054                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
1055                 }
1056 #endif
1057                 if (cur.textRow().endpos() == cur.pos() + 1 &&
1058                     cur.textRow().endpos() != cur.lastpos() &&
1059                                 !cur.paragraph().isNewline(cur.pos()) &&
1060                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
1061                                 !cur.paragraph().isSeparator(cur.pos())) {
1062                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
1063                 }
1064                 
1065                 // move right
1066                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
1067         }
1068
1069         // move to next paragraph
1070         if (cur.pit() != cur.lastpit())
1071                 return setCursor(cur, cur.pit() + 1, 0);
1072         return false;
1073 }
1074
1075
1076 bool Text::cursorUpParagraph(Cursor & cur)
1077 {
1078         bool updated = false;
1079         if (cur.pos() > 0)
1080                 updated = setCursor(cur, cur.pit(), 0);
1081         else if (cur.pit() != 0)
1082                 updated = setCursor(cur, cur.pit() - 1, 0);
1083         return updated;
1084 }
1085
1086
1087 bool Text::cursorDownParagraph(Cursor & cur)
1088 {
1089         bool updated = false;
1090         if (cur.pit() != cur.lastpit())
1091                 updated = setCursor(cur, cur.pit() + 1, 0);
1092         else
1093                 updated = setCursor(cur, cur.pit(), cur.lastpos());
1094         return updated;
1095 }
1096
1097
1098 // fix the cursor `cur' after a characters has been deleted at `where'
1099 // position. Called by deleteEmptyParagraphMechanism
1100 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1101 {
1102         // Do nothing if cursor is not in the paragraph where the
1103         // deletion occured,
1104         if (cur.pit() != where.pit())
1105                 return;
1106
1107         // If cursor position is after the deletion place update it
1108         if (cur.pos() > where.pos())
1109                 --cur.pos();
1110
1111         // Check also if we don't want to set the cursor on a spot behind the
1112         // pagragraph because we erased the last character.
1113         if (cur.pos() > cur.lastpos())
1114                 cur.pos() = cur.lastpos();
1115 }
1116
1117
1118 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
1119                 Cursor & old, bool & need_anchor_change)
1120 {
1121         //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1122
1123         Paragraph & oldpar = old.paragraph();
1124
1125         // We allow all kinds of "mumbo-jumbo" when freespacing.
1126         if (oldpar.isFreeSpacing())
1127                 return false;
1128
1129         /* Ok I'll put some comments here about what is missing.
1130            There are still some small problems that can lead to
1131            double spaces stored in the document file or space at
1132            the beginning of paragraphs(). This happens if you have
1133            the cursor between to spaces and then save. Or if you
1134            cut and paste and the selection have a space at the
1135            beginning and then save right after the paste. (Lgb)
1136         */
1137
1138         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1139         // delete the LineSeparator.
1140         // MISSING
1141
1142         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1143         // delete the LineSeparator.
1144         // MISSING
1145
1146         bool const same_inset = &old.inset() == &cur.inset();
1147         bool const same_par = same_inset && old.pit() == cur.pit();
1148         bool const same_par_pos = same_par && old.pos() == cur.pos();
1149
1150         // If the chars around the old cursor were spaces, delete one of them.
1151         if (!same_par_pos) {
1152                 // Only if the cursor has really moved.
1153                 if (old.pos() > 0
1154                     && old.pos() < oldpar.size()
1155                     && oldpar.isLineSeparator(old.pos())
1156                     && oldpar.isLineSeparator(old.pos() - 1)
1157                     && !oldpar.isDeleted(old.pos() - 1)) {
1158                         oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
1159 #ifdef WITH_WARNINGS
1160 #warning This will not work anymore when we have multiple views of the same buffer
1161 // In this case, we will have to correct also the cursors held by
1162 // other bufferviews. It will probably be easier to do that in a more
1163 // automated way in CursorSlice code. (JMarc 26/09/2001)
1164 #endif
1165                         // correct all cursor parts
1166                         if (same_par) {
1167                                 fixCursorAfterDelete(cur.top(), old.top());
1168                                 need_anchor_change = true;
1169                         }
1170                         return true;
1171                 }
1172         }
1173
1174         // only do our magic if we changed paragraph
1175         if (same_par)
1176                 return false;
1177
1178         // don't delete anything if this is the ONLY paragraph!
1179         if (old.lastpit() == 0)
1180                 return false;
1181
1182         // Do not delete empty paragraphs with keepempty set.
1183         if (oldpar.allowEmpty())
1184                 return false;
1185
1186         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1187                 // Delete old par.
1188                 recordUndo(old, Undo::ATOMIC,
1189                            max(old.pit() - 1, pit_type(0)),
1190                            min(old.pit() + 1, old.lastpit()));
1191                 ParagraphList & plist = old.text()->paragraphs();
1192                 plist.erase(boost::next(plist.begin(), old.pit()));
1193
1194                 // see #warning above
1195                 if (cur.depth() >= old.depth()) {
1196                         CursorSlice & curslice = cur[old.depth() - 1];
1197                         if (&curslice.inset() == &old.inset()
1198                             && curslice.pit() > old.pit()) {
1199                                 --curslice.pit();
1200                                 // since a paragraph has been deleted, all the
1201                                 // insets after `old' have been copied and
1202                                 // their address has changed. Therefore we
1203                                 // need to `regenerate' cur. (JMarc)
1204                                 cur.updateInsets(&(cur.bottom().inset()));
1205                                 need_anchor_change = true;
1206                         }
1207                 }
1208                 return true;
1209         }
1210
1211         if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
1212                 need_anchor_change = true;
1213                 // We return true here because the Paragraph contents changed and
1214                 // we need a redraw before further action is processed.
1215                 return true;
1216         }
1217
1218         return false;
1219 }
1220
1221
1222 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
1223 {
1224         BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
1225
1226         for (pit_type pit = first; pit <= last; ++pit) {
1227                 Paragraph & par = pars_[pit];
1228
1229                 // We allow all kinds of "mumbo-jumbo" when freespacing.
1230                 if (par.isFreeSpacing())
1231                         continue;
1232
1233                 for (pos_type pos = 1; pos < par.size(); ++pos) {
1234                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
1235                             && !par.isDeleted(pos - 1)) {
1236                                 if (par.eraseChar(pos - 1, trackChanges)) {
1237                                         --pos;
1238                                 }
1239                         }
1240                 }
1241
1242                 // don't delete anything if this is the only remaining paragraph within the given range
1243                 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
1244                 if (first == last)
1245                         continue;
1246
1247                 // don't delete empty paragraphs with keepempty set
1248                 if (par.allowEmpty())
1249                         continue;
1250
1251                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
1252                         pars_.erase(boost::next(pars_.begin(), pit));
1253                         --pit;
1254                         --last;
1255                         continue;
1256                 }
1257
1258                 par.stripLeadingSpaces(trackChanges);
1259         }
1260 }
1261
1262
1263 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
1264 {
1265         recordUndo(cur, Undo::ATOMIC, first, last);
1266 }
1267
1268
1269 void Text::recUndo(Cursor & cur, pit_type par) const
1270 {
1271         recordUndo(cur, Undo::ATOMIC, par, par);
1272 }
1273
1274 } // namespace lyx