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