]> git.lyx.org Git - lyx.git/blob - src/text2.C
add LCursor & parameter to cursor moving functions
[lyx.git] / src / text2.C
1 /**
2  * \file text2.C
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 "lyxtext.h"
23
24 #include "buffer.h"
25 #include "buffer_funcs.h"
26 #include "bufferparams.h"
27 #include "BufferView.h"
28 #include "Bullet.h"
29 #include "counters.h"
30 #include "cursor.h"
31 #include "CutAndPaste.h"
32 #include "debug.h"
33 #include "dispatchresult.h"
34 #include "errorlist.h"
35 #include "Floating.h"
36 #include "FloatList.h"
37 #include "funcrequest.h"
38 #include "gettext.h"
39 #include "language.h"
40 #include "LColor.h"
41 #include "lyxrc.h"
42 #include "lyxrow.h"
43 #include "lyxrow_funcs.h"
44 #include "paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "PosIterator.h"
48 #include "undo.h"
49 #include "vspace.h"
50
51 #include "frontends/font_metrics.h"
52 #include "frontends/LyXView.h"
53
54 #include "insets/insetbibitem.h"
55 #include "insets/insetenv.h"
56 #include "insets/insetfloat.h"
57 #include "insets/insetwrap.h"
58
59 #include "support/lstrings.h"
60 #include "support/textutils.h"
61 #include "support/tostr.h"
62 #include "support/std_sstream.h"
63
64 #include <boost/tuple/tuple.hpp>
65
66 using lyx::pos_type;
67 using lyx::paroffset_type;
68 using lyx::support::bformat;
69
70 using std::endl;
71 using std::ostringstream;
72 using std::string;
73
74
75 LyXText::LyXText(BufferView * bv, bool in_inset)
76         : height(0), width(0), textwidth_(bv ? bv->workWidth() : 100),
77                 background_color_(LColor::background),
78           bv_owner(bv), in_inset_(in_inset), xo_(0), yo_(0)
79 {}
80
81
82 void LyXText::init(BufferView * bv)
83 {
84         bv_owner = bv;
85
86         ParagraphList::iterator const beg = paragraphs().begin();
87         ParagraphList::iterator const end = paragraphs().end();
88         for (ParagraphList::iterator pit = beg; pit != end; ++pit)
89                 pit->rows.clear();
90
91         width = 0;
92         height = 0;
93
94         current_font = getFont(beg, 0);
95
96         redoParagraphs(beg, end);
97         bv->cursor().resetAnchor();
98
99         updateCounters();
100 }
101
102
103 // Gets the fully instantiated font at a given position in a paragraph
104 // Basically the same routine as Paragraph::getFont() in paragraph.C.
105 // The difference is that this one is used for displaying, and thus we
106 // are allowed to make cosmetic improvements. For instance make footnotes
107 // smaller. (Asger)
108 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
109 {
110         BOOST_ASSERT(pos >= 0);
111
112         LyXLayout_ptr const & layout = pit->layout();
113 #warning broken?
114         BufferParams const & params = bv()->buffer()->params();
115         pos_type const body_pos = pit->beginOfBody();
116
117         // We specialize the 95% common case:
118         if (!pit->getDepth()) {
119                 LyXFont f = pit->getFontSettings(params, pos);
120                 if (in_inset_)
121                         f.realize(font_);
122                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
123                         return f.realize(layout->reslabelfont);
124                 else
125                         return f.realize(layout->resfont);
126         }
127
128         // The uncommon case need not be optimized as much
129         LyXFont layoutfont;
130         if (pos < body_pos)
131                 layoutfont = layout->labelfont;
132         else
133                 layoutfont = layout->font;
134
135         LyXFont font = pit->getFontSettings(params, pos);
136         font.realize(layoutfont);
137
138         if (in_inset_)
139                 font.realize(font_);
140
141         // Realize with the fonts of lesser depth.
142         //font.realize(outerFont(pit, paragraphs()));
143         font.realize(defaultfont_);
144
145         return font;
146 }
147
148
149 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
150 {
151         LyXLayout_ptr const & layout = pit->layout();
152
153         if (!pit->getDepth())
154                 return layout->resfont;
155
156         LyXFont font = layout->font;
157         // Realize with the fonts of lesser depth.
158         //font.realize(outerFont(pit, paragraphs()));
159         font.realize(defaultfont_);
160
161         return font;
162 }
163
164
165 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
166 {
167         LyXLayout_ptr const & layout = pit->layout();
168
169         if (!pit->getDepth())
170                 return layout->reslabelfont;
171
172         LyXFont font = layout->labelfont;
173         // Realize with the fonts of lesser depth.
174         font.realize(outerFont(pit, paragraphs()));
175         font.realize(defaultfont_);
176
177         return font;
178 }
179
180
181 void LyXText::setCharFont(
182         ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
183 {
184         LyXFont font = fnt;
185         LyXLayout_ptr const & layout = pit->layout();
186
187         // Get concrete layout font to reduce against
188         LyXFont layoutfont;
189
190         if (pos < pit->beginOfBody())
191                 layoutfont = layout->labelfont;
192         else
193                 layoutfont = layout->font;
194
195         // Realize against environment font information
196         if (pit->getDepth()) {
197                 ParagraphList::iterator tp = pit;
198                 while (!layoutfont.resolved() &&
199                        tp != paragraphs().end() &&
200                        tp->getDepth()) {
201                         tp = outerHook(tp, paragraphs());
202                         if (tp != paragraphs().end())
203                                 layoutfont.realize(tp->layout()->font);
204                 }
205         }
206
207         layoutfont.realize(defaultfont_);
208
209         // Now, reduce font against full layout font
210         font.reduce(layoutfont);
211
212         pit->setFont(pos, font);
213 }
214
215
216 // used in setLayout
217 // Asger is not sure we want to do this...
218 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
219                                             Paragraph & par)
220 {
221         LyXLayout_ptr const & layout = par.layout();
222         pos_type const psize = par.size();
223
224         LyXFont layoutfont;
225         for (pos_type pos = 0; pos < psize; ++pos) {
226                 if (pos < par.beginOfBody())
227                         layoutfont = layout->labelfont;
228                 else
229                         layoutfont = layout->font;
230
231                 LyXFont tmpfont = par.getFontSettings(params, pos);
232                 tmpfont.reduce(layoutfont);
233                 par.setFont(pos, tmpfont);
234         }
235 }
236
237
238 // return past-the-last paragraph influenced by a layout change on pit
239 ParagraphList::iterator LyXText::undoSpan(ParagraphList::iterator pit)
240 {
241         ParagraphList::iterator end = paragraphs().end();
242         ParagraphList::iterator nextpit = boost::next(pit);
243         if (nextpit == end)
244                 return nextpit;
245         //because of parindents
246         if (!pit->getDepth())
247                 return boost::next(nextpit);
248         //because of depth constrains
249         for (; nextpit != end; ++pit, ++nextpit) {
250                 if (!pit->getDepth())
251                         break;
252         }
253         return nextpit;
254 }
255
256
257 ParagraphList::iterator
258 LyXText::setLayout(ParagraphList::iterator start,
259                    ParagraphList::iterator end,
260                    string const & layout)
261 {
262         BOOST_ASSERT(start != end);
263         ParagraphList::iterator undopit = undoSpan(boost::prior(end));
264         recUndo(parOffset(start), parOffset(undopit) - 1);
265
266         BufferParams const & bufparams = bv()->buffer()->params();
267         LyXLayout_ptr const & lyxlayout =
268                 bufparams.getLyXTextClass()[layout];
269
270         for (ParagraphList::iterator pit = start; pit != end; ++pit) {
271                 pit->applyLayout(lyxlayout);
272                 makeFontEntriesLayoutSpecific(bufparams, *pit);
273                 if (lyxlayout->margintype == MARGIN_MANUAL)
274                         pit->setLabelWidthString(lyxlayout->labelstring());
275         }
276
277         return undopit;
278 }
279
280
281 // set layout over selection and make a total rebreak of those paragraphs
282 void LyXText::setLayout(string const & layout)
283 {
284         // special handling of new environment insets
285         BufferParams const & params = bv()->buffer()->params();
286         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
287         if (lyxlayout->is_environment) {
288                 // move everything in a new environment inset
289                 lyxerr << "setting layout " << layout << endl;
290                 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
291                 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
292                 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
293                 InsetBase * inset = new InsetEnvironment(params, layout);
294                 insertInset(inset);
295                 //inset->edit(bv());
296                 //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
297                 return;
298         }
299
300         ParagraphList::iterator start =
301                 getPar(bv()->cursor().selBegin().par());
302         ParagraphList::iterator end =
303                 boost::next(getPar(bv()->cursor().selEnd().par()));
304         ParagraphList::iterator endpit = setLayout(start, end, layout);
305
306         redoParagraphs(start, endpit);
307         updateCounters();
308 }
309
310
311 namespace {
312
313
314 void getSelectionSpan(LyXText & text,
315         ParagraphList::iterator & beg,
316         ParagraphList::iterator & end)
317 {
318         if (!text.bv()->cursor().selection()) {
319                 beg = text.cursorPar();
320                 end = boost::next(beg);
321         } else {
322                 beg = text.getPar(text.bv()->cursor().selBegin());
323                 end = boost::next(text.getPar(text.bv()->cursor().selEnd()));
324         }
325 }
326
327
328 bool changeDepthAllowed(bv_funcs::DEPTH_CHANGE type,
329                         Paragraph const & par,
330                         int max_depth)
331 {
332         if (par.layout()->labeltype == LABEL_BIBLIO)
333                 return false;
334         int const depth = par.params().depth();
335         if (type == bv_funcs::INC_DEPTH && depth < max_depth)
336                 return true;
337         if (type == bv_funcs::DEC_DEPTH && depth > 0)
338                 return true;
339         return false;
340 }
341
342
343 }
344
345
346 bool LyXText::changeDepthAllowed(bv_funcs::DEPTH_CHANGE type)
347 {
348         ParagraphList::iterator beg, end; 
349         getSelectionSpan(*this, beg, end);
350         int max_depth = 0;
351         if (beg != paragraphs().begin())
352                 max_depth = boost::prior(beg)->getMaxDepthAfter();
353
354         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
355                 if (::changeDepthAllowed(type, *pit, max_depth))
356                         return true;
357                 max_depth = pit->getMaxDepthAfter();
358         }
359         return false;
360 }
361
362
363 void LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type)
364 {
365         ParagraphList::iterator beg, end;
366         getSelectionSpan(*this, beg, end);
367         
368         recUndo(parOffset(beg), parOffset(end) - 1);
369
370         int max_depth = 0;
371         if (beg != paragraphs().begin())
372                 max_depth = boost::prior(beg)->getMaxDepthAfter();
373
374         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
375                 if (::changeDepthAllowed(type, *pit, max_depth)) {
376                         int const depth = pit->params().depth();
377                         if (type == bv_funcs::INC_DEPTH)
378                                 pit->params().depth(depth + 1);
379                         else
380                                 pit->params().depth(depth - 1);
381                 }
382                 max_depth = pit->getMaxDepthAfter();
383         }
384         // this handles the counter labels, and also fixes up
385         // depth values for follow-on (child) paragraphs
386         updateCounters();
387 }
388
389
390 // set font over selection and make a total rebreak of those paragraphs
391 void LyXText::setFont(LyXFont const & font, bool toggleall)
392 {
393         LCursor & cur = bv()->cursor();
394         // if there is no selection just set the current_font
395         if (!cur.selection()) {
396                 // Determine basis font
397                 LyXFont layoutfont;
398                 if (cursor().pos() < cursorPar()->beginOfBody())
399                         layoutfont = getLabelFont(cursorPar());
400                 else
401                         layoutfont = getLayoutFont(cursorPar());
402
403                 // Update current font
404                 real_current_font.update(font,
405                                          bv()->buffer()->params().language,
406                                          toggleall);
407
408                 // Reduce to implicit settings
409                 current_font = real_current_font;
410                 current_font.reduce(layoutfont);
411                 // And resolve it completely
412                 real_current_font.realize(layoutfont);
413
414                 return;
415         }
416
417         // ok we have a selection.
418         recUndo(cur.selBegin().par(), cur.selEnd().par());
419         freezeUndo();
420
421         ParagraphList::iterator beg = getPar(cur.selBegin().par());
422         ParagraphList::iterator end = getPar(cur.selEnd().par());
423         
424         PosIterator pos(&paragraphs(), beg, cur.selBegin().pos());
425         PosIterator posend(&paragraphs(), end, cur.selEnd().pos());
426
427         BufferParams const & params = bv()->buffer()->params();
428
429         for (; pos != posend; ++pos) {
430                 LyXFont f = getFont(pos.pit(), pos.pos());
431                 f.update(font, params.language, toggleall);
432                 setCharFont(pos.pit(), pos.pos(), f);
433         }
434         
435         unFreezeUndo();
436
437         redoParagraphs(beg, ++end);
438 }
439
440
441 // the cursor set functions have a special mechanism. When they
442 // realize you left an empty paragraph, they will delete it.
443
444 void LyXText::cursorHome(LCursor & cur)
445 {
446         setCursor(cur.par(), cur.textRow().pos());
447 }
448
449
450 void LyXText::cursorEnd(LCursor & cur)
451 {
452         // if not on the last row of the par, put the cursor before
453         // the final space
454         pos_type const end = cur.textRow().endpos();
455         setCursor(cur.par(), end == cur.lastpos() ? end : end - 1);
456 }
457
458
459 void LyXText::cursorTop(LCursor &)
460 {
461         setCursor(0, 0);
462 }
463
464
465 void LyXText::cursorBottom(LCursor & cur)
466 {
467         setCursor(cur.lastpar(), boost::prior(paragraphs().end())->size());
468 }
469
470
471 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
472 {
473         // If the mask is completely neutral, tell user
474         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
475                 // Could only happen with user style
476                 bv()->owner()->message(_("No font change defined. "
477                         "Use Character under the Layout menu to define font change."));
478                 return;
479         }
480
481         // Try implicit word selection
482         // If there is a change in the language the implicit word selection
483         // is disabled.
484         CursorSlice resetCursor = cursor();
485         bool implicitSelection =
486                 font.language() == ignore_language
487                 && font.number() == LyXFont::IGNORE
488                 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
489
490         // Set font
491         setFont(font, toggleall);
492
493         // Implicit selections are cleared afterwards
494         //and cursor is set to the original position.
495         if (implicitSelection) {
496                 bv()->cursor().clearSelection();
497                 cursor() = resetCursor;
498                 bv()->cursor().resetAnchor();
499         }
500 }
501
502
503 string LyXText::getStringToIndex()
504 {
505         LCursor & cur = bv()->cursor();
506         // Try implicit word selection
507         // If there is a change in the language the implicit word selection
508         // is disabled.
509         CursorSlice const reset_cursor = cursor();
510         bool const implicitSelection =
511                 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
512
513         string idxstring;
514         if (!cur.selection())
515                 bv()->owner()->message(_("Nothing to index!"));
516         else if (cur.selBegin().par() != cur.selEnd().par())
517                 bv()->owner()->message(_("Cannot index more than one paragraph!"));
518         else
519                 idxstring = cur.selectionAsString(false);
520
521         // Reset cursors to their original position.
522         cursor() = reset_cursor;
523         cur.resetAnchor();
524
525         // Clear the implicit selection.
526         if (implicitSelection)
527                 cur.clearSelection();
528
529         return idxstring;
530 }
531
532
533 // the DTP switches for paragraphs(). LyX will store them in the first
534 // physical paragraph. When a paragraph is broken, the top settings rest,
535 // the bottom settings are given to the new one. So I can make sure,
536 // they do not duplicate themself and you cannot play dirty tricks with
537 // them!
538
539 void LyXText::setParagraph(Spacing const & spacing, LyXAlignment align,
540         string const & labelwidthstring, bool noindent)
541 {
542         LCursor & cur = bv()->cursor();
543         // make sure that the depth behind the selection are restored, too
544         ParagraphList::iterator undopit = undoSpan(getPar(cur.selEnd()));
545         recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
546
547         ParagraphList::reverse_iterator pit(getPar(cur.selEnd().par()));
548         ParagraphList::reverse_iterator beg(getPar(cur.selBegin().par()));
549
550         for (--pit; pit != beg; ++pit) {
551                 ParagraphParameters & params = pit->params();
552                 params.spacing(spacing);
553
554                 // does the layout allow the new alignment?
555                 LyXLayout_ptr const & layout = pit->layout();
556
557                 if (align == LYX_ALIGN_LAYOUT)
558                         align = layout->align;
559                 if (align & layout->alignpossible) {
560                         if (align == layout->align)
561                                 params.align(LYX_ALIGN_LAYOUT);
562                         else
563                                 params.align(align);
564                 }
565                 pit->setLabelWidthString(labelwidthstring);
566                 params.noindent(noindent);
567         }
568
569         redoParagraphs(getPar(cur.selBegin()), undopit);
570 }
571
572
573 string expandLabel(LyXTextClass const & textclass,
574         LyXLayout_ptr const & layout, bool appendix)
575 {
576         string fmt = appendix ?
577                 layout->labelstring_appendix() : layout->labelstring();
578
579         // handle 'inherited level parts' in 'fmt',
580         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
581         size_t const i = fmt.find('@', 0);
582         if (i != string::npos) {
583                 size_t const j = fmt.find('@', i + 1);
584                 if (j != string::npos) {
585                         string parent(fmt, i + 1, j - i - 1);
586                         string label = expandLabel(textclass, textclass[parent], appendix);
587                         fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
588                 }
589         }
590
591         return textclass.counters().counterLabel(fmt);
592 }
593
594
595 namespace {
596
597 void incrementItemDepth(ParagraphList::iterator pit,
598                         ParagraphList::iterator first_pit)
599 {
600         int const cur_labeltype = pit->layout()->labeltype;
601
602         if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
603                 return;
604
605         int const cur_depth = pit->getDepth();
606
607         ParagraphList::iterator prev_pit = boost::prior(pit);
608         while (true) {
609                 int const prev_depth = prev_pit->getDepth();
610                 int const prev_labeltype = prev_pit->layout()->labeltype;
611                 if (prev_depth == 0 && cur_depth > 0) {
612                         if (prev_labeltype == cur_labeltype) {
613                                 pit->itemdepth = prev_pit->itemdepth + 1;
614                         }
615                         break;
616                 } else if (prev_depth < cur_depth) {
617                         if (prev_labeltype == cur_labeltype) {
618                                 pit->itemdepth = prev_pit->itemdepth + 1;
619                                 break;
620                         }
621                 } else if (prev_depth == cur_depth) {
622                         if (prev_labeltype == cur_labeltype) {
623                                 pit->itemdepth = prev_pit->itemdepth;
624                                 break;
625                         }
626                 }
627                 if (prev_pit == first_pit)
628                         break;
629
630                 --prev_pit;
631         }
632 }
633
634
635 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
636                               ParagraphList::iterator firstpit,
637                               Counters & counters)
638 {
639         if (pit == firstpit)
640                 return;
641
642         int const cur_depth = pit->getDepth();
643         ParagraphList::iterator prev_pit = boost::prior(pit);
644         while (true) {
645                 int const prev_depth = prev_pit->getDepth();
646                 int const prev_labeltype = prev_pit->layout()->labeltype;
647                 if (prev_depth <= cur_depth) {
648                         if (prev_labeltype != LABEL_ENUMERATE) {
649                                 switch (pit->itemdepth) {
650                                 case 0:
651                                         counters.reset("enumi");
652                                 case 1:
653                                         counters.reset("enumii");
654                                 case 2:
655                                         counters.reset("enumiii");
656                                 case 3:
657                                         counters.reset("enumiv");
658                                 }
659                         }
660                         break;
661                 }
662
663                 if (prev_pit == firstpit)
664                         break;
665
666                 --prev_pit;
667         }
668 }
669
670 } // anon namespace
671
672
673 // set the counter of a paragraph. This includes the labels
674 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
675 {
676         BufferParams const & bufparams = buf.params();
677         LyXTextClass const & textclass = bufparams.getLyXTextClass();
678         LyXLayout_ptr const & layout = pit->layout();
679         ParagraphList::iterator first_pit = paragraphs().begin();
680         Counters & counters = textclass.counters();
681
682         // Always reset
683         pit->itemdepth = 0;
684
685         if (pit == first_pit) {
686                 pit->params().appendix(pit->params().startOfAppendix());
687         } else {
688                 pit->params().appendix(boost::prior(pit)->params().appendix());
689                 if (!pit->params().appendix() &&
690                     pit->params().startOfAppendix()) {
691                         pit->params().appendix(true);
692                         textclass.counters().reset();
693                 }
694
695                 // Maybe we have to increment the item depth.
696                 incrementItemDepth(pit, first_pit);
697         }
698
699         // erase what was there before
700         pit->params().labelString(string());
701
702         if (layout->margintype == MARGIN_MANUAL) {
703                 if (pit->params().labelWidthString().empty())
704                         pit->setLabelWidthString(layout->labelstring());
705         } else {
706                 pit->setLabelWidthString(string());
707         }
708
709         // is it a layout that has an automatic label?
710         if (layout->labeltype == LABEL_COUNTER) {
711                 BufferParams const & bufparams = buf.params();
712                 LyXTextClass const & textclass = bufparams.getLyXTextClass();
713                 counters.step(layout->counter);
714                 string label = expandLabel(textclass, layout, pit->params().appendix());
715                 pit->params().labelString(label);
716         } else if (layout->labeltype == LABEL_ITEMIZE) {
717                 // At some point of time we should do something more
718                 // clever here, like:
719                 //   pit->params().labelString(
720                 //    bufparams.user_defined_bullet(pit->itemdepth).getText());
721                 // for now, use a simple hardcoded label
722                 string itemlabel;
723                 switch (pit->itemdepth) {
724                 case 0:
725                         itemlabel = "*";
726                         break;
727                 case 1:
728                         itemlabel = "-";
729                         break;
730                 case 2:
731                         itemlabel = "@";
732                         break;
733                 case 3:
734                         itemlabel = "·";
735                         break;
736                 }
737
738                 pit->params().labelString(itemlabel);
739         } else if (layout->labeltype == LABEL_ENUMERATE) {
740                 // Maybe we have to reset the enumeration counter.
741                 resetEnumCounterIfNeeded(pit, first_pit, counters);
742
743                 // FIXME
744                 // Yes I know this is a really, really! bad solution
745                 // (Lgb)
746                 string enumcounter = "enum";
747
748                 switch (pit->itemdepth) {
749                 case 2:
750                         enumcounter += 'i';
751                 case 1:
752                         enumcounter += 'i';
753                 case 0:
754                         enumcounter += 'i';
755                         break;
756                 case 3:
757                         enumcounter += "iv";
758                         break;
759                 default:
760                         // not a valid enumdepth...
761                         break;
762                 }
763
764                 counters.step(enumcounter);
765
766                 pit->params().labelString(counters.enumLabel(enumcounter));
767         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
768                 counters.step("bibitem");
769                 int number = counters.value("bibitem");
770                 if (pit->bibitem()) {
771                         pit->bibitem()->setCounter(number);
772                         pit->params().labelString(layout->labelstring());
773                 }
774                 // In biblio should't be following counters but...
775         } else {
776                 string s = buf.B_(layout->labelstring());
777
778                 // the caption hack:
779                 if (layout->labeltype == LABEL_SENSITIVE) {
780                         ParagraphList::iterator end = paragraphs().end();
781                         ParagraphList::iterator tmppit = pit;
782                         InsetBase * in = 0;
783                         bool isOK = false;
784                         while (tmppit != end && tmppit->inInset()
785                                // the single '=' is intended below
786                                && (in = tmppit->inInset()->owner()))
787                         {
788                                 if (in->lyxCode() == InsetBase::FLOAT_CODE ||
789                                     in->lyxCode() == InsetBase::WRAP_CODE) {
790                                         isOK = true;
791                                         break;
792                                 } else {
793                                         Paragraph const * owner = &ownerPar(buf, in);
794                                         tmppit = first_pit;
795                                         for ( ; tmppit != end; ++tmppit)
796                                                 if (&*tmppit == owner)
797                                                         break;
798                                 }
799                         }
800
801                         if (isOK) {
802                                 string type;
803
804                                 if (in->lyxCode() == InsetBase::FLOAT_CODE)
805                                         type = static_cast<InsetFloat*>(in)->params().type;
806                                 else if (in->lyxCode() == InsetBase::WRAP_CODE)
807                                         type = static_cast<InsetWrap*>(in)->params().type;
808                                 else
809                                         BOOST_ASSERT(false);
810
811                                 Floating const & fl = textclass.floats().getType(type);
812
813                                 counters.step(fl.type());
814
815                                 // Doesn't work... yet.
816                                 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
817                         } else {
818                                 // par->SetLayout(0);
819                                 // s = layout->labelstring;
820                                 s = _("Senseless: ");
821                         }
822                 }
823                 pit->params().labelString(s);
824
825         }
826 }
827
828
829 // Updates all counters.
830 void LyXText::updateCounters()
831 {
832         // start over
833         bv()->buffer()->params().getLyXTextClass().counters().reset();
834
835         bool update_pos = false;
836         
837         ParagraphList::iterator beg = paragraphs().begin();
838         ParagraphList::iterator end = paragraphs().end();
839         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
840                 string const oldLabel = pit->params().labelString();
841                 size_t maxdepth = 0;
842                 if (pit != beg)
843                         maxdepth = boost::prior(pit)->getMaxDepthAfter();
844
845                 if (pit->params().depth() > maxdepth)
846                         pit->params().depth(maxdepth);
847
848                 // setCounter can potentially change the labelString.
849                 setCounter(*bv()->buffer(), pit);
850                 string const & newLabel = pit->params().labelString();
851                 if (oldLabel != newLabel) {
852                         redoParagraphInternal(pit);
853                         update_pos = true;
854                 }
855                 
856         }
857         if (update_pos)
858                 updateParPositions();
859 }
860
861
862 void LyXText::insertInset(InsetBase * inset)
863 {
864         if (!cursorPar()->insetAllowed(inset->lyxCode()))
865                 return;
866
867         recUndo(cursor().par());
868         freezeUndo();
869         cursorPar()->insertInset(cursor().pos(), inset);
870         // Just to rebreak and refresh correctly.
871         // The character will not be inserted a second time
872         insertChar(Paragraph::META_INSET);
873         // If we enter a highly editable inset the cursor should be before
874         // the inset. After an undo LyX tries to call inset->edit(...)
875         // and fails if the cursor is behind the inset and getInset
876         // does not return the inset!
877         if (isHighlyEditableInset(inset))
878                 cursorLeft(bv()->cursor(), true);
879
880         unFreezeUndo();
881 }
882
883
884 void LyXText::cutSelection(bool doclear, bool realcut)
885 {
886         LCursor & cur = bv()->cursor();
887         // Stuff what we got on the clipboard. Even if there is no selection.
888
889         // There is a problem with having the stuffing here in that the
890         // larger the selection the slower LyX will get. This can be
891         // solved by running the line below only when the selection has
892         // finished. The solution used currently just works, to make it
893         // faster we need to be more clever and probably also have more
894         // calls to stuffClipboard. (Lgb)
895         bv()->stuffClipboard(cur.selectionAsString(true));
896
897         // This doesn't make sense, if there is no selection
898         if (!cur.selection())
899                 return;
900
901         // OK, we have a selection. This is always between cur.selBegin()
902         // and cur.selEnd()
903
904         // make sure that the depth behind the selection are restored, too
905         ParagraphList::iterator begpit = getPar(cur.selBegin().par());
906         ParagraphList::iterator endpit = getPar(cur.selEnd().par());
907         ParagraphList::iterator undopit = undoSpan(endpit);
908         recUndo(cur.selBegin().par(), parOffset(undopit) - 1);
909
910         int endpos = cur.selEnd().pos();
911
912         BufferParams const & bufparams = bv()->buffer()->params();
913         boost::tie(endpit, endpos) = realcut ?
914                 CutAndPaste::cutSelection(bufparams,
915                                           paragraphs(),
916                                           begpit , endpit,
917                                           cur.selBegin().pos(), endpos,
918                                           bufparams.textclass,
919                                           doclear)
920                 : CutAndPaste::eraseSelection(bufparams,
921                                               paragraphs(),
922                                               begpit, endpit,
923                                               cur.selBegin().pos(), endpos,
924                                               doclear);
925         // sometimes necessary
926         if (doclear)
927                 begpit->stripLeadingSpaces();
928
929         redoParagraphs(begpit, undopit);
930         // cutSelection can invalidate the cursor so we need to set
931         // it anew. (Lgb)
932         // we prefer the end for when tracking changes
933         cursor().pos(endpos);
934         cursor().par(parOffset(endpit));
935
936         // need a valid cursor. (Lgb)
937         cur.clearSelection();
938         updateCounters();
939 }
940
941
942 void LyXText::copySelection()
943 {
944         LCursor & cur = bv()->cursor();
945         // stuff the selection onto the X clipboard, from an explicit copy request
946         bv()->stuffClipboard(cur.selectionAsString(true));
947
948         // this doesnt make sense, if there is no selection
949         if (!cur.selection())
950                 return;
951
952         // ok we have a selection. This is always between cur.selBegin()
953         // and sel_end cursor
954
955         // copy behind a space if there is one
956         while (getPar(cur.selBegin())->size() > cur.selBegin().pos()
957                && getPar(cur.selBegin())->isLineSeparator(cur.selBegin().pos())
958                && (cur.selBegin().par() != cur.selEnd().par()
959                    || cur.selBegin().pos() < cur.selEnd().pos()))
960                 cur.selBegin().pos(cur.selBegin().pos() + 1);
961
962         CutAndPaste::copySelection(getPar(cur.selBegin().par()),
963                                    getPar(cur.selEnd().par()),
964                                    cur.selBegin().pos(), 
965                                    cur.selEnd().pos(),
966                                    bv()->buffer()->params().textclass);
967 }
968
969
970 void LyXText::pasteSelection(size_t sel_index)
971 {
972         LCursor & cur = bv()->cursor();
973         // this does not make sense, if there is nothing to paste
974         if (!CutAndPaste::checkPastePossible())
975                 return;
976
977         recUndo(cursor().par());
978
979         ParagraphList::iterator endpit;
980         PitPosPair ppp;
981
982         ErrorList el;
983
984         boost::tie(ppp, endpit) =
985                 CutAndPaste::pasteSelection(*bv()->buffer(),
986                                             paragraphs(),
987                                             cursorPar(), cursor().pos(),
988                                             bv()->buffer()->params().textclass,
989                                             sel_index, el);
990         bufferErrors(*bv()->buffer(), el);
991         bv()->showErrorList(_("Paste"));
992
993         redoParagraphs(cursorPar(), endpit);
994
995         cur.clearSelection();
996         cur.resetAnchor();
997         setCursor(parOffset(ppp.first), ppp.second);
998         cur.setSelection();
999         updateCounters();
1000 }
1001
1002
1003 void LyXText::setSelectionRange(lyx::pos_type length)
1004 {
1005         if (!length)
1006                 return;
1007
1008         LCursor & cur = bv()->cursor();
1009         cur.resetAnchor();
1010         while (length--)
1011                 cursorRight(cur, true);
1012         cur.setSelection();
1013 }
1014
1015
1016 // simple replacing. The font of the first selected character is used
1017 void LyXText::replaceSelectionWithString(string const & str)
1018 {
1019         LCursor & cur = bv()->cursor();
1020         recUndo(cur.par());
1021         freezeUndo();
1022
1023         // Get font setting before we cut
1024         pos_type pos = cur.selEnd().pos();
1025         LyXFont const font = getPar(cur.selBegin())
1026                 ->getFontSettings(bv()->buffer()->params(),
1027                                   cur.selBegin().pos());
1028
1029         // Insert the new string
1030         string::const_iterator cit = str.begin();
1031         string::const_iterator end = str.end();
1032         for (; cit != end; ++cit) {
1033                 getPar(cur.selEnd())->insertChar(pos, (*cit), font);
1034                 ++pos;
1035         }
1036
1037         // Cut the selection
1038         cutSelection(true, false);
1039
1040         unFreezeUndo();
1041 }
1042
1043
1044 // needed to insert the selection
1045 void LyXText::insertStringAsLines(string const & str)
1046 {
1047         LCursor & cur = bv()->cursor();
1048         ParagraphList::iterator pit = cursorPar();
1049         pos_type pos = cursor().pos();
1050         ParagraphList::iterator endpit = boost::next(cursorPar());
1051         recordUndo(cur, Undo::ATOMIC);
1052
1053         // only to be sure, should not be neccessary
1054         cur.clearSelection();
1055         bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1056
1057         redoParagraphs(cursorPar(), endpit);
1058         cur.resetAnchor();
1059         setCursor(parOffset(pit), pos);
1060         cur.setSelection();
1061 }
1062
1063
1064 // turn double CR to single CR, others are converted into one
1065 // blank. Then insertStringAsLines is called
1066 void LyXText::insertStringAsParagraphs(string const & str)
1067 {
1068         string linestr(str);
1069         bool newline_inserted = false;
1070         string::size_type const siz = linestr.length();
1071
1072         for (string::size_type i = 0; i < siz; ++i) {
1073                 if (linestr[i] == '\n') {
1074                         if (newline_inserted) {
1075                                 // we know that \r will be ignored by
1076                                 // insertStringAsLines. Of course, it is a dirty
1077                                 // trick, but it works...
1078                                 linestr[i - 1] = '\r';
1079                                 linestr[i] = '\n';
1080                         } else {
1081                                 linestr[i] = ' ';
1082                                 newline_inserted = true;
1083                         }
1084                 } else if (IsPrintable(linestr[i])) {
1085                         newline_inserted = false;
1086                 }
1087         }
1088         insertStringAsLines(linestr);
1089 }
1090
1091
1092 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont,
1093         bool boundary)
1094 {
1095         CursorSlice old_cursor = cursor();
1096         setCursorIntern(par, pos, setfont, boundary);
1097         return deleteEmptyParagraphMechanism(old_cursor);
1098 }
1099
1100
1101 void LyXText::setCursor(CursorSlice & cur, paroffset_type par,
1102         pos_type pos, bool boundary)
1103 {
1104         BOOST_ASSERT(par != int(paragraphs().size()));
1105
1106         cur.par(par);
1107         cur.pos(pos);
1108         cur.boundary(boundary);
1109
1110         // no rows, no fun...
1111         if (paragraphs().begin()->rows.empty())
1112                 return;
1113
1114         // now some strict checking
1115         Paragraph & para = *getPar(par);
1116         Row const & row = *para.getRow(pos);
1117         pos_type const end = row.endpos();
1118
1119         // None of these should happen, but we're scaredy-cats
1120         if (pos < 0) {
1121                 lyxerr << "dont like -1" << endl;
1122                 pos = 0;
1123                 cur.pos(0);
1124                 BOOST_ASSERT(false);
1125         } else if (pos > para.size()) {
1126                 lyxerr << "dont like 1, pos: " << pos
1127                        << " size: " << para.size()
1128                        << " row.pos():" << row.pos()
1129                        << " paroffset: " << par << endl;
1130                 pos = 0;
1131                 cur.pos(0);
1132                 BOOST_ASSERT(false);
1133         } else if (pos > end) {
1134                 lyxerr << "dont like 2 please report" << endl;
1135                 // This shouldn't happen.
1136                 pos = end;
1137                 cur.pos(pos);
1138                 BOOST_ASSERT(false);
1139         } else if (pos < row.pos()) {
1140                 lyxerr << "dont like 3 please report pos:" << pos
1141                        << " size: " << para.size()
1142                        << " row.pos():" << row.pos()
1143                        << " paroffset: " << par << endl;
1144                 pos = row.pos();
1145                 cur.pos(pos);
1146                 BOOST_ASSERT(false);
1147         }
1148 }
1149
1150
1151 void LyXText::setCursorIntern(paroffset_type par,
1152                               pos_type pos, bool setfont, bool boundary)
1153 {
1154         setCursor(cursor(), par, pos, boundary);
1155         bv()->cursor().x_target() = cursorX(cursor());
1156         if (setfont)
1157                 setCurrentFont();
1158 }
1159
1160
1161 void LyXText::setCurrentFont()
1162 {
1163         LCursor & cur = bv()->cursor();
1164         pos_type pos = cur.pos();
1165         ParagraphList::iterator pit = cursorPar();
1166
1167         if (cursor().boundary() && pos > 0)
1168                 --pos;
1169
1170         if (pos > 0) {
1171                 if (pos == pit->size())
1172                         --pos;
1173                 else // potentional bug... BUG (Lgb)
1174                         if (pit->isSeparator(pos)) {
1175                                 if (pos > pit->getRow(pos)->pos() &&
1176                                     bidi.level(pos) % 2 ==
1177                                     bidi.level(pos - 1) % 2)
1178                                         --pos;
1179                                 else if (pos + 1 < pit->size())
1180                                         ++pos;
1181                         }
1182         }
1183
1184         BufferParams const & bufparams = bv()->buffer()->params();
1185         current_font = pit->getFontSettings(bufparams, pos);
1186         real_current_font = getFont(pit, pos);
1187
1188         if (cursor().pos() == pit->size() &&
1189             bidi.isBoundary(*bv()->buffer(), *pit, cursor().pos()) &&
1190             !cursor().boundary()) {
1191                 Language const * lang =
1192                         pit->getParLanguage(bufparams);
1193                 current_font.setLanguage(lang);
1194                 current_font.setNumber(LyXFont::OFF);
1195                 real_current_font.setLanguage(lang);
1196                 real_current_font.setNumber(LyXFont::OFF);
1197         }
1198 }
1199
1200 // x is an absolute screen coord
1201 // returns the column near the specified x-coordinate of the row
1202 // x is set to the real beginning of this column
1203 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1204         Row const & row, int & x, bool & boundary) const
1205 {
1206         x -= xo_;
1207         double tmpx             = row.x();
1208         double fill_separator   = row.fill_separator();
1209         double fill_hfill       = row.fill_hfill();
1210         double fill_label_hfill = row.fill_label_hfill();
1211
1212         pos_type vc = row.pos();
1213         pos_type end = row.endpos();
1214         pos_type c = 0;
1215         LyXLayout_ptr const & layout = pit->layout();
1216
1217         bool left_side = false;
1218
1219         pos_type body_pos = pit->beginOfBody();
1220         double last_tmpx = tmpx;
1221
1222         if (body_pos > 0 &&
1223             (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1224                 body_pos = 0;
1225
1226         // check for empty row
1227         if (vc == end) {
1228                 x = int(tmpx) + xo_;
1229                 return 0;
1230         }
1231
1232         while (vc < end && tmpx <= x) {
1233                 c = bidi.vis2log(vc);
1234                 last_tmpx = tmpx;
1235                 if (body_pos > 0 && c == body_pos - 1) {
1236                         tmpx += fill_label_hfill +
1237                                 font_metrics::width(layout->labelsep, getLabelFont(pit));
1238                         if (pit->isLineSeparator(body_pos - 1))
1239                                 tmpx -= singleWidth(pit, body_pos - 1);
1240                 }
1241
1242                 if (hfillExpansion(*pit, row, c)) {
1243                         tmpx += singleWidth(pit, c);
1244                         if (c >= body_pos)
1245                                 tmpx += fill_hfill;
1246                         else
1247                                 tmpx += fill_label_hfill;
1248                 } else if (pit->isSeparator(c)) {
1249                         tmpx += singleWidth(pit, c);
1250                         if (c >= body_pos)
1251                                 tmpx += fill_separator;
1252                 } else {
1253                         tmpx += singleWidth(pit, c);
1254                 }
1255                 ++vc;
1256         }
1257
1258         if ((tmpx + last_tmpx) / 2 > x) {
1259                 tmpx = last_tmpx;
1260                 left_side = true;
1261         }
1262
1263         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
1264
1265         boundary = false;
1266         // This (rtl_support test) is not needed, but gives
1267         // some speedup if rtl_support == false
1268         bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1269
1270         // If lastrow is false, we don't need to compute
1271         // the value of rtl.
1272         bool const rtl = lastrow
1273                 ? pit->isRightToLeftPar(bv()->buffer()->params())
1274                 : false;
1275         if (lastrow &&
1276                  ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
1277                   (!rtl && !left_side && vc == end  && x > tmpx + 5)))
1278                 c = end;
1279         else if (vc == row.pos()) {
1280                 c = bidi.vis2log(vc);
1281                 if (bidi.level(c) % 2 == 1)
1282                         ++c;
1283         } else {
1284                 c = bidi.vis2log(vc - 1);
1285                 bool const rtl = (bidi.level(c) % 2 == 1);
1286                 if (left_side == rtl) {
1287                         ++c;
1288                         boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1289                 }
1290         }
1291
1292         if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1293                 if (bidi.level(end -1) % 2 == 0)
1294                         tmpx -= singleWidth(pit, end - 1);
1295                 else
1296                         tmpx += singleWidth(pit, end - 1);
1297                 c = end - 1;
1298         }
1299
1300         x = int(tmpx) + xo_;
1301         return c - row.pos();
1302 }
1303
1304
1305 void LyXText::setCursorFromCoordinates(int x, int y)
1306 {
1307         CursorSlice old_cursor = cursor();
1308         setCursorFromCoordinates(cursor(), x, y);
1309         setCurrentFont();
1310         deleteEmptyParagraphMechanism(old_cursor);
1311 }
1312
1313
1314 // x,y are coordinates relative to this LyXText
1315 void LyXText::setCursorFromCoordinates(CursorSlice & cur, int x, int y)
1316 {
1317         ParagraphList::iterator pit;
1318         Row const & row = *getRowNearY(y, pit);
1319         bool bound = false;
1320         int xx = x + xo_; // getRowNearX get absolute x coords
1321         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1322         cur.par() = parOffset(pit);
1323         cur.pos() = pos;
1324         cur.boundary() = bound;
1325 }
1326
1327
1328 // x,y are absolute screen coordinates
1329 void LyXText::edit(LCursor & cur, int x, int y)
1330 {
1331         ParagraphList::iterator pit;
1332         Row const & row = *getRowNearY(y - yo_, pit);
1333         bool bound = false;
1334
1335         int xx = x; // is modified by getColumnNearX
1336         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1337         cur.par() = parOffset(pit);
1338         cur.pos() = pos;
1339         cur.boundary() = bound;
1340
1341         // try to descend into nested insets
1342         InsetBase * inset = checkInsetHit(x, y);
1343         if (inset) {
1344                 // This should be just before or just behind the
1345                 // cursor position set above.
1346                 BOOST_ASSERT((pos != 0 && inset == pit->getInset(pos - 1))
1347                              || inset == pit->getInset(pos));
1348                 // Make sure the cursor points to the position before
1349                 // this inset.
1350                 if (inset == pit->getInset(pos - 1))
1351                         --cur.pos();
1352                 inset->edit(cur, x, y);
1353         }
1354 }
1355
1356
1357 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1358 {
1359         if (cur.pos() == cur.lastpos())
1360                 return false;
1361         InsetBase * inset = cur.nextInset();
1362         if (!isHighlyEditableInset(inset))
1363                 return false;
1364         inset->edit(cur, front);
1365         return true;
1366 }
1367
1368
1369 DispatchResult LyXText::moveRight(LCursor & cur)
1370 {
1371         if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1372                 return moveLeftIntern(cur, false, true, false);
1373         else
1374                 return moveRightIntern(cur, true, true, false);
1375 }
1376
1377
1378 DispatchResult LyXText::moveLeft(LCursor & cur)
1379 {
1380         if (cur.paragraph().isRightToLeftPar(bv()->buffer()->params()))
1381                 return moveRightIntern(cur, true, true, false);
1382         else
1383                 return moveLeftIntern(cur, false, true, false);
1384 }
1385
1386
1387 DispatchResult LyXText::moveRightIntern(LCursor & cur, 
1388         bool front, bool activate_inset, bool selecting)
1389 {
1390         if (cur.par() == cur.lastpar() && cur.pos() == cur.lastpos())
1391                 return DispatchResult(false, FINISHED_RIGHT);
1392         if (activate_inset && checkAndActivateInset(cur, front))
1393                 return DispatchResult(true, true);
1394         cursorRight(cur, true);
1395         if (!selecting)
1396                 cur.clearSelection();
1397         return DispatchResult(true);
1398 }
1399
1400
1401 DispatchResult LyXText::moveLeftIntern(LCursor & cur,
1402         bool front, bool activate_inset, bool selecting)
1403 {
1404         if (cur.par() == 0 && cur.pos() == 0)
1405                 return DispatchResult(false, FINISHED);
1406         cursorLeft(cur, true);
1407         if (!selecting)
1408                 cur.clearSelection();
1409         if (activate_inset && checkAndActivateInset(cur, front))
1410                 return DispatchResult(true, true);
1411         return DispatchResult(true);
1412 }
1413
1414
1415 DispatchResult LyXText::moveUp(LCursor & cur)
1416 {
1417         if (cur.par() == 0 && cur.row() == 0)
1418                 return DispatchResult(false, FINISHED_UP);
1419         cursorUp(cur, false);
1420         cur.clearSelection();
1421         return DispatchResult(true);
1422 }
1423
1424
1425 DispatchResult LyXText::moveDown(LCursor & cur)
1426 {
1427         if (cur.par() == cur.lastpar() && cur.textRow().endpos() == cur.lastpos())
1428                 return DispatchResult(false, FINISHED_DOWN);
1429         cursorDown(cur, false);
1430         cur.clearSelection();
1431         return DispatchResult(true);
1432 }
1433
1434
1435 bool LyXText::cursorLeft(LCursor & cur, bool internal)
1436 {
1437         if (cur.pos() != 0) {
1438                 bool boundary = cur.boundary();
1439                 setCursor(cur.par(), cur.pos() - 1, true, false);
1440                 if (!internal && !boundary &&
1441                     bidi.isBoundary(*bv()->buffer(), cur.paragraph(), cur.pos() + 1))
1442                         setCursor(cur.par(), cur.pos() + 1, true, true);
1443                 return true;
1444         }
1445
1446         if (cur.par() != 0) {
1447                 // steps into the paragraph above
1448                 setCursor(cur.par() - 1, boost::prior(cursorPar())->size());
1449                 return true;
1450         }
1451
1452         return false;
1453 }
1454
1455
1456 bool LyXText::cursorRight(LCursor & cur, bool internal)
1457 {
1458         if (!internal && cur.boundary()) {
1459                 setCursor(cur.par(), cur.pos(), true, false);
1460                 return true;
1461         }
1462
1463         if (cur.pos() != cur.lastpos()) {
1464                 setCursor(cur.par(), cur.pos() + 1, true, false);
1465                 if (!internal && bidi.isBoundary(*bv()->buffer(), cur.paragraph(),
1466                                                  cur.pos()))
1467                         setCursor(cur.par(), cur.pos(), true, true);
1468                 return true;
1469         }
1470
1471         if (cur.par() != cur.lastpar()) {
1472                 setCursor(cur.par() + 1, 0);
1473                 return true;
1474         }
1475
1476         return false;
1477 }
1478
1479
1480 void LyXText::cursorUp(LCursor & cur, bool selecting)
1481 {
1482         Row const & row = *cursorRow();
1483         int x = cur.x_target();
1484         int y = cursorY(cur.current()) - row.baseline() - 1;
1485         setCursorFromCoordinates(x, y);
1486
1487         if (!selecting) {
1488                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1489                 if (inset_hit && isHighlyEditableInset(inset_hit))
1490                         inset_hit->edit(cur, cur.x_target(), y);
1491         }
1492 }
1493
1494
1495 void LyXText::cursorDown(LCursor & cur, bool selecting)
1496 {
1497         Row const & row = cur.textRow();
1498         int x = cur.x_target();
1499         int y = cursorY(cur.current()) - row.baseline() + row.height() + 1;
1500         setCursorFromCoordinates(cur.current(), x, y);
1501
1502         if (!selecting) {
1503                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1504                 if (inset_hit && isHighlyEditableInset(inset_hit))
1505                         inset_hit->edit(cur, cur.x_target(), y);
1506         }
1507 }
1508
1509
1510 void LyXText::cursorUpParagraph(LCursor & cur)
1511 {
1512         if (cur.pos() > 0)
1513                 setCursor(cur.par(), 0);
1514         else if (cur.par() != 0)
1515                 setCursor(cur.par() - 1, 0);
1516 }
1517
1518
1519 void LyXText::cursorDownParagraph(LCursor & cur)
1520 {
1521         if (cur.par() != cur.lastpar())
1522                 setCursor(cur.par() + 1, 0);
1523         else
1524                 setCursor(cur.par(), cur.lastpos());
1525 }
1526
1527
1528 // fix the cursor `cur' after a characters has been deleted at `where'
1529 // position. Called by deleteEmptyParagraphMechanism
1530 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1531 {
1532         // if cursor is not in the paragraph where the delete occured,
1533         // do nothing
1534         if (cur.par() != where.par())
1535                 return;
1536
1537         // if cursor position is after the place where the delete occured,
1538         // update it
1539         if (cur.pos() > where.pos())
1540                 --cur.pos();
1541
1542         // check also if we don't want to set the cursor on a spot behind the
1543         // pagragraph because we erased the last character.
1544         if (cur.pos() > cur.lastpos())
1545                 cur.pos() = cur.lastpos();
1546 }
1547
1548
1549 bool LyXText::deleteEmptyParagraphMechanism(CursorSlice const & old_cursor)
1550 {
1551 #warning Disabled as it crashes after the cursor data shift... (Andre)
1552         return false;
1553
1554         // Would be wrong to delete anything if we have a selection.
1555         if (bv()->cursor().selection())
1556                 return false;
1557
1558         // Don't do anything if the cursor is invalid
1559         if (old_cursor.par() == -1)
1560                 return false;
1561
1562 #if 0
1563         // We allow all kinds of "mumbo-jumbo" when freespacing.
1564         ParagraphList::iterator const old_pit = getPar(old_cursor);
1565         if (old_pit->isFreeSpacing())
1566                 return false;
1567
1568         /* Ok I'll put some comments here about what is missing.
1569            I have fixed BackSpace (and thus Delete) to not delete
1570            double-spaces automagically. I have also changed Cut,
1571            Copy and Paste to hopefully do some sensible things.
1572            There are still some small problems that can lead to
1573            double spaces stored in the document file or space at
1574            the beginning of paragraphs(). This happens if you have
1575            the cursor between to spaces and then save. Or if you
1576            cut and paste and the selection have a space at the
1577            beginning and then save right after the paste. I am
1578            sure none of these are very hard to fix, but I will
1579            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1580            that I can get some feedback. (Lgb)
1581         */
1582
1583         // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1584         // delete the LineSeparator.
1585         // MISSING
1586
1587         // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1588         // delete the LineSeparator.
1589         // MISSING
1590
1591         // If the pos around the old_cursor were spaces, delete one of them.
1592         if (old_cursor.par() != cursor().par()
1593             || old_cursor.pos() != cursor().pos()) {
1594
1595                 // Only if the cursor has really moved
1596                 if (old_cursor.pos() > 0
1597                     && old_cursor.pos() < old_pit->size()
1598                     && old_pit->isLineSeparator(old_cursor.pos())
1599                     && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1600                         bool erased = old_pit->erase(old_cursor.pos() - 1);
1601                         redoParagraph(old_pit);
1602
1603                         if (!erased)
1604                                 return false;
1605 #ifdef WITH_WARNINGS
1606 #warning This will not work anymore when we have multiple views of the same buffer
1607 // In this case, we will have to correct also the cursors held by
1608 // other bufferviews. It will probably be easier to do that in a more
1609 // automated way in CursorSlice code. (JMarc 26/09/2001)
1610 #endif
1611                         // correct all cursors held by the LyXText
1612                         fixCursorAfterDelete(cursor(), old_cursor);
1613                         fixCursorAfterDelete(anchor(), old_cursor);
1614                         return false;
1615                 }
1616         }
1617
1618         // don't delete anything if this is the ONLY paragraph!
1619         if (paragraphs().size() == 1)
1620                 return false;
1621
1622         // Do not delete empty paragraphs with keepempty set.
1623         if (old_pit->allowEmpty())
1624                 return false;
1625
1626         // only do our magic if we changed paragraph
1627         if (old_cursor.par() == cursor().par())
1628                 return false;
1629
1630         // record if we have deleted a paragraph
1631         // we can't possibly have deleted a paragraph before this point
1632         bool deleted = false;
1633
1634         if (old_pit->empty()
1635             || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1636                 // ok, we will delete something
1637                 CursorSlice tmpcursor;
1638
1639                 deleted = true;
1640
1641                 bool selection_position_was_oldcursor_position =
1642                         anchor().par() == old_cursor.par()
1643                         && anchor().pos() == old_cursor.pos();
1644
1645                 tmpcursor = cursor();
1646                 cursor() = old_cursor; // that undo can restore the right cursor position
1647
1648                 ParagraphList::iterator endpit = boost::next(old_pit);
1649                 while (endpit != paragraphs().end() && endpit->getDepth())
1650                         ++endpit;
1651
1652                 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1653                 cursor() = tmpcursor;
1654
1655                 // cache cursor pit
1656                 ParagraphList::iterator tmppit = cursorPar();
1657                 // delete old par
1658                 paragraphs().erase(old_pit);
1659                 // update cursor par offset
1660                 cursor().par(parOffset(tmppit));
1661                 redoParagraph();
1662
1663                 if (selection_position_was_oldcursor_position) {
1664                         // correct selection
1665                         bv()->resetAnchor();
1666                 }
1667         }
1668
1669         if (deleted)
1670                 return true;
1671
1672         if (old_pit->stripLeadingSpaces()) {
1673                 redoParagraph(old_pit);
1674                 bv()->resetAnchor();
1675         }
1676         return false;
1677 #endif
1678 }
1679
1680
1681 ParagraphList & LyXText::paragraphs() const
1682 {
1683         return const_cast<ParagraphList &>(paragraphs_);
1684 }
1685
1686
1687 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1688 {
1689         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1690 }
1691
1692
1693 void LyXText::recUndo(lyx::paroffset_type par) const
1694 {
1695         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1696 }
1697
1698
1699 bool LyXText::isInInset() const
1700 {
1701         return in_inset_;
1702 }
1703
1704
1705 bool LyXText::toggleInset()
1706 {
1707         InsetBase * inset = bv()->cursor().nextInset();
1708         // is there an editable inset at cursor position?
1709         if (!isEditableInset(inset))
1710                 return false;
1711         //bv()->owner()->message(inset->editMessage());
1712
1713         // do we want to keep this?? (JMarc)
1714         if (!isHighlyEditableInset(inset))
1715                 recUndo(cursor().par());
1716
1717         if (inset->isOpen())
1718                 inset->close();
1719         else
1720                 inset->open();
1721         return true;
1722 }
1723
1724
1725 int defaultRowHeight()
1726 {
1727         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1728 }