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