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