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