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