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