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