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