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