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