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