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