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