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