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