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