]> git.lyx.org Git - lyx.git/blob - src/text2.C
more getPar
[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(ParagraphList::iterator pit,
265         RowList::iterator rit)
266 {
267         RowList::iterator end = endRow(pit);
268         for (++rit; rit != end; ) {
269                 RowList::iterator rit2 = boost::next(rit);
270                 removeRow(rit);
271                 rit = rit2;
272         }
273 }
274
275
276 void LyXText::insertParagraph(ParagraphList::iterator pit,
277                               RowList::iterator rit)
278 {
279         // insert a new row, starting at position 0
280         rit = rowlist_.insert(rit, Row(0));
281
282         // and now append the whole paragraph before the new row
283
284         pos_type const last = pit->size();
285         bool done = false;
286
287         do {
288                 pos_type z = rowBreakPoint(pit, *rit);
289
290                 RowList::iterator tmprow = rit;
291
292                 if (z < last) {
293                         ++z;
294                         rit = rowlist_.insert(boost::next(rit), Row(z));
295                 } else {
296                         done = true;
297                 }
298
299                 // Set the dimensions of the row
300                 // fixed fill setting now by calling inset->update() in
301                 // singleWidth when needed!
302                 tmprow->fill(fill(pit, tmprow, workWidth()));
303                 setHeightOfRow(pit, tmprow);
304
305         } while (!done);
306 }
307
308
309 InsetOld * LyXText::getInset() const
310 {
311         ParagraphList::iterator pit = cursor.par();
312         pos_type const pos = cursor.pos();
313
314         if (pos < pit->size() && pit->isInset(pos)) {
315                 return pit->getInset(pos);
316         }
317         return 0;
318 }
319
320
321 void LyXText::toggleInset()
322 {
323         InsetOld * inset = getInset();
324         // is there an editable inset at cursor position?
325         if (!isEditableInset(inset)) {
326                 // No, try to see if we are inside a collapsable inset
327                 if (inset_owner && inset_owner->owner()
328                     && inset_owner->owner()->isOpen()) {
329                         bv()->unlockInset(inset_owner->owner());
330                         inset_owner->owner()->close(bv());
331                         bv()->getLyXText()->cursorRight(bv());
332                 }
333                 return;
334         }
335         //bv()->owner()->message(inset->editMessage());
336
337         // do we want to keep this?? (JMarc)
338         if (!isHighlyEditableInset(inset))
339                 recordUndo(bv(), Undo::ATOMIC);
340
341         if (inset->isOpen())
342                 inset->close(bv());
343         else
344                 inset->open(bv());
345
346         bv()->updateInset();
347 }
348
349
350 /* used in setlayout */
351 // Asger is not sure we want to do this...
352 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
353                                             Paragraph & par)
354 {
355         LyXLayout_ptr const & layout = par.layout();
356         pos_type const psize = par.size();
357
358         LyXFont layoutfont;
359         for (pos_type pos = 0; pos < psize; ++pos) {
360                 if (pos < par.beginningOfBody())
361                         layoutfont = layout->labelfont;
362                 else
363                         layoutfont = layout->font;
364
365                 LyXFont tmpfont = par.getFontSettings(params, pos);
366                 tmpfont.reduce(layoutfont);
367                 par.setFont(pos, tmpfont);
368         }
369 }
370
371
372 ParagraphList::iterator
373 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
374                    LyXCursor & send_cur,
375                    string const & layout)
376 {
377         ParagraphList::iterator endpit = boost::next(send_cur.par());
378         ParagraphList::iterator undoendpit = endpit;
379         ParagraphList::iterator pars_end = ownerParagraphs().end();
380
381         if (endpit != pars_end && endpit->getDepth()) {
382                 while (endpit != pars_end && endpit->getDepth()) {
383                         ++endpit;
384                         undoendpit = endpit;
385                 }
386         } else if (endpit != pars_end) {
387                 // because of parindents etc.
388                 ++endpit;
389         }
390
391         recordUndo(bv(), Undo::ATOMIC, sstart_cur.par(), boost::prior(undoendpit));
392
393         // ok we have a selection. This is always between sstart_cur
394         // and sel_end cursor
395         cur = sstart_cur;
396         ParagraphList::iterator pit = sstart_cur.par();
397         ParagraphList::iterator epit = boost::next(send_cur.par());
398
399         LyXLayout_ptr const & lyxlayout =
400                 bv()->buffer()->params.getLyXTextClass()[layout];
401
402         do {
403                 pit->applyLayout(lyxlayout);
404                 makeFontEntriesLayoutSpecific(bv()->buffer()->params, *pit);
405                 pit->params().spaceTop(lyxlayout->fill_top ?
406                                          VSpace(VSpace::VFILL)
407                                          : VSpace(VSpace::NONE));
408                 pit->params().spaceBottom(lyxlayout->fill_bottom ?
409                                             VSpace(VSpace::VFILL)
410                                             : VSpace(VSpace::NONE));
411                 if (lyxlayout->margintype == MARGIN_MANUAL)
412                         pit->setLabelWidthString(lyxlayout->labelstring());
413                 cur.par(pit);
414                 ++pit;
415         } while (pit != epit);
416
417         return endpit;
418 }
419
420
421 // set layout over selection and make a total rebreak of those paragraphs
422 void LyXText::setLayout(string const & layout)
423 {
424         LyXCursor tmpcursor = cursor;  // store the current cursor
425
426         // if there is no selection just set the layout
427         // of the current paragraph
428         if (!selection.set()) {
429                 selection.start = cursor;  // dummy selection
430                 selection.end = cursor;
431         }
432
433         // special handling of new environment insets
434         BufferParams const & params = bv()->buffer()->params;
435         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
436         if (lyxlayout->is_environment) {
437                 // move everything in a new environment inset
438                 lyxerr << "setting layout " << layout << endl;
439                 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
440                 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
441                 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
442                 InsetOld * inset = new InsetEnvironment(params, layout);
443                 if (bv()->insertInset(inset)) {
444                         //inset->edit(bv());
445                         //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
446                 }
447                 else
448                         delete inset;
449                 return;
450         }
451
452         ParagraphList::iterator endpit = setLayout(cursor, selection.start,
453                                                    selection.end, layout);
454         redoParagraphs(selection.start.par(), endpit);
455
456         // we have to reset the selection, because the
457         // geometry could have changed
458         setCursor(selection.start.par(), selection.start.pos(), false);
459         selection.cursor = cursor;
460         setCursor(selection.end.par(), selection.end.pos(), false);
461         updateCounters();
462         clearSelection();
463         setSelection();
464         setCursor(tmpcursor.par(), tmpcursor.pos(), true);
465 }
466
467
468 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
469 {
470         ParagraphList::iterator pit = cursor.par();
471         ParagraphList::iterator end = cursor.par();
472         ParagraphList::iterator start = pit;
473
474         if (selection.set()) {
475                 pit = selection.start.par();
476                 end = selection.end.par();
477                 start = pit;
478         }
479
480         ParagraphList::iterator pastend = boost::next(end);
481
482         if (!test_only)
483                 recordUndo(bv(), Undo::ATOMIC, start, end);
484
485         bool changed = false;
486
487         int prev_after_depth = 0;
488 #warning parlist ... could be nicer ?
489         if (start != ownerParagraphs().begin()) {
490                 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
491         }
492
493         while (true) {
494                 int const depth = pit->params().depth();
495                 if (type == bv_funcs::INC_DEPTH) {
496                         if (depth < prev_after_depth
497                             && pit->layout()->labeltype != LABEL_BIBLIO) {
498                                 changed = true;
499                                 if (!test_only)
500                                         pit->params().depth(depth + 1);
501                         }
502                 } else if (depth) {
503                         changed = true;
504                         if (!test_only)
505                                 pit->params().depth(depth - 1);
506                 }
507
508                 prev_after_depth = pit->getMaxDepthAfter();
509
510                 if (pit == end) {
511                         break;
512                 }
513
514                 ++pit;
515         }
516
517         if (test_only)
518                 return changed;
519
520         redoParagraphs(start, pastend);
521
522         // We need to actually move the text->cursor. I don't
523         // understand why ...
524         LyXCursor tmpcursor = cursor;
525
526         // we have to reset the visual selection because the
527         // geometry could have changed
528         if (selection.set()) {
529                 setCursor(selection.start.par(), selection.start.pos());
530                 selection.cursor = cursor;
531                 setCursor(selection.end.par(), selection.end.pos());
532         }
533
534         // this handles the counter labels, and also fixes up
535         // depth values for follow-on (child) paragraphs
536         updateCounters();
537
538         setSelection();
539         setCursor(tmpcursor.par(), tmpcursor.pos());
540
541         return changed;
542 }
543
544
545 // set font over selection and make a total rebreak of those paragraphs
546 void LyXText::setFont(LyXFont const & font, bool toggleall)
547 {
548         // if there is no selection just set the current_font
549         if (!selection.set()) {
550                 // Determine basis font
551                 LyXFont layoutfont;
552                 if (cursor.pos() < cursor.par()->beginningOfBody()) {
553                         layoutfont = getLabelFont(cursor.par());
554                 } else {
555                         layoutfont = getLayoutFont(cursor.par());
556                 }
557                 // Update current font
558                 real_current_font.update(font,
559                                          bv()->buffer()->params.language,
560                                          toggleall);
561
562                 // Reduce to implicit settings
563                 current_font = real_current_font;
564                 current_font.reduce(layoutfont);
565                 // And resolve it completely
566                 real_current_font.realize(layoutfont);
567
568                 return;
569         }
570
571         LyXCursor tmpcursor = cursor; // store the current cursor
572
573         // ok we have a selection. This is always between sel_start_cursor
574         // and sel_end cursor
575
576         recordUndo(bv(), Undo::ATOMIC, selection.start.par(), selection.end.par());
577         freezeUndo();
578         cursor = selection.start;
579         while (cursor.par() != selection.end.par() ||
580                cursor.pos() < selection.end.pos())
581         {
582                 if (cursor.pos() < cursor.par()->size()) {
583                         // an open footnote should behave like a closed one
584                         setCharFont(cursor.par(), cursor.pos(),
585                                     font, toggleall);
586                         cursor.pos(cursor.pos() + 1);
587                 } else {
588                         cursor.pos(0);
589                         cursor.par(boost::next(cursor.par()));
590                 }
591         }
592         unFreezeUndo();
593
594         redoParagraph(selection.start.par());
595
596         // we have to reset the selection, because the
597         // geometry could have changed, but we keep
598         // it for user convenience
599         setCursor(selection.start.par(), selection.start.pos());
600         selection.cursor = cursor;
601         setCursor(selection.end.par(), selection.end.pos());
602         setSelection();
603         setCursor(tmpcursor.par(), tmpcursor.pos(), true,
604                   tmpcursor.boundary());
605 }
606
607
608 // rebreaks all paragraphs between the specified pars
609 // This function is needed after SetLayout and SetFont etc.
610 void LyXText::redoParagraphs(ParagraphList::iterator start,
611   ParagraphList::iterator end)
612 {
613         for ( ; start != end; ++start)
614                 redoParagraph(start);
615 }
616
617
618 void LyXText::redoParagraph(ParagraphList::iterator pit)
619 {
620         RowList::iterator first = beginRow(pit);
621         RowList::iterator last = endRow(pit);
622
623         // remove paragraph from rowlist
624         while (first != last) {
625                 RowList::iterator rit2 = first;
626                 ++first;
627                 removeRow(rit2);
628         }
629
630         // reinsert the paragraph
631         insertParagraph(pit, last);
632 }
633
634
635 void LyXText::fullRebreak()
636 {
637         redoParagraphs(ownerParagraphs().begin(), ownerParagraphs().end());
638         redoCursor();
639         selection.cursor = cursor;
640 }
641
642
643 void LyXText::metrics(MetricsInfo & mi, Dimension & dim)
644 {
645         //lyxerr << "LyXText::metrics: width: " << mi.base.textwidth << endl;
646         //Assert(mi.base.textwidth);
647
648         // rebuild row cache
649         rowlist_.clear();
650         width = 0;
651         height = 0;
652
653         anchor_row_ = rows().end();
654         anchor_row_offset_ = 0;
655
656         ParagraphList::iterator pit = ownerParagraphs().begin();
657         ParagraphList::iterator end = ownerParagraphs().end();
658
659         for (; pit != end; ++pit) {
660                 InsetList::iterator ii = pit->insetlist.begin();
661                 InsetList::iterator iend = pit->insetlist.end();
662                 for (; ii != iend; ++ii) {
663                         Dimension dim;
664                         MetricsInfo m = mi;
665 #warning FIXME: pos != 0
666                         m.base.font = getFont(pit, 0);
667                         ii->inset->metrics(m, dim);
668                 }
669
670                 redoParagraph(pit);
671         }
672
673         // final dimension
674         dim.asc = rows().begin()->ascent_of_text();
675         dim.des = height - dim.asc;
676         dim.wid = std::max(mi.base.textwidth, int(width));
677 }
678
679
680 // important for the screen
681
682
683 // the cursor set functions have a special mechanism. When they
684 // realize, that you left an empty paragraph, they will delete it.
685 // They also delete the corresponding row
686
687 // need the selection cursor:
688 void LyXText::setSelection()
689 {
690         TextCursor::setSelection();
691 }
692
693
694
695 void LyXText::clearSelection()
696 {
697         TextCursor::clearSelection();
698
699         // reset this in the bv_owner!
700         if (bv_owner && bv_owner->text)
701                 bv_owner->text->xsel_cache.set(false);
702 }
703
704
705 void LyXText::cursorHome()
706 {
707         setCursor(cursor.par(), cursorRow()->pos());
708 }
709
710
711 void LyXText::cursorEnd()
712 {
713         if (cursor.par()->empty())
714                 return;
715
716         RowList::iterator rit = cursorRow();
717         RowList::iterator next_rit = boost::next(rit);
718         RowList::iterator end = boost::next(rit);
719         ParagraphList::iterator pit = cursor.par();
720         pos_type last_pos = lastPos(*this, pit, rit);
721
722         if (next_rit == end) {
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 old_row = row;
1401         // if we are before the first char of this row and are still in the
1402         // same paragraph and there is a previous row then put the cursor on
1403         // the end of the previous row
1404         cur.iy(y + row->baseline());
1405         if (row != beginRow(pit)
1406             && pos
1407             && pos < pit->size()
1408             && pit->getChar(pos) == Paragraph::META_INSET) {
1409                 InsetOld * ins = pit->getInset(pos);
1410                 if (ins && (ins->needFullRow() || ins->display())) {
1411                         --row;
1412                         y -= row->height();
1413                 }
1414         }
1415
1416         // y is now the beginning of the cursor row
1417         y += row->baseline();
1418         // y is now the cursor baseline
1419         cur.y(y);
1420
1421         pos_type last = lastPrintablePos(*this, pit, old_row);
1422
1423         // None of these should happen, but we're scaredy-cats
1424         if (pos > pit->size()) {
1425                 lyxerr << "dont like 1 please report" << endl;
1426                 pos = 0;
1427                 cur.pos(0);
1428         } else if (pos > last + 1) {
1429                 lyxerr << "dont like 2 please report" << endl;
1430                 // This shouldn't happen.
1431                 pos = last + 1;
1432                 cur.pos(pos);
1433         } else if (pos < row->pos()) {
1434                 lyxerr << "dont like 3 please report" << endl;
1435                 pos = row->pos();
1436                 cur.pos(pos);
1437         }
1438
1439         // now get the cursors x position
1440         float x = getCursorX(pit, row, pos, last, boundary);
1441         cur.x(int(x));
1442         cur.x_fix(cur.x());
1443         if (old_row != row) {
1444                 x = getCursorX(pit, old_row, pos, last, boundary);
1445                 cur.ix(int(x));
1446         } else
1447                 cur.ix(cur.x());
1448 /* We take out this for the time being because 1) the redraw code is not
1449    prepared to this yet and 2) because some good policy has yet to be decided
1450    while editting: for instance how to act on rows being created/deleted
1451    because of DEPM.
1452 */
1453 #if 0
1454         //if the cursor is in a visible row, anchor to it
1455         int topy = top_y();
1456         if (topy < y && y < topy + bv()->workHeight())
1457                 anchor_row(row);
1458 #endif
1459 }
1460
1461
1462 float LyXText::getCursorX(ParagraphList::iterator pit, RowList::iterator rit,
1463                           pos_type pos, pos_type last, bool boundary) const
1464 {
1465         pos_type cursor_vpos = 0;
1466         double x;
1467         double fill_separator;
1468         double fill_hfill;
1469         double fill_label_hfill;
1470         // This call HAS to be here because of the BidiTables!!!
1471         prepareToPrint(pit, rit, x, fill_separator, fill_hfill,
1472                        fill_label_hfill);
1473
1474         pos_type const rit_pos = rit->pos();
1475
1476         if (last < rit_pos)
1477                 cursor_vpos = rit_pos;
1478         else if (pos > last && !boundary)
1479                 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params))
1480                         ? rit_pos : last + 1;
1481         else if (pos > rit_pos && (pos > last || boundary))
1482                 // Place cursor after char at (logical) position pos - 1
1483                 cursor_vpos = (bidi_level(pos - 1) % 2 == 0)
1484                         ? log2vis(pos - 1) + 1 : log2vis(pos - 1);
1485         else
1486                 // Place cursor before char at (logical) position pos
1487                 cursor_vpos = (bidi_level(pos) % 2 == 0)
1488                         ? log2vis(pos) : log2vis(pos) + 1;
1489
1490         pos_type body_pos = pit->beginningOfBody();
1491         if (body_pos > 0 &&
1492             (body_pos - 1 > last || !pit->isLineSeparator(body_pos - 1)))
1493                 body_pos = 0;
1494
1495         for (pos_type vpos = rit_pos; vpos < cursor_vpos; ++vpos) {
1496                 pos_type pos = vis2log(vpos);
1497                 if (body_pos > 0 && pos == body_pos - 1) {
1498                         x += fill_label_hfill +
1499                                 font_metrics::width(
1500                                         pit->layout()->labelsep, getLabelFont(pit));
1501                         if (pit->isLineSeparator(body_pos - 1))
1502                                 x -= singleWidth(pit, body_pos - 1);
1503                 }
1504
1505                 if (hfillExpansion(*this, pit, rit, pos)) {
1506                         x += singleWidth(pit, pos);
1507                         if (pos >= body_pos)
1508                                 x += fill_hfill;
1509                         else
1510                                 x += fill_label_hfill;
1511                 } else if (pit->isSeparator(pos)) {
1512                         x += singleWidth(pit, pos);
1513                         if (pos >= body_pos)
1514                                 x += fill_separator;
1515                 } else
1516                         x += singleWidth(pit, pos);
1517         }
1518         return x;
1519 }
1520
1521
1522 void LyXText::setCursorIntern(ParagraphList::iterator pit,
1523                               pos_type pos, bool setfont, bool boundary)
1524 {
1525         setCursor(cursor, pit, pos, boundary);
1526         if (setfont)
1527                 setCurrentFont();
1528 }
1529
1530
1531 void LyXText::setCurrentFont()
1532 {
1533         pos_type pos = cursor.pos();
1534         ParagraphList::iterator pit = cursor.par();
1535
1536         if (cursor.boundary() && pos > 0)
1537                 --pos;
1538
1539         if (pos > 0) {
1540                 if (pos == pit->size())
1541                         --pos;
1542                 else // potentional bug... BUG (Lgb)
1543                         if (pit->isSeparator(pos)) {
1544                                 if (pos > cursorRow()->pos() &&
1545                                     bidi_level(pos) % 2 ==
1546                                     bidi_level(pos - 1) % 2)
1547                                         --pos;
1548                                 else if (pos + 1 < pit->size())
1549                                         ++pos;
1550                         }
1551         }
1552
1553         current_font = pit->getFontSettings(bv()->buffer()->params, pos);
1554         real_current_font = getFont(pit, pos);
1555
1556         if (cursor.pos() == pit->size() &&
1557             isBoundary(bv()->buffer(), *pit, cursor.pos()) &&
1558             !cursor.boundary()) {
1559                 Language const * lang =
1560                         pit->getParLanguage(bv()->buffer()->params);
1561                 current_font.setLanguage(lang);
1562                 current_font.setNumber(LyXFont::OFF);
1563                 real_current_font.setLanguage(lang);
1564                 real_current_font.setNumber(LyXFont::OFF);
1565         }
1566 }
1567
1568
1569 // returns the column near the specified x-coordinate of the row
1570 // x is set to the real beginning of this column
1571 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1572         RowList::iterator rit, int & x, bool & boundary) const
1573 {
1574         double tmpx = 0;
1575         double fill_separator;
1576         double fill_hfill;
1577         double fill_label_hfill;
1578
1579         prepareToPrint(pit, rit, tmpx, fill_separator, fill_hfill, fill_label_hfill);
1580
1581         pos_type vc = rit->pos();
1582         pos_type last = lastPrintablePos(*this, pit, rit);
1583         pos_type c = 0;
1584         LyXLayout_ptr const & layout = pit->layout();
1585
1586         bool left_side = false;
1587
1588         pos_type body_pos = pit->beginningOfBody();
1589         double last_tmpx = tmpx;
1590
1591         if (body_pos > 0 &&
1592             (body_pos - 1 > last ||
1593              !pit->isLineSeparator(body_pos - 1)))
1594                 body_pos = 0;
1595
1596         // check for empty row
1597         if (!pit->size()) {
1598                 x = int(tmpx);
1599                 return 0;
1600         }
1601
1602         while (vc <= last && tmpx <= x) {
1603                 c = vis2log(vc);
1604                 last_tmpx = tmpx;
1605                 if (body_pos > 0 && c == body_pos - 1) {
1606                         tmpx += fill_label_hfill +
1607                                 font_metrics::width(layout->labelsep, getLabelFont(pit));
1608                         if (pit->isLineSeparator(body_pos - 1))
1609                                 tmpx -= singleWidth(pit, body_pos - 1);
1610                 }
1611
1612                 if (hfillExpansion(*this, pit, rit, c)) {
1613                         tmpx += singleWidth(pit, c);
1614                         if (c >= body_pos)
1615                                 tmpx += fill_hfill;
1616                         else
1617                                 tmpx += fill_label_hfill;
1618                 } else if (pit->isSeparator(c)) {
1619                         tmpx += singleWidth(pit, c);
1620                         if (c >= body_pos)
1621                                 tmpx += fill_separator;
1622                 } else {
1623                         tmpx += singleWidth(pit, c);
1624                 }
1625                 ++vc;
1626         }
1627
1628         if ((tmpx + last_tmpx) / 2 > x) {
1629                 tmpx = last_tmpx;
1630                 left_side = true;
1631         }
1632
1633         if (vc > last + 1)  // This shouldn't happen.
1634                 vc = last + 1;
1635
1636         boundary = false;
1637         // This (rtl_support test) is not needed, but gives
1638         // some speedup if rtl_support == false
1639         bool const lastrow = lyxrc.rtl_support
1640                         && boost::next(rit) == endRow(pit);
1641
1642         // If lastrow is false, we don't need to compute
1643         // the value of rtl.
1644         bool const rtl = (lastrow)
1645                 ? pit->isRightToLeftPar(bv()->buffer()->params)
1646                 : false;
1647         if (lastrow &&
1648                  ((rtl  &&  left_side && vc == rit->pos() && x < tmpx - 5) ||
1649                   (!rtl && !left_side && vc == last + 1   && x > tmpx + 5)))
1650                 c = last + 1;
1651         else if (vc == rit->pos()) {
1652                 c = vis2log(vc);
1653                 if (bidi_level(c) % 2 == 1)
1654                         ++c;
1655         } else {
1656                 c = vis2log(vc - 1);
1657                 bool const rtl = (bidi_level(c) % 2 == 1);
1658                 if (left_side == rtl) {
1659                         ++c;
1660                         boundary = isBoundary(bv()->buffer(), *pit, c);
1661                 }
1662         }
1663
1664         if (rit->pos() <= last && c > last && pit->isNewline(last)) {
1665                 if (bidi_level(last) % 2 == 0)
1666                         tmpx -= singleWidth(pit, last);
1667                 else
1668                         tmpx += singleWidth(pit, last);
1669                 c = last;
1670         }
1671
1672         c -= rit->pos();
1673         x = int(tmpx);
1674         return c;
1675 }
1676
1677
1678 void LyXText::setCursorFromCoordinates(int x, int y)
1679 {
1680         //LyXCursor old_cursor = cursor;
1681         setCursorFromCoordinates(cursor, x, y);
1682         setCurrentFont();
1683 #warning DEPM disabled, otherwise crash when entering new table
1684         //deleteEmptyParagraphMechanism(old_cursor);
1685 }
1686
1687
1688 namespace {
1689
1690         /**
1691          * return true if the cursor given is at the end of a row,
1692          * and the next row is filled by an inset that spans an entire
1693          * row.
1694          */
1695         bool beforeFullRowInset(LyXText & lt, LyXCursor const & cur)
1696         {
1697                 RowList::iterator row = lt.getRow(cur);
1698                 if (boost::next(row) == lt.rows().end())
1699                         return false;
1700
1701                 RowList::iterator next = boost::next(row);
1702
1703                 if (next == lt.endRow(cur.par()) || next->pos() != cur.pos())
1704                         return false;
1705
1706                 if (cur.pos() == cur.par()->size()
1707                     || !cur.par()->isInset(cur.pos()))
1708                         return false;
1709
1710                 InsetOld const * inset = cur.par()->getInset(cur.pos());
1711                 if (inset->needFullRow() || inset->display())
1712                         return true;
1713
1714                 return false;
1715         }
1716 }
1717
1718
1719 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1720 {
1721         // Get the row first.
1722
1723         RowList::iterator row = getRowNearY(y);
1724         ParagraphList::iterator pit = getPar(row);
1725         bool bound = false;
1726         pos_type const column = getColumnNearX(pit, row, x, bound);
1727         cur.par(pit);
1728         cur.pos(row->pos() + column);
1729         cur.x(x);
1730         cur.y(y + row->baseline());
1731
1732         if (beforeFullRowInset(*this, cur)) {
1733                 pos_type const last = lastPrintablePos(*this, pit, row);
1734                 RowList::iterator next_row = boost::next(row);
1735                 cur.ix(int(getCursorX(pit, next_row, cur.pos(), last, bound)));
1736                 cur.iy(y + row->height() + next_row->baseline());
1737         } else {
1738                 cur.iy(cur.y());
1739                 cur.ix(cur.x());
1740         }
1741         cur.boundary(bound);
1742 }
1743
1744
1745 void LyXText::cursorLeft(bool internal)
1746 {
1747         if (cursor.pos() > 0) {
1748                 bool boundary = cursor.boundary();
1749                 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1750                 if (!internal && !boundary &&
1751                     isBoundary(bv()->buffer(), *cursor.par(), cursor.pos() + 1))
1752                         setCursor(cursor.par(), cursor.pos() + 1, true, true);
1753         } else if (cursor.par() != ownerParagraphs().begin()) {
1754                 // steps into the paragraph above
1755                 ParagraphList::iterator pit = boost::prior(cursor.par());
1756                 setCursor(pit, pit->size());
1757         }
1758 }
1759
1760
1761 void LyXText::cursorRight(bool internal)
1762 {
1763         bool const at_end = (cursor.pos() == cursor.par()->size());
1764         bool const at_newline = !at_end &&
1765                 cursor.par()->isNewline(cursor.pos());
1766
1767         if (!internal && cursor.boundary() && !at_newline)
1768                 setCursor(cursor.par(), cursor.pos(), true, false);
1769         else if (!at_end) {
1770                 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1771                 if (!internal &&
1772                     isBoundary(bv()->buffer(), *cursor.par(), cursor.pos()))
1773                         setCursor(cursor.par(), cursor.pos(), true, true);
1774         } else if (boost::next(cursor.par()) != ownerParagraphs().end())
1775                 setCursor(boost::next(cursor.par()), 0);
1776 }
1777
1778
1779 void LyXText::cursorUp(bool selecting)
1780 {
1781 #if 1
1782         int x = cursor.x_fix();
1783         int y = cursor.y() - cursorRow()->baseline() - 1;
1784         setCursorFromCoordinates(x, y);
1785         if (!selecting) {
1786                 int topy = top_y();
1787                 int y1 = cursor.iy() - topy;
1788                 int y2 = y1;
1789                 y -= topy;
1790                 InsetOld * inset_hit = checkInsetHit(x, y1);
1791                 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1792                         inset_hit->localDispatch(
1793                                 FuncRequest(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none));
1794                 }
1795         }
1796 #else
1797         setCursorFromCoordinates(bv(), cursor.x_fix(),
1798                                  cursor.y() - cursorRow()->baseline() - 1);
1799 #endif
1800 }
1801
1802
1803 void LyXText::cursorDown(bool selecting)
1804 {
1805 #if 1
1806         int x = cursor.x_fix();
1807         int y = cursor.y() - cursorRow()->baseline() + cursorRow()->height() + 1;
1808         setCursorFromCoordinates(x, y);
1809         if (!selecting && cursorRow() == cursorIRow()) {
1810                 int topy = top_y();
1811                 int y1 = cursor.iy() - topy;
1812                 int y2 = y1;
1813                 y -= topy;
1814                 InsetOld * inset_hit = checkInsetHit(x, y1);
1815                 if (inset_hit && isHighlyEditableInset(inset_hit)) {
1816                         FuncRequest cmd(bv(), LFUN_INSET_EDIT, x, y - (y2 - y1), mouse_button::none);
1817                         inset_hit->localDispatch(cmd);
1818                 }
1819         }
1820 #else
1821         setCursorFromCoordinates(bv(), cursor.x_fix(),
1822                                  cursor.y() - cursorRow()->baseline()
1823                                  + cursorRow()->height() + 1);
1824 #endif
1825 }
1826
1827
1828 void LyXText::cursorUpParagraph()
1829 {
1830         if (cursor.pos() > 0)
1831                 setCursor(cursor.par(), 0);
1832         else if (cursor.par() != ownerParagraphs().begin())
1833                 setCursor(boost::prior(cursor.par()), 0);
1834 }
1835
1836
1837 void LyXText::cursorDownParagraph()
1838 {
1839         ParagraphList::iterator par = cursor.par();
1840         ParagraphList::iterator next_par = boost::next(par);
1841
1842         if (next_par != ownerParagraphs().end())
1843                 setCursor(next_par, 0);
1844         else
1845                 setCursor(par, par->size());
1846 }
1847
1848
1849 // fix the cursor `cur' after a characters has been deleted at `where'
1850 // position. Called by deleteEmptyParagraphMechanism
1851 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1852 {
1853         // if cursor is not in the paragraph where the delete occured,
1854         // do nothing
1855         if (cur.par() != where.par())
1856                 return;
1857
1858         // if cursor position is after the place where the delete occured,
1859         // update it
1860         if (cur.pos() > where.pos())
1861                 cur.pos(cur.pos()-1);
1862
1863         // check also if we don't want to set the cursor on a spot behind the
1864         // pagragraph because we erased the last character.
1865         if (cur.pos() > cur.par()->size())
1866                 cur.pos(cur.par()->size());
1867
1868         // recompute row et al. for this cursor
1869         setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1870 }
1871
1872
1873 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1874 {
1875         // Would be wrong to delete anything if we have a selection.
1876         if (selection.set())
1877                 return false;
1878
1879         // We allow all kinds of "mumbo-jumbo" when freespacing.
1880         if (old_cursor.par()->isFreeSpacing())
1881                 return false;
1882
1883         /* Ok I'll put some comments here about what is missing.
1884            I have fixed BackSpace (and thus Delete) to not delete
1885            double-spaces automagically. I have also changed Cut,
1886            Copy and Paste to hopefully do some sensible things.
1887            There are still some small problems that can lead to
1888            double spaces stored in the document file or space at
1889            the beginning of paragraphs. This happens if you have
1890            the cursor betwenn to spaces and then save. Or if you
1891            cut and paste and the selection have a space at the
1892            beginning and then save right after the paste. I am
1893            sure none of these are very hard to fix, but I will
1894            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1895            that I can get some feedback. (Lgb)
1896         */
1897
1898         // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1899         // delete the LineSeparator.
1900         // MISSING
1901
1902         // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1903         // delete the LineSeparator.
1904         // MISSING
1905
1906         // If the pos around the old_cursor were spaces, delete one of them.
1907         if (old_cursor.par() != cursor.par()
1908             || old_cursor.pos() != cursor.pos()) {
1909
1910                 // Only if the cursor has really moved
1911                 if (old_cursor.pos() > 0
1912                     && old_cursor.pos() < old_cursor.par()->size()
1913                     && old_cursor.par()->isLineSeparator(old_cursor.pos())
1914                     && old_cursor.par()->isLineSeparator(old_cursor.pos() - 1)) {
1915                         bool erased = old_cursor.par()->erase(old_cursor.pos() - 1);
1916                         redoParagraph(old_cursor.par());
1917
1918                         if (!erased)
1919                                 return false;
1920 #ifdef WITH_WARNINGS
1921 #warning This will not work anymore when we have multiple views of the same buffer
1922 // In this case, we will have to correct also the cursors held by
1923 // other bufferviews. It will probably be easier to do that in a more
1924 // automated way in LyXCursor code. (JMarc 26/09/2001)
1925 #endif
1926                         // correct all cursors held by the LyXText
1927                         fixCursorAfterDelete(cursor, old_cursor);
1928                         fixCursorAfterDelete(selection.cursor, old_cursor);
1929                         fixCursorAfterDelete(selection.start, old_cursor);
1930                         fixCursorAfterDelete(selection.end, old_cursor);
1931                         return false;
1932                 }
1933         }
1934
1935         // don't delete anything if this is the ONLY paragraph!
1936         if (ownerParagraphs().size() == 1)
1937                 return false;
1938
1939         // Do not delete empty paragraphs with keepempty set.
1940         if (old_cursor.par()->allowEmpty())
1941                 return false;
1942
1943         // only do our magic if we changed paragraph
1944         if (old_cursor.par() == cursor.par())
1945                 return false;
1946
1947         // record if we have deleted a paragraph
1948         // we can't possibly have deleted a paragraph before this point
1949         bool deleted = false;
1950
1951         if (old_cursor.par()->empty() ||
1952             (old_cursor.par()->size() == 1 &&
1953              old_cursor.par()->isLineSeparator(0))) {
1954                 // ok, we will delete anything
1955                 LyXCursor tmpcursor;
1956
1957                 deleted = true;
1958
1959                 bool selection_position_was_oldcursor_position = (
1960                         selection.cursor.par()  == old_cursor.par()
1961                         && selection.cursor.pos() == old_cursor.pos());
1962
1963                 if (getRow(old_cursor) != rows().begin()) {
1964                         RowList::iterator prevrow = boost::prior(getRow(old_cursor));
1965                         tmpcursor = cursor;
1966                         cursor = old_cursor; // that undo can restore the right cursor position
1967                         #warning FIXME. --end() iterator is usable here
1968                         ParagraphList::iterator endpit = boost::next(old_cursor.par());
1969                         while (endpit != ownerParagraphs().end() &&
1970                                endpit->getDepth()) {
1971                                 ++endpit;
1972                         }
1973
1974                         recordUndo(bv(), Undo::DELETE, old_cursor.par(),
1975                                 boost::prior(endpit));
1976                         cursor = tmpcursor;
1977
1978                         // delete old row
1979                         removeRow(getRow(old_cursor));
1980                         // delete old par
1981                         ownerParagraphs().erase(old_cursor.par());
1982                 } else {
1983                         RowList::iterator nextrow = boost::next(getRow(old_cursor));
1984
1985                         tmpcursor = cursor;
1986                         cursor = old_cursor; // that undo can restore the right cursor position
1987 #warning FIXME. --end() iterator is usable here
1988                         ParagraphList::iterator endpit = boost::next(old_cursor.par());
1989                         while (endpit != ownerParagraphs().end() &&
1990                                endpit->getDepth()) {
1991                                 ++endpit;
1992                         }
1993
1994                         recordUndo(bv(), Undo::DELETE, old_cursor.par(), boost::prior(endpit));
1995                         cursor = tmpcursor;
1996
1997                         // delete old row
1998                         removeRow(getRow(old_cursor));
1999                         // delete old par
2000                         ownerParagraphs().erase(old_cursor.par());
2001                 }
2002
2003                 // correct cursor y
2004                 setCursorIntern(cursor.par(), cursor.pos());
2005
2006                 if (selection_position_was_oldcursor_position) {
2007                         // correct selection
2008                         selection.cursor = cursor;
2009                 }
2010         }
2011         if (!deleted) {
2012                 if (old_cursor.par()->stripLeadingSpaces()) {
2013                         redoParagraph(old_cursor.par());
2014                         // correct cursor y
2015                         setCursorIntern(cursor.par(), cursor.pos());
2016                         selection.cursor = cursor;
2017                 }
2018         }
2019         return deleted;
2020 }
2021
2022
2023 ParagraphList & LyXText::ownerParagraphs() const
2024 {
2025         if (inset_owner) {
2026                 return inset_owner->paragraphs;
2027         }
2028         return bv_owner->buffer()->paragraphs;
2029 }
2030
2031
2032 bool LyXText::isInInset() const
2033 {
2034         // Sub-level has non-null bv owner and non-null inset owner.
2035         return inset_owner != 0 && bv_owner != 0;
2036 }
2037
2038
2039 int defaultRowHeight()
2040 {
2041         LyXFont const font(LyXFont::ALL_SANE);
2042         return int(font_metrics::maxAscent(font)
2043                  + font_metrics::maxDescent(font) * 1.5);
2044 }