]> git.lyx.org Git - lyx.git/blob - src/text2.C
the update/updateInset merge
[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)
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->beginningOfBody();
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->beginningOfBody())
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(bv());
260                         bv()->getLyXText()->cursorRight(bv());
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(bv());
273         else
274                 inset->open(bv());
275
276         bv()->update();
277 }
278
279
280 // used in setLayout 
281 // Asger is not sure we want to do this...
282 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
283                                             Paragraph & par)
284 {
285         LyXLayout_ptr const & layout = par.layout();
286         pos_type const psize = par.size();
287
288         LyXFont layoutfont;
289         for (pos_type pos = 0; pos < psize; ++pos) {
290                 if (pos < par.beginningOfBody())
291                         layoutfont = layout->labelfont;
292                 else
293                         layoutfont = layout->font;
294
295                 LyXFont tmpfont = par.getFontSettings(params, pos);
296                 tmpfont.reduce(layoutfont);
297                 par.setFont(pos, tmpfont);
298         }
299 }
300
301
302 ParagraphList::iterator
303 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
304                    LyXCursor & send_cur,
305                    string const & layout)
306 {
307         ParagraphList::iterator endpit = boost::next(getPar(send_cur));
308         ParagraphList::iterator undoendpit = endpit;
309         ParagraphList::iterator pars_end = ownerParagraphs().end();
310
311         if (endpit != pars_end && endpit->getDepth()) {
312                 while (endpit != pars_end && endpit->getDepth()) {
313                         ++endpit;
314                         undoendpit = endpit;
315                 }
316         } else if (endpit != pars_end) {
317                 // because of parindents etc.
318                 ++endpit;
319         }
320
321         recUndo(sstart_cur.par(), parOffset(undoendpit) - 1);
322
323         // ok we have a selection. This is always between sstart_cur
324         // and sel_end cursor
325         cur = sstart_cur;
326         ParagraphList::iterator pit = getPar(sstart_cur);
327         ParagraphList::iterator epit = boost::next(getPar(send_cur));
328
329         BufferParams const & bufparams = bv()->buffer()->params();
330         LyXLayout_ptr const & lyxlayout =
331                 bufparams.getLyXTextClass()[layout];
332
333         do {
334                 pit->applyLayout(lyxlayout);
335                 makeFontEntriesLayoutSpecific(bufparams, *pit);
336                 pit->params().spaceTop(lyxlayout->fill_top ?
337                                          VSpace(VSpace::VFILL)
338                                          : VSpace(VSpace::NONE));
339                 pit->params().spaceBottom(lyxlayout->fill_bottom ?
340                                             VSpace(VSpace::VFILL)
341                                             : VSpace(VSpace::NONE));
342                 if (lyxlayout->margintype == MARGIN_MANUAL)
343                         pit->setLabelWidthString(lyxlayout->labelstring());
344                 cur.par(std::distance(ownerParagraphs().begin(), pit));
345                 ++pit;
346         } while (pit != epit);
347
348         return endpit;
349 }
350
351
352 // set layout over selection and make a total rebreak of those paragraphs
353 void LyXText::setLayout(string const & layout)
354 {
355         LyXCursor tmpcursor = cursor;  // store the current cursor
356
357         // if there is no selection just set the layout
358         // of the current paragraph
359         if (!selection.set()) {
360                 selection.start = cursor;  // dummy selection
361                 selection.end = cursor;
362         }
363
364         // special handling of new environment insets
365         BufferParams const & params = bv()->buffer()->params();
366         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
367         if (lyxlayout->is_environment) {
368                 // move everything in a new environment inset
369                 lyxerr << "setting layout " << layout << endl;
370                 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
371                 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
372                 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
373                 InsetOld * inset = new InsetEnvironment(params, layout);
374                 if (bv()->insertInset(inset)) {
375                         //inset->edit(bv());
376                         //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
377                 }
378                 else
379                         delete inset;
380                 return;
381         }
382
383         ParagraphList::iterator endpit = setLayout(cursor, selection.start,
384                                                    selection.end, layout);
385         redoParagraphs(getPar(selection.start), endpit);
386
387         // we have to reset the selection, because the
388         // geometry could have changed
389         setCursor(selection.start.par(), selection.start.pos(), false);
390         selection.cursor = cursor;
391         setCursor(selection.end.par(), selection.end.pos(), false);
392         updateCounters();
393         clearSelection();
394         setSelection();
395         setCursor(tmpcursor.par(), tmpcursor.pos(), true);
396 }
397
398
399 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
400 {
401         ParagraphList::iterator pit = cursorPar();
402         ParagraphList::iterator end = cursorPar();
403         ParagraphList::iterator start = pit;
404
405         if (selection.set()) {
406                 pit = getPar(selection.start);
407                 end = getPar(selection.end);
408                 start = pit;
409         }
410
411         ParagraphList::iterator pastend = boost::next(end);
412
413         if (!test_only)
414                 recUndo(parOffset(start), parOffset(end));
415
416         bool changed = false;
417
418         int prev_after_depth = 0;
419 #warning parlist ... could be nicer ?
420         if (start != ownerParagraphs().begin()) {
421                 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
422         }
423
424         while (true) {
425                 int const depth = pit->params().depth();
426                 if (type == bv_funcs::INC_DEPTH) {
427                         if (depth < prev_after_depth
428                             && pit->layout()->labeltype != LABEL_BIBLIO) {
429                                 changed = true;
430                                 if (!test_only)
431                                         pit->params().depth(depth + 1);
432                         }
433                 } else if (depth) {
434                         changed = true;
435                         if (!test_only)
436                                 pit->params().depth(depth - 1);
437                 }
438
439                 prev_after_depth = pit->getMaxDepthAfter();
440
441 #warning SERIOUS: Uahh... does this mean we access end->getMaxDepthAfter?
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 void LyXText::clearSelection()
553 {
554         TextCursor::clearSelection();
555
556         // reset this in the bv()!
557         if (bv() && bv()->text)
558                 bv()->text->xsel_cache.set(false);
559 }
560
561
562 void LyXText::cursorHome()
563 {
564         ParagraphList::iterator cpit = cursorPar();
565         setCursor(cpit, cpit->getRow(cursor.pos())->pos());
566 }
567
568
569 void LyXText::cursorEnd()
570 {
571         ParagraphList::iterator cpit = cursorPar();
572         pos_type end = cpit->getRow(cursor.pos())->endpos();
573         // if not on the last row of the par, put the cursor before
574         // the final space
575         setCursor(cpit, end == cpit->size() ? end : end - 1);
576 }
577
578
579 void LyXText::cursorTop()
580 {
581         setCursor(ownerParagraphs().begin(), 0);
582 }
583
584
585 void LyXText::cursorBottom()
586 {
587         ParagraphList::iterator lastpit =
588                 boost::prior(ownerParagraphs().end());
589         setCursor(lastpit, lastpit->size());
590 }
591
592
593 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
594 {
595         // If the mask is completely neutral, tell user
596         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
597                 // Could only happen with user style
598                 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
599                 return;
600         }
601
602         // Try implicit word selection
603         // If there is a change in the language the implicit word selection
604         // is disabled.
605         LyXCursor resetCursor = cursor;
606         bool implicitSelection =
607                 font.language() == ignore_language
608                 && font.number() == LyXFont::IGNORE
609                 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
610
611         // Set font
612         setFont(font, toggleall);
613
614         // Implicit selections are cleared afterwards
615         //and cursor is set to the original position.
616         if (implicitSelection) {
617                 clearSelection();
618                 cursor = resetCursor;
619                 setCursor(cursorPar(), cursor.pos());
620                 selection.cursor = cursor;
621         }
622 }
623
624
625 string LyXText::getStringToIndex()
626 {
627         // Try implicit word selection
628         // If there is a change in the language the implicit word selection
629         // is disabled.
630         LyXCursor const reset_cursor = cursor;
631         bool const implicitSelection =
632                 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
633
634         string idxstring;
635         if (!selection.set())
636                 bv()->owner()->message(_("Nothing to index!"));
637         else if (selection.start.par() != selection.end.par())
638                 bv()->owner()->message(_("Cannot index more than one paragraph!"));
639         else
640                 idxstring = selectionAsString(*bv()->buffer(), false);
641
642         // Reset cursors to their original position.
643         cursor = reset_cursor;
644         setCursor(cursorPar(), cursor.pos());
645         selection.cursor = cursor;
646
647         // Clear the implicit selection.
648         if (implicitSelection)
649                 clearSelection();
650
651         return idxstring;
652 }
653
654
655 // the DTP switches for paragraphs. LyX will store them in the first
656 // physical paragraph. When a paragraph is broken, the top settings rest,
657 // the bottom settings are given to the new one. So I can make sure,
658 // they do not duplicate themself and you cannot play dirty tricks with
659 // them!
660
661 void LyXText::setParagraph(
662                            VSpace const & space_top,
663                            VSpace const & space_bottom,
664                            Spacing const & spacing,
665                            LyXAlignment align,
666                            string const & labelwidthstring,
667                            bool noindent)
668 {
669         LyXCursor tmpcursor = cursor;
670         if (!selection.set()) {
671                 selection.start = cursor;
672                 selection.end = cursor;
673         }
674
675         // make sure that the depth behind the selection are restored, too
676         ParagraphList::iterator endpit = boost::next(getPar(selection.end));
677         ParagraphList::iterator undoendpit = endpit;
678         ParagraphList::iterator pars_end = ownerParagraphs().end();
679
680         if (endpit != pars_end && endpit->getDepth()) {
681                 while (endpit != pars_end && endpit->getDepth()) {
682                         ++endpit;
683                         undoendpit = endpit;
684                 }
685         } else if (endpit != pars_end) {
686                 // because of parindents etc.
687                 ++endpit;
688         }
689
690         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
691
692         int tmppit = selection.end.par();
693
694         while (tmppit != selection.start.par() - 1) {
695                 setCursor(tmppit, 0);
696
697                 ParagraphList::iterator const pit = cursorPar();
698                 ParagraphParameters & params = pit->params();
699                 params.spaceTop(space_top);
700                 params.spaceBottom(space_bottom);
701                 params.spacing(spacing);
702
703                 // does the layout allow the new alignment?
704                 LyXLayout_ptr const & layout = pit->layout();
705
706                 if (align == LYX_ALIGN_LAYOUT)
707                         align = layout->align;
708                 if (align & layout->alignpossible) {
709                         if (align == layout->align)
710                                 params.align(LYX_ALIGN_LAYOUT);
711                         else
712                                 params.align(align);
713                 }
714                 pit->setLabelWidthString(labelwidthstring);
715                 params.noindent(noindent);
716                 --tmppit;
717         }
718
719         redoParagraphs(getPar(selection.start), endpit);
720
721         clearSelection();
722         setCursor(selection.start.par(), selection.start.pos());
723         selection.cursor = cursor;
724         setCursor(selection.end.par(), selection.end.pos());
725         setSelection();
726         setCursor(tmpcursor.par(), tmpcursor.pos());
727         bv()->update();
728 }
729
730
731 namespace {
732
733 string expandLabel(LyXTextClass const & textclass,
734         LyXLayout_ptr const & layout, bool appendix)
735 {
736         string fmt = appendix ?
737                 layout->labelstring_appendix() : layout->labelstring();
738
739         // handle 'inherited level parts' in 'fmt',
740         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
741         size_t const i = fmt.find('@', 0);
742         if (i != string::npos) {
743                 size_t const j = fmt.find('@', i + 1);
744                 if (j != string::npos) {
745                         string parent(fmt, i + 1, j - i - 1);
746                         string label = expandLabel(textclass, textclass[parent], appendix);
747                         fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
748                 }
749         }
750
751         return textclass.counters().counterLabel(fmt);
752 }
753
754
755 void incrementItemDepth(ParagraphList::iterator pit,
756                         ParagraphList::iterator first_pit)
757 {
758         int const cur_labeltype = pit->layout()->labeltype;
759
760         if (cur_labeltype != LABEL_ENUMERATE &&
761             cur_labeltype != LABEL_ITEMIZE)
762                 return;
763
764         int const cur_depth = pit->getDepth();
765
766         ParagraphList::iterator prev_pit = boost::prior(pit);
767         while (true) {
768                 int const prev_depth = prev_pit->getDepth();
769                 int const prev_labeltype = prev_pit->layout()->labeltype;
770                 if (prev_depth == 0 && cur_depth > 0) {
771                         if (prev_labeltype == cur_labeltype) {
772                                 pit->itemdepth = prev_pit->itemdepth + 1;
773                         }
774                         break;
775                 } else if (prev_depth < cur_depth) {
776                         if (prev_labeltype == cur_labeltype) {
777                                 pit->itemdepth = prev_pit->itemdepth + 1;
778                                 break;
779                         }
780                 } else if (prev_depth == cur_depth) {
781                         if (prev_labeltype == cur_labeltype) {
782                                 pit->itemdepth = prev_pit->itemdepth;
783                                 break;
784                         }
785                 }
786                 if (prev_pit == first_pit)
787                         break;
788
789                 --prev_pit;
790         }
791 }
792
793
794 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
795                               ParagraphList::iterator firstpit,
796                               Counters & counters)
797 {
798         if (pit == firstpit)
799                 return;
800
801         int const cur_depth = pit->getDepth();
802         ParagraphList::iterator prev_pit = boost::prior(pit);
803         while (true) {
804                 int const prev_depth = prev_pit->getDepth();
805                 int const prev_labeltype = prev_pit->layout()->labeltype;
806                 if (prev_depth <= cur_depth) {
807                         if (prev_labeltype != LABEL_ENUMERATE) {
808                                 switch (pit->itemdepth) {
809                                 case 0:
810                                         counters.reset("enumi");
811                                 case 1:
812                                         counters.reset("enumii");
813                                 case 2:
814                                         counters.reset("enumiii");
815                                 case 3:
816                                         counters.reset("enumiv");
817                                 }
818                         }
819                         break;
820                 }
821
822                 if (prev_pit == firstpit)
823                         break;
824
825                 --prev_pit;
826         }
827 }
828
829 } // anon namespace
830
831
832 // set the counter of a paragraph. This includes the labels
833 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
834 {
835         BufferParams const & bufparams = buf.params();
836         LyXTextClass const & textclass = bufparams.getLyXTextClass();
837         LyXLayout_ptr const & layout = pit->layout();
838         ParagraphList::iterator first_pit = ownerParagraphs().begin();
839         Counters & counters = textclass.counters();
840
841         // Always reset
842         pit->itemdepth = 0;
843
844         if (pit == first_pit) {
845                 pit->params().appendix(pit->params().startOfAppendix());
846         } else {
847                 pit->params().appendix(boost::prior(pit)->params().appendix());
848                 if (!pit->params().appendix() &&
849                     pit->params().startOfAppendix()) {
850                         pit->params().appendix(true);
851                         textclass.counters().reset();
852                 }
853
854                 // Maybe we have to increment the item depth.
855                 incrementItemDepth(pit, first_pit);
856         }
857
858         // erase what was there before
859         pit->params().labelString(string());
860
861         if (layout->margintype == MARGIN_MANUAL) {
862                 if (pit->params().labelWidthString().empty())
863                         pit->setLabelWidthString(layout->labelstring());
864         } else {
865                 pit->setLabelWidthString(string());
866         }
867
868         // is it a layout that has an automatic label?
869         if (layout->labeltype == LABEL_COUNTER) {
870                 BufferParams const & bufparams = buf.params();
871                 LyXTextClass const & textclass = bufparams.getLyXTextClass();
872                 counters.step(layout->counter);
873                 string label = expandLabel(textclass, layout, pit->params().appendix());
874                 pit->params().labelString(label);
875         } else if (layout->labeltype == LABEL_ITEMIZE) {
876                 // At some point of time we should do something more
877                 // clever here, like:
878                 //   pit->params().labelString(
879                 //    bufparams.user_defined_bullet(pit->itemdepth).getText());
880                 // for now, use a simple hardcoded label
881                 string itemlabel;
882                 switch (pit->itemdepth) {
883                 case 0:
884                         itemlabel = "*";
885                         break;
886                 case 1:
887                         itemlabel = "-";
888                         break;
889                 case 2:
890                         itemlabel = "@";
891                         break;
892                 case 3:
893                         itemlabel = "·";
894                         break;
895                 }
896
897                 pit->params().labelString(itemlabel);
898         } else if (layout->labeltype == LABEL_ENUMERATE) {
899                 // Maybe we have to reset the enumeration counter.
900                 resetEnumCounterIfNeeded(pit, first_pit, counters);
901
902                 // FIXME
903                 // Yes I know this is a really, really! bad solution
904                 // (Lgb)
905                 string enumcounter = "enum";
906
907                 switch (pit->itemdepth) {
908                 case 2:
909                         enumcounter += 'i';
910                 case 1:
911                         enumcounter += 'i';
912                 case 0:
913                         enumcounter += 'i';
914                         break;
915                 case 3:
916                         enumcounter += "iv";
917                         break;
918                 default:
919                         // not a valid enumdepth...
920                         break;
921                 }
922
923                 counters.step(enumcounter);
924
925                 pit->params().labelString(counters.enumLabel(enumcounter));
926         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
927                 counters.step("bibitem");
928                 int number = counters.value("bibitem");
929                 if (pit->bibitem()) {
930                         pit->bibitem()->setCounter(number);
931                         pit->params().labelString(layout->labelstring());
932                 }
933                 // In biblio should't be following counters but...
934         } else {
935                 string s = buf.B_(layout->labelstring());
936
937                 // the caption hack:
938                 if (layout->labeltype == LABEL_SENSITIVE) {
939                         ParagraphList::iterator end = ownerParagraphs().end();
940                         ParagraphList::iterator tmppit = pit;
941                         InsetOld * in = 0;
942                         bool isOK = false;
943                         while (tmppit != end && tmppit->inInset()
944                                // the single '=' is intended below
945                                && (in = tmppit->inInset()->owner()))
946                         {
947                                 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
948                                     in->lyxCode() == InsetOld::WRAP_CODE) {
949                                         isOK = true;
950                                         break;
951                                 } else {
952                                         Paragraph const * owner = &ownerPar(buf, in);
953                                         tmppit = first_pit;
954                                         for ( ; tmppit != end; ++tmppit)
955                                                 if (&*tmppit == owner)
956                                                         break;
957                                 }
958                         }
959
960                         if (isOK) {
961                                 string type;
962
963                                 if (in->lyxCode() == InsetOld::FLOAT_CODE)
964                                         type = static_cast<InsetFloat*>(in)->params().type;
965                                 else if (in->lyxCode() == InsetOld::WRAP_CODE)
966                                         type = static_cast<InsetWrap*>(in)->params().type;
967                                 else
968                                         BOOST_ASSERT(false);
969
970                                 Floating const & fl = textclass.floats().getType(type);
971
972                                 counters.step(fl.type());
973
974                                 // Doesn't work... yet.
975                                 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
976                         } else {
977                                 // par->SetLayout(0);
978                                 // s = layout->labelstring;
979                                 s = _("Senseless: ");
980                         }
981                 }
982                 pit->params().labelString(s);
983
984         }
985 }
986
987
988 // Updates all counters. Paragraphs with changed label string will be rebroken
989 void LyXText::updateCounters()
990 {
991         // start over
992         bv()->buffer()->params().getLyXTextClass().counters().reset();
993
994         ParagraphList::iterator beg = ownerParagraphs().begin();
995         ParagraphList::iterator end = ownerParagraphs().end();
996         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
997                 string const oldLabel = pit->params().labelString();
998
999                 size_t maxdepth = 0;
1000                 if (pit != beg)
1001                         maxdepth = boost::prior(pit)->getMaxDepthAfter();
1002
1003                 if (pit->params().depth() > maxdepth)
1004                         pit->params().depth(maxdepth);
1005
1006                 // setCounter can potentially change the labelString.
1007                 setCounter(*bv()->buffer(), pit);
1008
1009                 string const & newLabel = pit->params().labelString();
1010
1011                 if (oldLabel != newLabel)
1012                         redoParagraph(pit);
1013         }
1014 }
1015
1016
1017 void LyXText::insertInset(InsetOld * inset)
1018 {
1019         if (!cursorPar()->insetAllowed(inset->lyxCode()))
1020                 return;
1021
1022         recUndo(cursor.par());
1023         freezeUndo();
1024         cursorPar()->insertInset(cursor.pos(), inset);
1025         // Just to rebreak and refresh correctly.
1026         // The character will not be inserted a second time
1027         insertChar(Paragraph::META_INSET);
1028         // If we enter a highly editable inset the cursor should be before
1029         // the inset. After an Undo LyX tries to call inset->edit(...)
1030         // and fails if the cursor is behind the inset and getInset
1031         // does not return the inset!
1032         if (isHighlyEditableInset(inset))
1033                 cursorLeft(true);
1034         unFreezeUndo();
1035 }
1036
1037
1038 void LyXText::cutSelection(bool doclear, bool realcut)
1039 {
1040         // Stuff what we got on the clipboard. Even if there is no selection.
1041
1042         // There is a problem with having the stuffing here in that the
1043         // larger the selection the slower LyX will get. This can be
1044         // solved by running the line below only when the selection has
1045         // finished. The solution used currently just works, to make it
1046         // faster we need to be more clever and probably also have more
1047         // calls to stuffClipboard. (Lgb)
1048         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1049
1050         // This doesn't make sense, if there is no selection
1051         if (!selection.set())
1052                 return;
1053
1054         // OK, we have a selection. This is always between selection.start
1055         // and selection.end
1056
1057         // make sure that the depth behind the selection are restored, too
1058         ParagraphList::iterator endpit = boost::next(getPar(selection.end.par()));
1059         ParagraphList::iterator undoendpit = endpit;
1060         ParagraphList::iterator pars_end = ownerParagraphs().end();
1061
1062         if (endpit != pars_end && endpit->getDepth()) {
1063                 while (endpit != pars_end && endpit->getDepth()) {
1064                         ++endpit;
1065                         undoendpit = endpit;
1066                 }
1067         } else if (endpit != pars_end) {
1068                 // because of parindents etc.
1069                 ++endpit;
1070         }
1071
1072         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
1073
1074         endpit = getPar(selection.end.par());
1075         int endpos = selection.end.pos();
1076
1077         BufferParams const & bufparams = bv()->buffer()->params();
1078         boost::tie(endpit, endpos) = realcut ?
1079                 CutAndPaste::cutSelection(bufparams,
1080                                           ownerParagraphs(),
1081                                           getPar(selection.start.par()), endpit,
1082                                           selection.start.pos(), endpos,
1083                                           bufparams.textclass,
1084                                           doclear)
1085                 : CutAndPaste::eraseSelection(bufparams,
1086                                               ownerParagraphs(),
1087                                               getPar(selection.start.par()), endpit,
1088                                               selection.start.pos(), endpos,
1089                                               doclear);
1090         // sometimes necessary
1091         if (doclear)
1092                 getPar(selection.start.par())->stripLeadingSpaces();
1093
1094         redoParagraphs(getPar(selection.start.par()), boost::next(endpit));
1095         // cutSelection can invalidate the cursor so we need to set
1096         // it anew. (Lgb)
1097         // we prefer the end for when tracking changes
1098         cursor.pos(endpos);
1099         cursor.par(parOffset(endpit));
1100
1101         // need a valid cursor. (Lgb)
1102         clearSelection();
1103
1104         setCursor(cursorPar(), cursor.pos());
1105         selection.cursor = cursor;
1106         updateCounters();
1107 }
1108
1109
1110 void LyXText::copySelection()
1111 {
1112         // stuff the selection onto the X clipboard, from an explicit copy request
1113         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1114
1115         // this doesnt make sense, if there is no selection
1116         if (!selection.set())
1117                 return;
1118
1119         // ok we have a selection. This is always between selection.start
1120         // and sel_end cursor
1121
1122         // copy behind a space if there is one
1123         while (getPar(selection.start)->size() > selection.start.pos()
1124                && getPar(selection.start)->isLineSeparator(selection.start.pos())
1125                && (selection.start.par() != selection.end.par()
1126                    || selection.start.pos() < selection.end.pos()))
1127                 selection.start.pos(selection.start.pos() + 1);
1128
1129         CutAndPaste::copySelection(getPar(selection.start.par()),
1130                                    getPar(selection.end.par()),
1131                                    selection.start.pos(), selection.end.pos(),
1132                                    bv()->buffer()->params().textclass);
1133 }
1134
1135
1136 void LyXText::pasteSelection(size_t sel_index)
1137 {
1138         // this does not make sense, if there is nothing to paste
1139         if (!CutAndPaste::checkPastePossible())
1140                 return;
1141
1142         recUndo(cursor.par());
1143
1144         ParagraphList::iterator endpit;
1145         PitPosPair ppp;
1146
1147         ErrorList el;
1148
1149         boost::tie(ppp, endpit) =
1150                 CutAndPaste::pasteSelection(*bv()->buffer(),
1151                                             ownerParagraphs(),
1152                                             cursorPar(), cursor.pos(),
1153                                             bv()->buffer()->params().textclass,
1154                                             sel_index, el);
1155         bufferErrors(*bv()->buffer(), el);
1156         bv()->showErrorList(_("Paste"));
1157
1158         redoParagraphs(cursorPar(), endpit);
1159
1160         setCursor(cursor.par(), cursor.pos());
1161         clearSelection();
1162
1163         selection.cursor = cursor;
1164         setCursor(ppp.first, ppp.second);
1165         setSelection();
1166         updateCounters();
1167 }
1168
1169
1170 void LyXText::setSelectionRange(lyx::pos_type length)
1171 {
1172         if (!length)
1173                 return;
1174
1175         selection.cursor = cursor;
1176         while (length--)
1177                 cursorRight(bv());
1178         setSelection();
1179 }
1180
1181
1182 // simple replacing. The font of the first selected character is used
1183 void LyXText::replaceSelectionWithString(string const & str)
1184 {
1185         recUndo(cursor.par());
1186         freezeUndo();
1187
1188         if (!selection.set()) { // create a dummy selection
1189                 selection.end = cursor;
1190                 selection.start = cursor;
1191         }
1192
1193         // Get font setting before we cut
1194         pos_type pos = selection.end.pos();
1195         LyXFont const font = getPar(selection.start)
1196                 ->getFontSettings(bv()->buffer()->params(),
1197                                   selection.start.pos());
1198
1199         // Insert the new string
1200         string::const_iterator cit = str.begin();
1201         string::const_iterator end = str.end();
1202         for (; cit != end; ++cit) {
1203                 getPar(selection.end)->insertChar(pos, (*cit), font);
1204                 ++pos;
1205         }
1206
1207         // Cut the selection
1208         cutSelection(true, false);
1209
1210         unFreezeUndo();
1211 }
1212
1213
1214 // needed to insert the selection
1215 void LyXText::insertStringAsLines(string const & str)
1216 {
1217         ParagraphList::iterator pit = cursorPar();
1218         pos_type pos = cursor.pos();
1219         ParagraphList::iterator endpit = boost::next(cursorPar());
1220
1221         recUndo(cursor.par());
1222
1223         // only to be sure, should not be neccessary
1224         clearSelection();
1225
1226         bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1227
1228         redoParagraphs(cursorPar(), endpit);
1229         setCursor(cursorPar(), cursor.pos());
1230         selection.cursor = cursor;
1231         setCursor(pit, pos);
1232         setSelection();
1233 }
1234
1235
1236 // turns double-CR to single CR, others where converted into one
1237 // blank. Then InsertStringAsLines is called
1238 void LyXText::insertStringAsParagraphs(string const & str)
1239 {
1240         string linestr(str);
1241         bool newline_inserted = false;
1242         string::size_type const siz = linestr.length();
1243
1244         for (string::size_type i = 0; i < siz; ++i) {
1245                 if (linestr[i] == '\n') {
1246                         if (newline_inserted) {
1247                                 // we know that \r will be ignored by
1248                                 // InsertStringA. Of course, it is a dirty
1249                                 // trick, but it works...
1250                                 linestr[i - 1] = '\r';
1251                                 linestr[i] = '\n';
1252                         } else {
1253                                 linestr[i] = ' ';
1254                                 newline_inserted = true;
1255                         }
1256                 } else if (IsPrintable(linestr[i])) {
1257                         newline_inserted = false;
1258                 }
1259         }
1260         insertStringAsLines(linestr);
1261 }
1262
1263
1264 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1265 {
1266         setCursor(parOffset(pit), pos);
1267 }
1268
1269
1270 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont, bool boundary)
1271 {
1272         LyXCursor old_cursor = cursor;
1273         setCursorIntern(par, pos, setfont, boundary);
1274         return deleteEmptyParagraphMechanism(old_cursor);
1275 }
1276
1277
1278 void LyXText::redoCursor()
1279 {
1280 #warning maybe the same for selections?
1281         setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1282 }
1283
1284
1285 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1286         pos_type pos, bool boundary)
1287 {
1288         BOOST_ASSERT(par != int(ownerParagraphs().size()));
1289
1290         cur.par(par);
1291         cur.pos(pos);
1292         cur.boundary(boundary);
1293
1294         // no rows, no fun...
1295         if (ownerParagraphs().begin()->rows.empty())
1296                 return;
1297
1298         // get the cursor y position in text
1299
1300         ParagraphList::iterator pit = getPar(par);
1301         Row const & row = *pit->getRow(pos);
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         bv()->x_target(cur.x());
1343 }
1344
1345
1346 float LyXText::getCursorX(ParagraphList::iterator pit, Row const & row,
1347                           pos_type pos, bool boundary) const
1348 {
1349         pos_type cursor_vpos    = 0;
1350         double x                = row.x();
1351         double fill_separator   = row.fill_separator();
1352         double fill_hfill       = row.fill_hfill();
1353         double fill_label_hfill = row.fill_label_hfill();
1354         pos_type const row_pos  = row.pos();
1355         pos_type const end = row.endpos();
1356
1357         if (end <= row_pos)
1358                 cursor_vpos = row_pos;
1359         else if (pos >= end && !boundary)
1360                 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params()))
1361                         ? row_pos : end;
1362         else if (pos > row_pos && (pos >= end || boundary))
1363                 // Place cursor after char at (logical) position pos - 1
1364                 cursor_vpos = (bidi.level(pos - 1) % 2 == 0)
1365                         ? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1);
1366         else
1367                 // Place cursor before char at (logical) position pos
1368                 cursor_vpos = (bidi.level(pos) % 2 == 0)
1369                         ? bidi.log2vis(pos) : bidi.log2vis(pos) + 1;
1370
1371         pos_type body_pos = pit->beginningOfBody();
1372         if (body_pos > 0 &&
1373             (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1374                 body_pos = 0;
1375
1376         for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
1377                 pos_type pos = bidi.vis2log(vpos);
1378                 if (body_pos > 0 && pos == body_pos - 1) {
1379                         x += fill_label_hfill
1380                                 + font_metrics::width(pit->layout()->labelsep,
1381                                                       getLabelFont(pit));
1382                         if (pit->isLineSeparator(body_pos - 1))
1383                                 x -= singleWidth(pit, body_pos - 1);
1384                 }
1385
1386                 if (hfillExpansion(*pit, row, pos)) {
1387                         x += singleWidth(pit, pos);
1388                         if (pos >= body_pos)
1389                                 x += fill_hfill;
1390                         else
1391                                 x += fill_label_hfill;
1392                 } else if (pit->isSeparator(pos)) {
1393                         x += singleWidth(pit, pos);
1394                         if (pos >= body_pos)
1395                                 x += fill_separator;
1396                 } else
1397                         x += singleWidth(pit, pos);
1398         }
1399         return x;
1400 }
1401
1402
1403 void LyXText::setCursorIntern(paroffset_type par,
1404                               pos_type pos, bool setfont, bool boundary)
1405 {
1406         setCursor(cursor, par, pos, boundary);
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->beginningOfBody();
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
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 void LyXText::cursorLeft(bool internal)
1584 {
1585         if (cursor.pos() > 0) {
1586                 bool boundary = cursor.boundary();
1587                 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1588                 if (!internal && !boundary &&
1589                     bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1590                         setCursor(cursor.par(), cursor.pos() + 1, true, true);
1591         } else if (cursor.par() != 0) {
1592                 // steps into the paragraph above
1593                 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1594         }
1595 }
1596
1597
1598 void LyXText::cursorRight(bool internal)
1599 {
1600         bool const at_end = (cursor.pos() == cursorPar()->size());
1601         bool const at_newline = !at_end &&
1602                 cursorPar()->isNewline(cursor.pos());
1603
1604         if (!internal && cursor.boundary() && !at_newline)
1605                 setCursor(cursor.par(), cursor.pos(), true, false);
1606         else if (!at_end) {
1607                 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1608                 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1609                                                  cursor.pos()))
1610                         setCursor(cursor.par(), cursor.pos(), true, true);
1611         } else if (cursor.par() + 1 != int(ownerParagraphs().size()))
1612                 setCursor(cursor.par() + 1, 0);
1613 }
1614
1615
1616 void LyXText::cursorUp(bool selecting)
1617 {
1618         ParagraphList::iterator cpit = cursorPar();
1619         Row const & crow = *cpit->getRow(cursor.pos());
1620 #if 1
1621         int x = bv()->x_target();
1622         int y = cursor.y() - crow.baseline() - 1;
1623         setCursorFromCoordinates(x, y);
1624         if (!selecting) {
1625                 int topy = bv()->top_y();
1626                 int y1 = cursor.y() - topy;
1627                 int y2 = y1;
1628                 y -= topy;
1629                 InsetOld * inset_hit = checkInsetHit(x, y1);
1630                 if (inset_hit && isHighlyEditableInset(inset_hit))
1631                         inset_hit->edit(bv(), x, y - (y2 - y1));
1632         }
1633 #else
1634         lyxerr << "cursorUp: y " << cursor.y() << " bl: " <<
1635                 crow.baseline() << endl;
1636         setCursorFromCoordinates(bv()->x_target(),
1637                 cursor.y() - crow.baseline() - 1);
1638 #endif
1639 }
1640
1641
1642 void LyXText::cursorDown(bool selecting)
1643 {
1644         ParagraphList::iterator cpit = cursorPar();
1645         Row const & crow = *cpit->getRow(cursor.pos());
1646 #if 1
1647         int x = bv()->x_target();
1648         int y = cursor.y() - crow.baseline() + crow.height() + 1;
1649         setCursorFromCoordinates(x, y);
1650         if (!selecting) {
1651                 int topy = bv()->top_y();
1652                 int y1 = cursor.y() - topy;
1653                 int y2 = y1;
1654                 y -= topy;
1655                 InsetOld * inset_hit = checkInsetHit(x, y1);
1656                 if (inset_hit && isHighlyEditableInset(inset_hit))
1657                         inset_hit->edit(bv(), x, y - (y2 - y1));
1658         }
1659 #else
1660         setCursorFromCoordinates(bv()->x_target(),
1661                  cursor.y() - crow.baseline() + crow.height() + 1);
1662 #endif
1663 }
1664
1665
1666 void LyXText::cursorUpParagraph()
1667 {
1668         ParagraphList::iterator cpit = cursorPar();
1669         if (cursor.pos() > 0)
1670                 setCursor(cpit, 0);
1671         else if (cpit != ownerParagraphs().begin())
1672                 setCursor(boost::prior(cpit), 0);
1673 }
1674
1675
1676 void LyXText::cursorDownParagraph()
1677 {
1678         ParagraphList::iterator pit = cursorPar();
1679         ParagraphList::iterator next_pit = boost::next(pit);
1680
1681         if (next_pit != ownerParagraphs().end())
1682                 setCursor(next_pit, 0);
1683         else
1684                 setCursor(pit, pit->size());
1685 }
1686
1687
1688 // fix the cursor `cur' after a characters has been deleted at `where'
1689 // position. Called by deleteEmptyParagraphMechanism
1690 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1691 {
1692         // if cursor is not in the paragraph where the delete occured,
1693         // do nothing
1694         if (cur.par() != where.par())
1695                 return;
1696
1697         // if cursor position is after the place where the delete occured,
1698         // update it
1699         if (cur.pos() > where.pos())
1700                 cur.pos(cur.pos()-1);
1701
1702         // check also if we don't want to set the cursor on a spot behind the
1703         // pagragraph because we erased the last character.
1704         if (cur.pos() > getPar(cur)->size())
1705                 cur.pos(getPar(cur)->size());
1706
1707         // recompute row et al. for this cursor
1708         setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1709 }
1710
1711
1712 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1713 {
1714         // Would be wrong to delete anything if we have a selection.
1715         if (selection.set())
1716                 return false;
1717
1718         // Don't do anything if the cursor is invalid
1719         if (old_cursor.par() == -1)
1720                 return false;
1721
1722         // We allow all kinds of "mumbo-jumbo" when freespacing.
1723         ParagraphList::iterator const old_pit = getPar(old_cursor);
1724         if (old_pit->isFreeSpacing())
1725                 return false;
1726
1727         /* Ok I'll put some comments here about what is missing.
1728            I have fixed BackSpace (and thus Delete) to not delete
1729            double-spaces automagically. I have also changed Cut,
1730            Copy and Paste to hopefully do some sensible things.
1731            There are still some small problems that can lead to
1732            double spaces stored in the document file or space at
1733            the beginning of paragraphs. This happens if you have
1734            the cursor between to spaces and then save. Or if you
1735            cut and paste and the selection have a space at the
1736            beginning and then save right after the paste. I am
1737            sure none of these are very hard to fix, but I will
1738            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1739            that I can get some feedback. (Lgb)
1740         */
1741
1742         // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1743         // delete the LineSeparator.
1744         // MISSING
1745
1746         // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1747         // delete the LineSeparator.
1748         // MISSING
1749
1750         // If the pos around the old_cursor were spaces, delete one of them.
1751         if (old_cursor.par() != cursor.par()
1752             || old_cursor.pos() != cursor.pos()) {
1753
1754                 // Only if the cursor has really moved
1755                 if (old_cursor.pos() > 0
1756                     && old_cursor.pos() < old_pit->size()
1757                     && old_pit->isLineSeparator(old_cursor.pos())
1758                     && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1759                         bool erased = old_pit->erase(old_cursor.pos() - 1);
1760                         redoParagraph(old_pit);
1761
1762                         if (!erased)
1763                                 return false;
1764 #ifdef WITH_WARNINGS
1765 #warning This will not work anymore when we have multiple views of the same buffer
1766 // In this case, we will have to correct also the cursors held by
1767 // other bufferviews. It will probably be easier to do that in a more
1768 // automated way in LyXCursor code. (JMarc 26/09/2001)
1769 #endif
1770                         // correct all cursors held by the LyXText
1771                         fixCursorAfterDelete(cursor, old_cursor);
1772                         fixCursorAfterDelete(selection.cursor, old_cursor);
1773                         fixCursorAfterDelete(selection.start, old_cursor);
1774                         fixCursorAfterDelete(selection.end, old_cursor);
1775                         return false;
1776                 }
1777         }
1778
1779         // don't delete anything if this is the ONLY paragraph!
1780         if (ownerParagraphs().size() == 1)
1781                 return false;
1782
1783         // Do not delete empty paragraphs with keepempty set.
1784         if (old_pit->allowEmpty())
1785                 return false;
1786
1787         // only do our magic if we changed paragraph
1788         if (old_cursor.par() == cursor.par())
1789                 return false;
1790
1791         // record if we have deleted a paragraph
1792         // we can't possibly have deleted a paragraph before this point
1793         bool deleted = false;
1794
1795         if (old_pit->empty()
1796             || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1797                 // ok, we will delete something
1798                 LyXCursor tmpcursor;
1799
1800                 deleted = true;
1801
1802                 bool selection_position_was_oldcursor_position =
1803                         selection.cursor.par() == old_cursor.par()
1804                         && selection.cursor.pos() == old_cursor.pos();
1805
1806                 tmpcursor = cursor;
1807                 cursor = old_cursor; // that undo can restore the right cursor position
1808
1809                 ParagraphList::iterator endpit = boost::next(old_pit);
1810                 while (endpit != ownerParagraphs().end() && endpit->getDepth())
1811                         ++endpit;
1812
1813                 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1814                 cursor = tmpcursor;
1815
1816                 // cache cursor pit
1817                 ParagraphList::iterator tmppit = cursorPar();
1818                 // delete old par
1819                 ownerParagraphs().erase(old_pit);
1820                 // update cursor par offset
1821                 cursor.par(parOffset(tmppit));
1822                 redoParagraph();
1823
1824                 // correct cursor y
1825                 setCursorIntern(cursor.par(), cursor.pos());
1826
1827                 if (selection_position_was_oldcursor_position) {
1828                         // correct selection
1829                         selection.cursor = cursor;
1830                 }
1831         }
1832
1833         if (deleted)
1834                 return true;
1835
1836         if (old_pit->stripLeadingSpaces()) {
1837                 redoParagraph(old_pit);
1838                 // correct cursor y
1839                 setCursorIntern(cursor.par(), cursor.pos());
1840                 selection.cursor = cursor;
1841         }
1842         return false;
1843 }
1844
1845
1846 ParagraphList & LyXText::ownerParagraphs() const
1847 {
1848         return *paragraphs_;
1849 }
1850
1851
1852 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1853 {
1854         recordUndo(Undo::ATOMIC, this, first, last);
1855 }
1856
1857
1858 void LyXText::recUndo(lyx::paroffset_type par) const
1859 {
1860         recordUndo(Undo::ATOMIC, this, par, par);
1861 }
1862
1863
1864 bool LyXText::isInInset() const
1865 {
1866         // Sub-level has non-null bv owner and non-null inset owner.
1867         return inset_owner != 0;
1868 }
1869
1870
1871 int defaultRowHeight()
1872 {
1873         LyXFont const font(LyXFont::ALL_SANE);
1874         return int(font_metrics::maxHeight(font) *  1.2);
1875 }