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