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