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