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