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