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