]> git.lyx.org Git - lyx.git/blob - src/text2.C
a9a9bc00243711d37ed919722e28e20734f80399
[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         for (rit = pit->rows.begin(); rit != end; ++rit) {
579                 int const f = fill(pit, rit, ww);
580                 int const w = ww - f;
581                 par_width = std::max(par_width, w);
582                 rit->fill(f);
583                 rit->width(w);
584                 prepareToPrint(pit, rit);
585                 setHeightOfRow(pit, rit);
586                 height += rit->height();
587         }
588
589         //lyxerr << "redoParagraph: " << pit->rows.size() << " rows\n";
590         return par_width;
591 }
592
593
594 int LyXText::redoParagraphs(ParagraphList::iterator start,
595   ParagraphList::iterator end)
596 {
597         int pars_width = 0;
598         for ( ; start != end; ++start) {
599                 int par_width = redoParagraphInternal(start);
600                 pars_width = std::max(par_width, pars_width);
601         }
602         updateRowPositions();
603         return pars_width;
604 }
605
606
607 void LyXText::redoParagraph(ParagraphList::iterator pit)
608 {
609         redoParagraphInternal(pit);
610         updateRowPositions();
611 }
612
613
614 void LyXText::fullRebreak()
615 {
616         redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
617         redoCursor();
618         selection.cursor = cursor;
619 }
620
621
622 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
623 {
624         //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth
625         //      << " workWidth: " << workWidth() << endl;
626         //BOOST_ASSERT(mi.base.textwidth);
627
628         // rebuild row cache
629         width  = 0;
630         ///height = 0;
631
632         //anchor_y_ = 0;
633         width = redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
634
635         // final dimension
636         dim.asc = firstRow()->ascent_of_text();
637         dim.des = height - dim.asc;
638         dim.wid = std::max(mi.base.textwidth, int(width));
639 }
640
641
642 // important for the screen
643
644
645 // the cursor set functions have a special mechanism. When they
646 // realize, that you left an empty paragraph, they will delete it.
647 // They also delete the corresponding row
648
649 // need the selection cursor:
650 void LyXText::setSelection()
651 {
652         TextCursor::setSelection();
653 }
654
655
656
657 void LyXText::clearSelection()
658 {
659         TextCursor::clearSelection();
660
661         // reset this in the bv_owner!
662         if (bv_owner && bv_owner->text)
663                 bv_owner->text->xsel_cache.set(false);
664 }
665
666
667 void LyXText::cursorHome()
668 {
669         setCursor(cursorPar(), cursorRow()->pos());
670 }
671
672
673 void LyXText::cursorEnd()
674 {
675         setCursor(cursorPar(), cursorRow()->end() - 1);
676 }
677
678
679 void LyXText::cursorTop()
680 {
681         setCursor(ownerParagraphs().begin(), 0);
682 }
683
684
685 void LyXText::cursorBottom()
686 {
687         ParagraphList::iterator lastpit =
688                 boost::prior(ownerParagraphs().end());
689         setCursor(lastpit, lastpit->size());
690 }
691
692
693 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
694 {
695         // If the mask is completely neutral, tell user
696         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
697                 // Could only happen with user style
698                 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
699                 return;
700         }
701
702         // Try implicit word selection
703         // If there is a change in the language the implicit word selection
704         // is disabled.
705         LyXCursor resetCursor = cursor;
706         bool implicitSelection = (font.language() == ignore_language
707                                   && font.number() == LyXFont::IGNORE)
708                 ? selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT) : false;
709
710         // Set font
711         setFont(font, toggleall);
712
713         // Implicit selections are cleared afterwards
714         //and cursor is set to the original position.
715         if (implicitSelection) {
716                 clearSelection();
717                 cursor = resetCursor;
718                 setCursor(cursorPar(), cursor.pos());
719                 selection.cursor = cursor;
720         }
721 }
722
723
724 string LyXText::getStringToIndex()
725 {
726         // Try implicit word selection
727         // If there is a change in the language the implicit word selection
728         // is disabled.
729         LyXCursor const reset_cursor = cursor;
730         bool const implicitSelection =
731                 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
732
733         string idxstring;
734         if (!selection.set())
735                 bv()->owner()->message(_("Nothing to index!"));
736         else if (selection.start.par() != selection.end.par())
737                 bv()->owner()->message(_("Cannot index more than one paragraph!"));
738         else
739                 idxstring = selectionAsString(*bv()->buffer(), false);
740
741         // Reset cursors to their original position.
742         cursor = reset_cursor;
743         setCursor(cursorPar(), cursor.pos());
744         selection.cursor = cursor;
745
746         // Clear the implicit selection.
747         if (implicitSelection)
748                 clearSelection();
749
750         return idxstring;
751 }
752
753
754 // the DTP switches for paragraphs. LyX will store them in the first
755 // physical paragraph. When a paragraph is broken, the top settings rest,
756 // the bottom settings are given to the new one. So I can make sure,
757 // they do not duplicate themself and you cannnot make dirty things with
758 // them!
759
760 void LyXText::setParagraph(bool line_top, bool line_bottom,
761                            bool pagebreak_top, bool pagebreak_bottom,
762                            VSpace const & space_top,
763                            VSpace const & space_bottom,
764                            Spacing const & spacing,
765                            LyXAlignment align,
766                            string const & labelwidthstring,
767                            bool noindent)
768 {
769         LyXCursor tmpcursor = cursor;
770         if (!selection.set()) {
771                 selection.start = cursor;
772                 selection.end = cursor;
773         }
774
775         // make sure that the depth behind the selection are restored, too
776         ParagraphList::iterator endpit = boost::next(getPar(selection.end));
777         ParagraphList::iterator undoendpit = endpit;
778         ParagraphList::iterator pars_end = ownerParagraphs().end();
779
780         if (endpit != pars_end && endpit->getDepth()) {
781                 while (endpit != pars_end && endpit->getDepth()) {
782                         ++endpit;
783                         undoendpit = endpit;
784                 }
785         } else if (endpit != pars_end) {
786                 // because of parindents etc.
787                 ++endpit;
788         }
789
790         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
791
792
793         int tmppit = selection.end.par();
794
795         while (tmppit != selection.start.par() - 1) {
796                 setCursor(tmppit, 0);
797
798                 ParagraphList::iterator const pit = cursorPar();
799                 ParagraphParameters & params = pit->params();
800
801                 params.lineTop(line_top);
802                 params.lineBottom(line_bottom);
803                 params.pagebreakTop(pagebreak_top);
804                 params.pagebreakBottom(pagebreak_bottom);
805                 params.spaceTop(space_top);
806                 params.spaceBottom(space_bottom);
807                 params.spacing(spacing);
808                 // does the layout allow the new alignment?
809                 LyXLayout_ptr const & layout = pit->layout();
810
811                 if (align == LYX_ALIGN_LAYOUT)
812                         align = layout->align;
813                 if (align & layout->alignpossible) {
814                         if (align == layout->align)
815                                 params.align(LYX_ALIGN_LAYOUT);
816                         else
817                                 params.align(align);
818                 }
819                 pit->setLabelWidthString(labelwidthstring);
820                 params.noindent(noindent);
821                 --tmppit;
822         }
823
824         redoParagraphs(getPar(selection.start), endpit);
825
826         clearSelection();
827         setCursor(selection.start.par(), selection.start.pos());
828         selection.cursor = cursor;
829         setCursor(selection.end.par(), selection.end.pos());
830         setSelection();
831         setCursor(tmpcursor.par(), tmpcursor.pos());
832         if (inset_owner)
833                 bv()->updateInset(inset_owner);
834 }
835
836
837 namespace {
838
839 string expandLabel(LyXTextClass const & textclass,
840         LyXLayout_ptr const & layout, bool appendix)
841 {
842         string fmt = appendix ?
843                 layout->labelstring_appendix() : layout->labelstring();
844
845         // handle 'inherited level parts' in 'fmt',
846         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
847         size_t const i = fmt.find('@', 0);
848         if (i != string::npos) {
849                 size_t const j = fmt.find('@', i + 1);
850                 if (j != string::npos) {
851                         string parent(fmt, i + 1, j - i - 1);
852                         string label = expandLabel(textclass, textclass[parent], appendix);
853                         fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
854                 }
855         }
856
857         return textclass.counters().counterLabel(fmt);
858 }
859
860
861 void incrementItemDepth(ParagraphList::iterator pit,
862                         ParagraphList::iterator first_pit)
863 {
864         int const cur_labeltype = pit->layout()->labeltype;
865
866         if (cur_labeltype != LABEL_ENUMERATE &&
867             cur_labeltype != LABEL_ITEMIZE)
868                 return;
869
870         int const cur_depth = pit->getDepth();
871
872         ParagraphList::iterator prev_pit = boost::prior(pit);
873         while (true) {
874                 int const prev_depth = prev_pit->getDepth();
875                 int const prev_labeltype = prev_pit->layout()->labeltype;
876                 if (prev_depth == 0 && cur_depth > 0) {
877                         if (prev_labeltype == cur_labeltype) {
878                                 pit->itemdepth = prev_pit->itemdepth + 1;
879                         }
880                         break;
881                 } else if (prev_depth < cur_depth) {
882                         if (prev_labeltype == cur_labeltype) {
883                                 pit->itemdepth = prev_pit->itemdepth + 1;
884                                 break;
885                         }
886                 } else if (prev_depth == cur_depth) {
887                         if (prev_labeltype == cur_labeltype) {
888                                 pit->itemdepth = prev_pit->itemdepth;
889                                 break;
890                         }
891                 }
892                 if (prev_pit == first_pit)
893                         break;
894
895                 --prev_pit;
896         }
897 }
898
899
900 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
901                               ParagraphList::iterator firstpit,
902                               Counters & counters)
903 {
904         if (pit == firstpit)
905                 return;
906
907         int const cur_depth = pit->getDepth();
908         ParagraphList::iterator prev_pit = boost::prior(pit);
909         while (true) {
910                 int const prev_depth = prev_pit->getDepth();
911                 int const prev_labeltype = prev_pit->layout()->labeltype;
912                 if (prev_depth <= cur_depth) {
913                         if (prev_labeltype != LABEL_ENUMERATE) {
914                                 switch (pit->itemdepth) {
915                                 case 0:
916                                         counters.reset("enumi");
917                                 case 1:
918                                         counters.reset("enumii");
919                                 case 2:
920                                         counters.reset("enumiii");
921                                 case 3:
922                                         counters.reset("enumiv");
923                                 }
924                         }
925                         break;
926                 }
927
928                 if (prev_pit == firstpit)
929                         break;
930
931                 --prev_pit;
932         }
933 }
934
935 } // anon namespace
936
937
938 // set the counter of a paragraph. This includes the labels
939 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
940 {
941         BufferParams const & bufparams = buf.params();
942         LyXTextClass const & textclass = bufparams.getLyXTextClass();
943         LyXLayout_ptr const & layout = pit->layout();
944         ParagraphList::iterator first_pit = ownerParagraphs().begin();
945         Counters & counters = textclass.counters();
946
947         // Always reset
948         pit->itemdepth = 0;
949
950         if (pit == first_pit) {
951                 pit->params().appendix(pit->params().startOfAppendix());
952         } else {
953                 pit->params().appendix(boost::prior(pit)->params().appendix());
954                 if (!pit->params().appendix() &&
955                     pit->params().startOfAppendix()) {
956                         pit->params().appendix(true);
957                         textclass.counters().reset();
958                 }
959
960                 // Maybe we have to increment the item depth.
961                 incrementItemDepth(pit, first_pit);
962         }
963
964         // erase what was there before
965         pit->params().labelString(string());
966
967         if (layout->margintype == MARGIN_MANUAL) {
968                 if (pit->params().labelWidthString().empty())
969                         pit->setLabelWidthString(layout->labelstring());
970         } else {
971                 pit->setLabelWidthString(string());
972         }
973
974         // is it a layout that has an automatic label?
975         if (layout->labeltype == LABEL_COUNTER) {
976                 BufferParams const & bufparams = buf.params();
977                 LyXTextClass const & textclass = bufparams.getLyXTextClass();
978                 counters.step(layout->counter);
979                 string label = expandLabel(textclass, layout, pit->params().appendix());
980                 pit->params().labelString(label);
981         } else if (layout->labeltype == LABEL_ITEMIZE) {
982                 // At some point of time we should do something more
983                 // clever here, like:
984                 //   pit->params().labelString(
985                 //    bufparams.user_defined_bullet(pit->itemdepth).getText());
986                 // for now, use a simple hardcoded label
987                 string itemlabel;
988                 switch (pit->itemdepth) {
989                 case 0:
990                         itemlabel = "*";
991                         break;
992                 case 1:
993                         itemlabel = "-";
994                         break;
995                 case 2:
996                         itemlabel = "@";
997                         break;
998                 case 3:
999                         itemlabel = "·";
1000                         break;
1001                 }
1002
1003                 pit->params().labelString(itemlabel);
1004         } else if (layout->labeltype == LABEL_ENUMERATE) {
1005                 // Maybe we have to reset the enumeration counter.
1006                 resetEnumCounterIfNeeded(pit, first_pit, counters);
1007
1008                 // FIXME
1009                 // Yes I know this is a really, really! bad solution
1010                 // (Lgb)
1011                 string enumcounter = "enum";
1012
1013                 switch (pit->itemdepth) {
1014                 case 2:
1015                         enumcounter += 'i';
1016                 case 1:
1017                         enumcounter += 'i';
1018                 case 0:
1019                         enumcounter += 'i';
1020                         break;
1021                 case 3:
1022                         enumcounter += "iv";
1023                         break;
1024                 default:
1025                         // not a valid enumdepth...
1026                         break;
1027                 }
1028
1029                 counters.step(enumcounter);
1030
1031                 pit->params().labelString(counters.enumLabel(enumcounter));
1032         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
1033                 counters.step("bibitem");
1034                 int number = counters.value("bibitem");
1035                 if (pit->bibitem()) {
1036                         pit->bibitem()->setCounter(number);
1037                         pit->params().labelString(layout->labelstring());
1038                 }
1039                 // In biblio should't be following counters but...
1040         } else {
1041                 string s = buf.B_(layout->labelstring());
1042
1043                 // the caption hack:
1044                 if (layout->labeltype == LABEL_SENSITIVE) {
1045                         ParagraphList::iterator end = ownerParagraphs().end();
1046                         ParagraphList::iterator tmppit = pit;
1047                         InsetOld * in = 0;
1048                         bool isOK = false;
1049                         while (tmppit != end && tmppit->inInset()
1050                                // the single '=' is intended below
1051                                && (in = tmppit->inInset()->owner()))
1052                         {
1053                                 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
1054                                     in->lyxCode() == InsetOld::WRAP_CODE) {
1055                                         isOK = true;
1056                                         break;
1057                                 } else {
1058                                         Paragraph const * owner = &ownerPar(buf, in);
1059                                         tmppit = first_pit;
1060                                         for ( ; tmppit != end; ++tmppit)
1061                                                 if (&*tmppit == owner)
1062                                                         break;
1063                                 }
1064                         }
1065
1066                         if (isOK) {
1067                                 string type;
1068
1069                                 if (in->lyxCode() == InsetOld::FLOAT_CODE)
1070                                         type = static_cast<InsetFloat*>(in)->params().type;
1071                                 else if (in->lyxCode() == InsetOld::WRAP_CODE)
1072                                         type = static_cast<InsetWrap*>(in)->params().type;
1073                                 else
1074                                         BOOST_ASSERT(false);
1075
1076                                 Floating const & fl = textclass.floats().getType(type);
1077
1078                                 counters.step(fl.type());
1079
1080                                 // Doesn't work... yet.
1081                                 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
1082                         } else {
1083                                 // par->SetLayout(0);
1084                                 // s = layout->labelstring;
1085                                 s = _("Senseless: ");
1086                         }
1087                 }
1088                 pit->params().labelString(s);
1089
1090         }
1091 }
1092
1093
1094 // Updates all counters. Paragraphs with changed label string will be rebroken
1095 void LyXText::updateCounters()
1096 {
1097         // start over
1098         bv()->buffer()->params().getLyXTextClass().counters().reset();
1099
1100         ParagraphList::iterator beg = ownerParagraphs().begin();
1101         ParagraphList::iterator end = ownerParagraphs().end();
1102         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
1103                 string const oldLabel = pit->params().labelString();
1104
1105                 size_t maxdepth = 0;
1106                 if (pit != beg)
1107                         maxdepth = boost::prior(pit)->getMaxDepthAfter();
1108
1109                 if (pit->params().depth() > maxdepth)
1110                         pit->params().depth(maxdepth);
1111
1112                 // setCounter can potentially change the labelString.
1113                 setCounter(*bv()->buffer(), pit);
1114
1115                 string const & newLabel = pit->params().labelString();
1116
1117                 if (oldLabel != newLabel)
1118                         redoParagraph(pit);
1119         }
1120 }
1121
1122
1123 void LyXText::insertInset(InsetOld * inset)
1124 {
1125         if (!cursorPar()->insetAllowed(inset->lyxCode()))
1126                 return;
1127         recUndo(cursor.par());
1128         freezeUndo();
1129         cursorPar()->insertInset(cursor.pos(), inset);
1130         // Just to rebreak and refresh correctly.
1131         // The character will not be inserted a second time
1132         insertChar(Paragraph::META_INSET);
1133         // If we enter a highly editable inset the cursor should be before
1134         // the inset. After an Undo LyX tries to call inset->edit(...)
1135         // and fails if the cursor is behind the inset and getInset
1136         // does not return the inset!
1137         if (isHighlyEditableInset(inset))
1138                 cursorLeft(true);
1139         unFreezeUndo();
1140 }
1141
1142
1143 void LyXText::cutSelection(bool doclear, bool realcut)
1144 {
1145         // Stuff what we got on the clipboard. Even if there is no selection.
1146
1147         // There is a problem with having the stuffing here in that the
1148         // larger the selection the slower LyX will get. This can be
1149         // solved by running the line below only when the selection has
1150         // finished. The solution used currently just works, to make it
1151         // faster we need to be more clever and probably also have more
1152         // calls to stuffClipboard. (Lgb)
1153         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1154
1155         // This doesn't make sense, if there is no selection
1156         if (!selection.set())
1157                 return;
1158
1159         // OK, we have a selection. This is always between selection.start
1160         // and selection.end
1161
1162         // make sure that the depth behind the selection are restored, too
1163         ParagraphList::iterator endpit = boost::next(getPar(selection.end.par()));
1164         ParagraphList::iterator undoendpit = endpit;
1165         ParagraphList::iterator pars_end = ownerParagraphs().end();
1166
1167         if (endpit != pars_end && endpit->getDepth()) {
1168                 while (endpit != pars_end && endpit->getDepth()) {
1169                         ++endpit;
1170                         undoendpit = endpit;
1171                 }
1172         } else if (endpit != pars_end) {
1173                 // because of parindents etc.
1174                 ++endpit;
1175         }
1176
1177         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
1178
1179         endpit = getPar(selection.end.par());
1180         int endpos = selection.end.pos();
1181
1182         BufferParams const & bufparams = bv()->buffer()->params();
1183         boost::tie(endpit, endpos) = realcut ?
1184                 CutAndPaste::cutSelection(bufparams,
1185                                           ownerParagraphs(),
1186                                           getPar(selection.start.par()), endpit,
1187                                           selection.start.pos(), endpos,
1188                                           bufparams.textclass,
1189                                           doclear)
1190                 : CutAndPaste::eraseSelection(bufparams,
1191                                               ownerParagraphs(),
1192                                               getPar(selection.start.par()), endpit,
1193                                               selection.start.pos(), endpos,
1194                                               doclear);
1195         // sometimes necessary
1196         if (doclear)
1197                 getPar(selection.start.par())->stripLeadingSpaces();
1198
1199         redoParagraphs(getPar(selection.start.par()), boost::next(endpit));
1200         // cutSelection can invalidate the cursor so we need to set
1201         // it anew. (Lgb)
1202         // we prefer the end for when tracking changes
1203         cursor.pos(endpos);
1204         cursor.par(parOffset(endpit));
1205
1206         // need a valid cursor. (Lgb)
1207         clearSelection();
1208
1209         setCursor(cursorPar(), cursor.pos());
1210         selection.cursor = cursor;
1211         updateCounters();
1212 }
1213
1214
1215 void LyXText::copySelection()
1216 {
1217         // stuff the selection onto the X clipboard, from an explicit copy request
1218         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1219
1220         // this doesnt make sense, if there is no selection
1221         if (!selection.set())
1222                 return;
1223
1224         // ok we have a selection. This is always between selection.start
1225         // and sel_end cursor
1226
1227         // copy behind a space if there is one
1228         while (getPar(selection.start)->size() > selection.start.pos()
1229                && getPar(selection.start)->isLineSeparator(selection.start.pos())
1230                && (selection.start.par() != selection.end.par()
1231                    || selection.start.pos() < selection.end.pos()))
1232                 selection.start.pos(selection.start.pos() + 1);
1233
1234         CutAndPaste::copySelection(getPar(selection.start.par()),
1235                                    getPar(selection.end.par()),
1236                                    selection.start.pos(), selection.end.pos(),
1237                                    bv()->buffer()->params().textclass);
1238 }
1239
1240
1241 void LyXText::pasteSelection(size_t sel_index)
1242 {
1243         // this does not make sense, if there is nothing to paste
1244         if (!CutAndPaste::checkPastePossible())
1245                 return;
1246
1247         recUndo(cursor.par());
1248
1249         ParagraphList::iterator endpit;
1250         PitPosPair ppp;
1251
1252         ErrorList el;
1253
1254         boost::tie(ppp, endpit) =
1255                 CutAndPaste::pasteSelection(*bv()->buffer(),
1256                                             ownerParagraphs(),
1257                                             cursorPar(), cursor.pos(),
1258                                             bv()->buffer()->params().textclass,
1259                                             sel_index, el);
1260         bufferErrors(*bv()->buffer(), el);
1261         bv()->showErrorList(_("Paste"));
1262
1263         redoParagraphs(cursorPar(), endpit);
1264
1265         setCursor(cursor.par(), cursor.pos());
1266         clearSelection();
1267
1268         selection.cursor = cursor;
1269         setCursor(ppp.first, ppp.second);
1270         setSelection();
1271         updateCounters();
1272 }
1273
1274
1275 void LyXText::setSelectionRange(lyx::pos_type length)
1276 {
1277         if (!length)
1278                 return;
1279
1280         selection.cursor = cursor;
1281         while (length--)
1282                 cursorRight(bv());
1283         setSelection();
1284 }
1285
1286
1287 // simple replacing. The font of the first selected character is used
1288 void LyXText::replaceSelectionWithString(string const & str)
1289 {
1290         recUndo(cursor.par());
1291         freezeUndo();
1292
1293         if (!selection.set()) { // create a dummy selection
1294                 selection.end = cursor;
1295                 selection.start = cursor;
1296         }
1297
1298         // Get font setting before we cut
1299         pos_type pos = selection.end.pos();
1300         LyXFont const font = getPar(selection.start)
1301                 ->getFontSettings(bv()->buffer()->params(),
1302                                   selection.start.pos());
1303
1304         // Insert the new string
1305         string::const_iterator cit = str.begin();
1306         string::const_iterator end = str.end();
1307         for (; cit != end; ++cit) {
1308                 getPar(selection.end)->insertChar(pos, (*cit), font);
1309                 ++pos;
1310         }
1311
1312         // Cut the selection
1313         cutSelection(true, false);
1314
1315         unFreezeUndo();
1316 }
1317
1318
1319 // needed to insert the selection
1320 void LyXText::insertStringAsLines(string const & str)
1321 {
1322         ParagraphList::iterator pit = cursorPar();
1323         pos_type pos = cursor.pos();
1324         ParagraphList::iterator endpit = boost::next(cursorPar());
1325
1326         recUndo(cursor.par());
1327
1328         // only to be sure, should not be neccessary
1329         clearSelection();
1330
1331         bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1332
1333         redoParagraphs(cursorPar(), endpit);
1334         setCursor(cursorPar(), cursor.pos());
1335         selection.cursor = cursor;
1336         setCursor(pit, pos);
1337         setSelection();
1338 }
1339
1340
1341 // turns double-CR to single CR, others where converted into one
1342 // blank. Then InsertStringAsLines is called
1343 void LyXText::insertStringAsParagraphs(string const & str)
1344 {
1345         string linestr(str);
1346         bool newline_inserted = false;
1347         string::size_type const siz = linestr.length();
1348
1349         for (string::size_type i = 0; i < siz; ++i) {
1350                 if (linestr[i] == '\n') {
1351                         if (newline_inserted) {
1352                                 // we know that \r will be ignored by
1353                                 // InsertStringA. Of course, it is a dirty
1354                                 // trick, but it works...
1355                                 linestr[i - 1] = '\r';
1356                                 linestr[i] = '\n';
1357                         } else {
1358                                 linestr[i] = ' ';
1359                                 newline_inserted = true;
1360                         }
1361                 } else if (IsPrintable(linestr[i])) {
1362                         newline_inserted = false;
1363                 }
1364         }
1365         insertStringAsLines(linestr);
1366 }
1367
1368
1369 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1370 {
1371         setCursor(parOffset(pit), pos);
1372 }
1373
1374
1375 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont, bool boundary)
1376 {
1377         LyXCursor old_cursor = cursor;
1378         setCursorIntern(par, pos, setfont, boundary);
1379         return deleteEmptyParagraphMechanism(old_cursor);
1380 }
1381
1382
1383 void LyXText::redoCursor()
1384 {
1385 #warning maybe the same for selections?
1386         setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1387 }
1388
1389
1390 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1391         pos_type pos, bool boundary)
1392 {
1393         BOOST_ASSERT(par != int(ownerParagraphs().size()));
1394
1395         cur.par(par);
1396         cur.pos(pos);
1397         cur.boundary(boundary);
1398
1399         // no rows, no fun...
1400         if (ownerParagraphs().begin()->rows.empty())
1401                 return;
1402
1403         // get the cursor y position in text
1404
1405         ParagraphList::iterator pit = getPar(par);
1406         RowList::iterator row = getRow(pit, pos);
1407         int y = row->y();
1408
1409         // y is now the beginning of the cursor row
1410         y += row->baseline();
1411         // y is now the cursor baseline
1412         cur.y(y);
1413
1414         pos_type last = lastPos(*pit, row);
1415
1416         // None of these should happen, but we're scaredy-cats
1417         if (pos > pit->size()) {
1418                 lyxerr << "dont like 1, pos: " << pos << " size: " << pit->size() << endl;
1419                 pos = 0;
1420                 cur.pos(0);
1421         } else if (pos > last + 1) {
1422                 lyxerr << "dont like 2 please report" << endl;
1423                 // This shouldn't happen.
1424                 pos = last + 1;
1425                 cur.pos(pos);
1426         } else if (pos < row->pos()) {
1427                 lyxerr << "dont like 3 please report" << endl;
1428                 pos = row->pos();
1429                 cur.pos(pos);
1430         }
1431
1432         // now get the cursors x position
1433         float x = getCursorX(pit, row, pos, last, boundary);
1434         cur.x(int(x));
1435         cur.x_fix(cur.x());
1436 }
1437
1438
1439 float LyXText::getCursorX(ParagraphList::iterator pit, RowList::iterator rit,
1440                           pos_type pos, pos_type last, bool boundary) const
1441 {
1442         pos_type cursor_vpos    = 0;
1443         double x                = rit->x();
1444         double fill_separator   = rit->fill_separator();
1445         double fill_hfill       = rit->fill_hfill();
1446         double fill_label_hfill = rit->fill_label_hfill();
1447         pos_type const rit_pos  = rit->pos();
1448
1449         if (last < rit_pos)
1450                 cursor_vpos = rit_pos;
1451         else if (pos > last && !boundary)
1452                 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params()))
1453                         ? rit_pos : last + 1;
1454         else if (pos > rit_pos && (pos > last || boundary))
1455                 // Place cursor after char at (logical) position pos - 1
1456                 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1457                         ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1458         else
1459                 // Place cursor before char at (logical) position pos
1460                 cursor_vpos = (bidi_level(pos) % 2 == 0)
1461                         ? log2vis(pos) : log2vis(pos) + 1;
1462
1463         pos_type body_pos = pit->beginningOfBody();
1464         if (body_pos > 0 &&
1465             (body_pos - 1 > last || !pit->isLineSeparator(body_pos - 1)))
1466                 body_pos = 0;
1467
1468         for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1469                 pos_type pos = vis2log(vpos);
1470                 if (body_pos > 0 && pos == body_pos - 1) {
1471                         x += fill_label_hfill +
1472                                 font_metrics::width(
1473                                         pit->layout()->labelsep, getLabelFont(pit));
1474                         if (pit->isLineSeparator(body_pos - 1))
1475                                 x -= singleWidth(pit, body_pos - 1);
1476                 }
1477
1478                 if (hfillExpansion(*pit, rit, pos)) {
1479                         x += singleWidth(pit, pos);
1480                         if (pos >= body_pos)
1481                                 x += fill_hfill;
1482                         else
1483                                 x += fill_label_hfill;
1484                 } else if (pit->isSeparator(pos)) {
1485                         x += singleWidth(pit, pos);
1486                         if (pos >= body_pos)
1487                                 x += fill_separator;
1488                 } else
1489                         x += singleWidth(pit, pos);
1490         }
1491         return x;
1492 }
1493
1494
1495 void LyXText::setCursorIntern(paroffset_type par,
1496                               pos_type pos, bool setfont, bool boundary)
1497 {
1498         setCursor(cursor, par, pos, boundary);
1499         if (setfont)
1500                 setCurrentFont();
1501 }
1502
1503
1504 void LyXText::setCurrentFont()
1505 {
1506         pos_type pos = cursor.pos();
1507         ParagraphList::iterator pit = cursorPar();
1508
1509         if (cursor.boundary() && pos > 0)
1510                 --pos;
1511
1512         if (pos > 0) {
1513                 if (pos == pit->size())
1514                         --pos;
1515                 else // potentional bug... BUG (Lgb)
1516                         if (pit->isSeparator(pos)) {
1517                                 if (pos > cursorRow()->pos() &&
1518                                     bidi_level(pos) % 2 ==
1519                                     bidi_level(pos - 1) % 2)
1520                                         --pos;
1521                                 else if (pos + 1 < pit->size())
1522                                         ++pos;
1523                         }
1524         }
1525
1526         BufferParams const & bufparams = bv()->buffer()->params();
1527         current_font = pit->getFontSettings(bufparams, pos);
1528         real_current_font = getFont(pit, pos);
1529
1530         if (cursor.pos() == pit->size() &&
1531             isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1532             !cursor.boundary()) {
1533                 Language const * lang =
1534                         pit->getParLanguage(bufparams);
1535                 current_font.setLanguage(lang);
1536                 current_font.setNumber(LyXFont::OFF);
1537                 real_current_font.setLanguage(lang);
1538                 real_current_font.setNumber(LyXFont::OFF);
1539         }
1540 }
1541
1542
1543 // returns the column near the specified x-coordinate of the row
1544 // x is set to the real beginning of this column
1545 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1546         RowList::iterator rit, int & x, bool & boundary) const
1547 {
1548         double tmpx             = rit->x();
1549         double fill_separator   = rit->fill_separator();
1550         double fill_hfill       = rit->fill_hfill();
1551         double fill_label_hfill = rit->fill_label_hfill();
1552
1553         pos_type vc = rit->pos();
1554         pos_type last = lastPos(*pit, rit);
1555         pos_type c = 0;
1556         LyXLayout_ptr const & layout = pit->layout();
1557
1558         bool left_side = false;
1559
1560         pos_type body_pos = pit->beginningOfBody();
1561         double last_tmpx = tmpx;
1562
1563         if (body_pos > 0 &&
1564             (body_pos - 1 > last ||
1565              !pit->isLineSeparator(body_pos - 1)))
1566                 body_pos = 0;
1567
1568         // check for empty row
1569         if (!pit->size()) {
1570                 x = int(tmpx);
1571                 return 0;
1572         }
1573
1574         while (vc <= last && tmpx <= x) {
1575                 c = vis2log(vc);
1576                 last_tmpx = tmpx;
1577                 if (body_pos > 0 && c == body_pos - 1) {
1578                         tmpx += fill_label_hfill +
1579                                 font_metrics::width(layout->labelsep, getLabelFont(pit));
1580                         if (pit->isLineSeparator(body_pos - 1))
1581                                 tmpx -= singleWidth(pit, body_pos - 1);
1582                 }
1583
1584                 if (hfillExpansion(*pit, rit, c)) {
1585                         tmpx += singleWidth(pit, c);
1586                         if (c >= body_pos)
1587                                 tmpx += fill_hfill;
1588                         else
1589                                 tmpx += fill_label_hfill;
1590                 } else if (pit->isSeparator(c)) {
1591                         tmpx += singleWidth(pit, c);
1592                         if (c >= body_pos)
1593                                 tmpx += fill_separator;
1594                 } else {
1595                         tmpx += singleWidth(pit, c);
1596                 }
1597                 ++vc;
1598         }
1599
1600         if ((tmpx + last_tmpx) / 2 > x) {
1601                 tmpx = last_tmpx;
1602                 left_side = true;
1603         }
1604
1605         if (vc > last + 1)  // This shouldn't happen.
1606                 vc = last + 1;
1607
1608         boundary = false;
1609         // This (rtl_support test) is not needed, but gives
1610         // some speedup if rtl_support == false
1611         bool const lastrow = lyxrc.rtl_support
1612                         && boost::next(rit) == pit->rows.end();
1613
1614         // If lastrow is false, we don't need to compute
1615         // the value of rtl.
1616         bool const rtl = (lastrow)
1617                 ? pit->isRightToLeftPar(bv()->buffer()->params())
1618                 : false;
1619         if (lastrow &&
1620                  ((rtl  &&  left_side && vc == rit->pos() && x < tmpx - 5) ||
1621                   (!rtl && !left_side && vc == last + 1   && x > tmpx + 5)))
1622                 c = last + 1;
1623         else if (vc == rit->pos()) {
1624                 c = vis2log(vc);
1625                 if (bidi_level(c) % 2 == 1)
1626                         ++c;
1627         } else {
1628                 c = vis2log(vc - 1);
1629                 bool const rtl = (bidi_level(c) % 2 == 1);
1630                 if (left_side == rtl) {
1631                         ++c;
1632                         boundary = isBoundary(*bv()->buffer(), *pit, c);
1633                 }
1634         }
1635
1636         if (rit->pos() <= last && c > last && pit->isNewline(last)) {
1637                 if (bidi_level(last) % 2 == 0)
1638                         tmpx -= singleWidth(pit, last);
1639                 else
1640                         tmpx += singleWidth(pit, last);
1641                 c = last;
1642         }
1643
1644         c -= rit->pos();
1645         x = int(tmpx);
1646         return c;
1647 }
1648
1649
1650 void LyXText::setCursorFromCoordinates(int x, int y)
1651 {
1652         LyXCursor old_cursor = cursor;
1653         setCursorFromCoordinates(cursor, x, y);
1654         setCurrentFont();
1655         deleteEmptyParagraphMechanism(old_cursor);
1656 }
1657
1658
1659 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1660 {
1661         // Get the row first.
1662         ParagraphList::iterator pit;
1663         RowList::iterator rit = getRowNearY(y, pit);
1664         y = rit->y();
1665
1666         bool bound = false;
1667         pos_type const column = getColumnNearX(pit, rit, x, bound);
1668         cur.par(parOffset(pit));
1669         cur.pos(rit->pos() + column);
1670         cur.x(x);
1671         cur.y(y + rit->baseline());
1672
1673         cur.boundary(bound);
1674 }
1675
1676
1677 void LyXText::cursorLeft(bool internal)
1678 {
1679         if (cursor.pos() > 0) {
1680                 bool boundary = cursor.boundary();
1681                 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1682                 if (!internal && !boundary &&
1683                     isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1684                         setCursor(cursor.par(), cursor.pos() + 1, true, true);
1685         } else if (cursor.par() != 0) {
1686                 // steps into the paragraph above
1687                 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1688         }
1689 }
1690
1691
1692 void LyXText::cursorRight(bool internal)
1693 {
1694         bool const at_end = (cursor.pos() == cursorPar()->size());
1695         bool const at_newline = !at_end &&
1696                 cursorPar()->isNewline(cursor.pos());
1697
1698         if (!internal && cursor.boundary() && !at_newline)
1699                 setCursor(cursor.par(), cursor.pos(), true, false);
1700         else if (!at_end) {
1701                 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1702                 if (!internal &&
1703                     isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos()))
1704                         setCursor(cursor.par(), cursor.pos(), true, true);
1705         } else if (cursor.par() + 1 != int(ownerParagraphs().size()))
1706                 setCursor(cursor.par() + 1, 0);
1707 }
1708
1709
1710 void LyXText::cursorUp(bool selecting)
1711 {
1712 #if 1
1713         int x = cursor.x_fix();
1714         int y = cursor.y() - cursorRow()->baseline() - 1;
1715         setCursorFromCoordinates(x, y);
1716         if (!selecting) {
1717                 int topy = bv_owner->top_y();
1718                 int y1 = cursor.y() - topy;
1719                 int y2 = y1;
1720                 y -= topy;
1721                 InsetOld * inset_hit = checkInsetHit(x, y1);
1722                 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1723                         inset_hit->localDispatch(
1724                                 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1725                 }
1726         }
1727 #else
1728         lyxerr << "cursorUp: y " << cursor.y() << " bl: " <<
1729                 cursorRow()->baseline() << endl;
1730         setCursorFromCoordinates(cursor.x_fix(),
1731                 cursor.y() - cursorRow()->baseline() - 1);
1732 #endif
1733 }
1734
1735
1736 void LyXText::cursorDown(bool selecting)
1737 {
1738 #if 1
1739         int x = cursor.x_fix();
1740         int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1741         setCursorFromCoordinates(x, y);
1742         if (!selecting && cursorRow() == cursorIRow()) {
1743                 int topy = bv_owner->top_y();
1744                 int y1 = cursor.y() - topy;
1745                 int y2 = y1;
1746                 y -= topy;
1747                 InsetOld * inset_hit = checkInsetHit(x, y1);
1748                 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1749                         FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1750                         inset_hit->localDispatch(cmd);
1751                 }
1752         }
1753 #else
1754         setCursorFromCoordinates(cursor.x_fix(),
1755                  cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1);
1756 #endif
1757 }
1758
1759
1760 void LyXText::cursorUpParagraph()
1761 {
1762         if (cursor.pos() > 0)
1763                 setCursor(cursorPar(), 0);
1764         else if (cursorPar() != ownerParagraphs().begin())
1765                 setCursor(boost::prior(cursorPar()), 0);
1766 }
1767
1768
1769 void LyXText::cursorDownParagraph()
1770 {
1771         ParagraphList::iterator par = cursorPar();
1772         ParagraphList::iterator next_par = boost::next(par);
1773
1774         if (next_par != ownerParagraphs().end())
1775                 setCursor(next_par, 0);
1776         else
1777                 setCursor(par, par->size());
1778 }
1779
1780
1781 // fix the cursor `cur' after a characters has been deleted at `where'
1782 // position. Called by deleteEmptyParagraphMechanism
1783 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1784 {
1785         // if cursor is not in the paragraph where the delete occured,
1786         // do nothing
1787         if (cur.par() != where.par())
1788                 return;
1789
1790         // if cursor position is after the place where the delete occured,
1791         // update it
1792         if (cur.pos() > where.pos())
1793                 cur.pos(cur.pos()-1);
1794
1795         // check also if we don't want to set the cursor on a spot behind the
1796         // pagragraph because we erased the last character.
1797         if (cur.pos() > getPar(cur)->size())
1798                 cur.pos(getPar(cur)->size());
1799
1800         // recompute row et al. for this cursor
1801         setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1802 }
1803
1804
1805 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1806 {
1807         // Would be wrong to delete anything if we have a selection.
1808         if (selection.set())
1809                 return false;
1810
1811         // Don't do anything if the cursor is invalid
1812         if (old_cursor.par() == -1)
1813                 return false;
1814
1815         // We allow all kinds of "mumbo-jumbo" when freespacing.
1816         ParagraphList::iterator const old_pit = getPar(old_cursor);
1817         if (old_pit->isFreeSpacing())
1818                 return false;
1819
1820         /* Ok I'll put some comments here about what is missing.
1821            I have fixed BackSpace (and thus Delete) to not delete
1822            double-spaces automagically. I have also changed Cut,
1823            Copy and Paste to hopefully do some sensible things.
1824            There are still some small problems that can lead to
1825            double spaces stored in the document file or space at
1826            the beginning of paragraphs. This happens if you have
1827            the cursor between to spaces and then save. Or if you
1828            cut and paste and the selection have a space at the
1829            beginning and then save right after the paste. I am
1830            sure none of these are very hard to fix, but I will
1831            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1832            that I can get some feedback. (Lgb)
1833         */
1834
1835         // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1836         // delete the LineSeparator.
1837         // MISSING
1838
1839         // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1840         // delete the LineSeparator.
1841         // MISSING
1842
1843         // If the pos around the old_cursor were spaces, delete one of them.
1844         if (old_cursor.par() != cursor.par()
1845             || old_cursor.pos() != cursor.pos()) {
1846
1847                 // Only if the cursor has really moved
1848                 if (old_cursor.pos() > 0
1849                     && old_cursor.pos() < old_pit->size()
1850                     && old_pit->isLineSeparator(old_cursor.pos())
1851                     && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1852                         bool erased = old_pit->erase(old_cursor.pos() - 1);
1853                         redoParagraph(old_pit);
1854
1855                         if (!erased)
1856                                 return false;
1857 #ifdef WITH_WARNINGS
1858 #warning This will not work anymore when we have multiple views of the same buffer
1859 // In this case, we will have to correct also the cursors held by
1860 // other bufferviews. It will probably be easier to do that in a more
1861 // automated way in LyXCursor code. (JMarc 26/09/2001)
1862 #endif
1863                         // correct all cursors held by the LyXText
1864                         fixCursorAfterDelete(cursor, old_cursor);
1865                         fixCursorAfterDelete(selection.cursor, old_cursor);
1866                         fixCursorAfterDelete(selection.start, old_cursor);
1867                         fixCursorAfterDelete(selection.end, old_cursor);
1868                         return false;
1869                 }
1870         }
1871
1872         // don't delete anything if this is the ONLY paragraph!
1873         if (ownerParagraphs().size() == 1)
1874                 return false;
1875
1876         // Do not delete empty paragraphs with keepempty set.
1877         if (old_pit->allowEmpty())
1878                 return false;
1879
1880         // only do our magic if we changed paragraph
1881         if (old_cursor.par() == cursor.par())
1882                 return false;
1883
1884         // record if we have deleted a paragraph
1885         // we can't possibly have deleted a paragraph before this point
1886         bool deleted = false;
1887
1888         if (old_pit->empty() ||
1889             (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1890                 // ok, we will delete something
1891                 LyXCursor tmpcursor;
1892
1893                 deleted = true;
1894
1895                 bool selection_position_was_oldcursor_position =
1896                         selection.cursor.par() == old_cursor.par()
1897                         && selection.cursor.pos() == old_cursor.pos();
1898
1899                 tmpcursor = cursor;
1900                 cursor = old_cursor; // that undo can restore the right cursor position
1901
1902                 ParagraphList::iterator endpit = boost::next(old_pit);
1903                 while (endpit != ownerParagraphs().end() && endpit->getDepth())
1904                         ++endpit;
1905
1906                 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1907                 cursor = tmpcursor;
1908
1909                 // delete old par
1910                 ownerParagraphs().erase(old_pit);
1911                 redoParagraph();
1912
1913                 // correct cursor y
1914                 setCursorIntern(cursor.par(), cursor.pos());
1915
1916                 if (selection_position_was_oldcursor_position) {
1917                         // correct selection
1918                         selection.cursor = cursor;
1919                 }
1920         }
1921         if (!deleted) {
1922                 if (old_pit->stripLeadingSpaces()) {
1923                         redoParagraph(old_pit);
1924                         // correct cursor y
1925                         setCursorIntern(cursor.par(), cursor.pos());
1926                         selection.cursor = cursor;
1927                 }
1928         }
1929         return deleted;
1930 }
1931
1932
1933 ParagraphList & LyXText::ownerParagraphs() const
1934 {
1935         return *paragraphs_;
1936 }
1937
1938
1939 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1940 {
1941         recordUndo(Undo::ATOMIC, this, first, last);
1942 }
1943
1944
1945 void LyXText::recUndo(lyx::paroffset_type par) const
1946 {
1947         recordUndo(Undo::ATOMIC, this, par, par);
1948 }
1949
1950
1951 bool LyXText::isInInset() const
1952 {
1953         // Sub-level has non-null bv owner and non-null inset owner.
1954         return inset_owner != 0;
1955 }
1956
1957
1958 int defaultRowHeight()
1959 {
1960         LyXFont const font(LyXFont::ALL_SANE);
1961         return int(font_metrics::maxAscent(font)
1962                  + font_metrics::maxDescent(font) * 1.5);
1963 }