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