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