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