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