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