]> git.lyx.org Git - lyx.git/blob - src/text2.C
A couple of changing resulting from gcc 3.4 debug mode testing.
[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 rebroken
1013 void LyXText::updateCounters()
1014 {
1015         // start over
1016         bv()->buffer()->params().getLyXTextClass().counters().reset();
1017
1018         ParagraphList::iterator beg = ownerParagraphs().begin();
1019         ParagraphList::iterator end = ownerParagraphs().end();
1020         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
1021                 string const oldLabel = pit->params().labelString();
1022
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                 string const & newLabel = pit->params().labelString();
1034
1035                 if (oldLabel != newLabel)
1036                         redoParagraph(pit);
1037         }
1038 }
1039
1040
1041 void LyXText::insertInset(InsetOld * inset)
1042 {
1043         if (!cursorPar()->insetAllowed(inset->lyxCode()))
1044                 return;
1045
1046         recUndo(cursor.par());
1047         freezeUndo();
1048         cursorPar()->insertInset(cursor.pos(), inset);
1049         // Just to rebreak and refresh correctly.
1050         // The character will not be inserted a second time
1051         insertChar(Paragraph::META_INSET);
1052         // If we enter a highly editable inset the cursor should be before
1053         // the inset. After an Undo LyX tries to call inset->edit(...)
1054         // and fails if the cursor is behind the inset and getInset
1055         // does not return the inset!
1056         if (isHighlyEditableInset(inset))
1057                 cursorLeft(true);
1058
1059         unFreezeUndo();
1060 }
1061
1062
1063 void LyXText::cutSelection(bool doclear, bool realcut)
1064 {
1065         // Stuff what we got on the clipboard. Even if there is no selection.
1066
1067         // There is a problem with having the stuffing here in that the
1068         // larger the selection the slower LyX will get. This can be
1069         // solved by running the line below only when the selection has
1070         // finished. The solution used currently just works, to make it
1071         // faster we need to be more clever and probably also have more
1072         // calls to stuffClipboard. (Lgb)
1073         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1074
1075         // This doesn't make sense, if there is no selection
1076         if (!selection.set())
1077                 return;
1078
1079         // OK, we have a selection. This is always between selection.start
1080         // and selection.end
1081
1082         // make sure that the depth behind the selection are restored, too
1083         ParagraphList::iterator endpit = boost::next(getPar(selection.end.par()));
1084         ParagraphList::iterator undoendpit = endpit;
1085         ParagraphList::iterator pars_end = ownerParagraphs().end();
1086
1087         if (endpit != pars_end && endpit->getDepth()) {
1088                 while (endpit != pars_end && endpit->getDepth()) {
1089                         ++endpit;
1090                         undoendpit = endpit;
1091                 }
1092         } else if (endpit != pars_end) {
1093                 // because of parindents etc.
1094                 ++endpit;
1095         }
1096
1097         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
1098
1099         endpit = getPar(selection.end.par());
1100         int endpos = selection.end.pos();
1101
1102         BufferParams const & bufparams = bv()->buffer()->params();
1103         boost::tie(endpit, endpos) = realcut ?
1104                 CutAndPaste::cutSelection(bufparams,
1105                                           ownerParagraphs(),
1106                                           getPar(selection.start.par()), endpit,
1107                                           selection.start.pos(), endpos,
1108                                           bufparams.textclass,
1109                                           doclear)
1110                 : CutAndPaste::eraseSelection(bufparams,
1111                                               ownerParagraphs(),
1112                                               getPar(selection.start.par()), endpit,
1113                                               selection.start.pos(), endpos,
1114                                               doclear);
1115         // sometimes necessary
1116         if (doclear)
1117                 getPar(selection.start.par())->stripLeadingSpaces();
1118
1119         redoParagraphs(getPar(selection.start.par()), boost::next(endpit));
1120         // cutSelection can invalidate the cursor so we need to set
1121         // it anew. (Lgb)
1122         // we prefer the end for when tracking changes
1123         cursor.pos(endpos);
1124         cursor.par(parOffset(endpit));
1125
1126         // need a valid cursor. (Lgb)
1127         clearSelection();
1128
1129         setCursor(cursorPar(), cursor.pos());
1130         selection.cursor = cursor;
1131         updateCounters();
1132 }
1133
1134
1135 void LyXText::copySelection()
1136 {
1137         // stuff the selection onto the X clipboard, from an explicit copy request
1138         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1139
1140         // this doesnt make sense, if there is no selection
1141         if (!selection.set())
1142                 return;
1143
1144         // ok we have a selection. This is always between selection.start
1145         // and sel_end cursor
1146
1147         // copy behind a space if there is one
1148         while (getPar(selection.start)->size() > selection.start.pos()
1149                && getPar(selection.start)->isLineSeparator(selection.start.pos())
1150                && (selection.start.par() != selection.end.par()
1151                    || selection.start.pos() < selection.end.pos()))
1152                 selection.start.pos(selection.start.pos() + 1);
1153
1154         CutAndPaste::copySelection(getPar(selection.start.par()),
1155                                    getPar(selection.end.par()),
1156                                    selection.start.pos(), selection.end.pos(),
1157                                    bv()->buffer()->params().textclass);
1158 }
1159
1160
1161 void LyXText::pasteSelection(size_t sel_index)
1162 {
1163         // this does not make sense, if there is nothing to paste
1164         if (!CutAndPaste::checkPastePossible())
1165                 return;
1166
1167         recUndo(cursor.par());
1168
1169         ParagraphList::iterator endpit;
1170         PitPosPair ppp;
1171
1172         ErrorList el;
1173
1174         boost::tie(ppp, endpit) =
1175                 CutAndPaste::pasteSelection(*bv()->buffer(),
1176                                             ownerParagraphs(),
1177                                             cursorPar(), cursor.pos(),
1178                                             bv()->buffer()->params().textclass,
1179                                             sel_index, el);
1180         bufferErrors(*bv()->buffer(), el);
1181         bv()->showErrorList(_("Paste"));
1182
1183         redoParagraphs(cursorPar(), endpit);
1184
1185         setCursor(cursor.par(), cursor.pos());
1186         clearSelection();
1187
1188         selection.cursor = cursor;
1189         setCursor(ppp.first, ppp.second);
1190         setSelection();
1191         updateCounters();
1192 }
1193
1194
1195 void LyXText::setSelectionRange(lyx::pos_type length)
1196 {
1197         if (!length)
1198                 return;
1199
1200         selection.cursor = cursor;
1201         while (length--)
1202                 cursorRight(true);
1203         setSelection();
1204 }
1205
1206
1207 // simple replacing. The font of the first selected character is used
1208 void LyXText::replaceSelectionWithString(string const & str)
1209 {
1210         recUndo(cursor.par());
1211         freezeUndo();
1212
1213         if (!selection.set()) { // create a dummy selection
1214                 selection.end = cursor;
1215                 selection.start = cursor;
1216         }
1217
1218         // Get font setting before we cut
1219         pos_type pos = selection.end.pos();
1220         LyXFont const font = getPar(selection.start)
1221                 ->getFontSettings(bv()->buffer()->params(),
1222                                   selection.start.pos());
1223
1224         // Insert the new string
1225         string::const_iterator cit = str.begin();
1226         string::const_iterator end = str.end();
1227         for (; cit != end; ++cit) {
1228                 getPar(selection.end)->insertChar(pos, (*cit), font);
1229                 ++pos;
1230         }
1231
1232         // Cut the selection
1233         cutSelection(true, false);
1234
1235         unFreezeUndo();
1236 }
1237
1238
1239 // needed to insert the selection
1240 void LyXText::insertStringAsLines(string const & str)
1241 {
1242         ParagraphList::iterator pit = cursorPar();
1243         pos_type pos = cursor.pos();
1244         ParagraphList::iterator endpit = boost::next(cursorPar());
1245
1246         recUndo(cursor.par());
1247
1248         // only to be sure, should not be neccessary
1249         clearSelection();
1250
1251         bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1252
1253         redoParagraphs(cursorPar(), endpit);
1254         setCursor(cursorPar(), cursor.pos());
1255         selection.cursor = cursor;
1256         setCursor(pit, pos);
1257         setSelection();
1258 }
1259
1260
1261 // turns double-CR to single CR, others where converted into one
1262 // blank. Then InsertStringAsLines is called
1263 void LyXText::insertStringAsParagraphs(string const & str)
1264 {
1265         string linestr(str);
1266         bool newline_inserted = false;
1267         string::size_type const siz = linestr.length();
1268
1269         for (string::size_type i = 0; i < siz; ++i) {
1270                 if (linestr[i] == '\n') {
1271                         if (newline_inserted) {
1272                                 // we know that \r will be ignored by
1273                                 // InsertStringA. Of course, it is a dirty
1274                                 // trick, but it works...
1275                                 linestr[i - 1] = '\r';
1276                                 linestr[i] = '\n';
1277                         } else {
1278                                 linestr[i] = ' ';
1279                                 newline_inserted = true;
1280                         }
1281                 } else if (IsPrintable(linestr[i])) {
1282                         newline_inserted = false;
1283                 }
1284         }
1285         insertStringAsLines(linestr);
1286 }
1287
1288
1289 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1290 {
1291         setCursor(parOffset(pit), pos);
1292 }
1293
1294
1295 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont, bool boundary)
1296 {
1297         LyXCursor old_cursor = cursor;
1298         setCursorIntern(par, pos, setfont, boundary);
1299         return deleteEmptyParagraphMechanism(old_cursor);
1300 }
1301
1302
1303 void LyXText::redoCursor()
1304 {
1305 #warning maybe the same for selections?
1306         setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1307 }
1308
1309
1310 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1311         pos_type pos, bool boundary)
1312 {
1313         BOOST_ASSERT(par != int(ownerParagraphs().size()));
1314
1315         cur.par(par);
1316         cur.pos(pos);
1317         cur.boundary(boundary);
1318
1319         // no rows, no fun...
1320         if (ownerParagraphs().begin()->rows.empty())
1321                 return;
1322
1323         // get the cursor y position in text
1324
1325         ParagraphList::iterator pit = getPar(par);
1326         Row const & row = *pit->getRow(pos);
1327
1328         int y = pit->y + row.y_offset();
1329
1330         // y is now the beginning of the cursor row
1331         y += row.baseline();
1332         // y is now the cursor baseline
1333         cur.y(y);
1334
1335         pos_type const end = row.endpos();
1336
1337         // None of these should happen, but we're scaredy-cats
1338         if (pos < 0) {
1339                 lyxerr << "dont like -1" << endl;
1340                 pos = 0;
1341                 cur.pos(0);
1342                 BOOST_ASSERT(false);
1343         } else if (pos > pit->size()) {
1344                 lyxerr << "dont like 1, pos: " << pos
1345                        << " size: " << pit->size()
1346                        << " row.pos():" << row.pos()
1347                        << " paroffset: " << par << endl;
1348                 pos = 0;
1349                 cur.pos(0);
1350                 BOOST_ASSERT(false);
1351         } else if (pos > end) {
1352                 lyxerr << "dont like 2 please report" << endl;
1353                 // This shouldn't happen.
1354                 pos = end;
1355                 cur.pos(pos);
1356                 BOOST_ASSERT(false);
1357         } else if (pos < row.pos()) {
1358                 lyxerr << "dont like 3 please report pos:" << pos
1359                        << " size: " << pit->size()
1360                        << " row.pos():" << row.pos()
1361                        << " paroffset: " << par << endl;
1362                 pos = row.pos();
1363                 cur.pos(pos);
1364                 BOOST_ASSERT(false);
1365         }
1366         // now get the cursors x position
1367         cur.x(int(getCursorX(pit, row, pos, boundary)));
1368 }
1369
1370
1371 float LyXText::getCursorX(ParagraphList::iterator pit, Row const & row,
1372                           pos_type pos, bool boundary) const
1373 {
1374         pos_type cursor_vpos    = 0;
1375         double x                = row.x();
1376         double fill_separator   = row.fill_separator();
1377         double fill_hfill       = row.fill_hfill();
1378         double fill_label_hfill = row.fill_label_hfill();
1379         pos_type const row_pos  = row.pos();
1380         pos_type const end = row.endpos();
1381
1382         if (end <= row_pos)
1383                 cursor_vpos = row_pos;
1384         else if (pos >= end && !boundary)
1385                 cursor_vpos = (pit->isRightToLeftPar(bv()->buffer()->params()))
1386                         ? row_pos : end;
1387         else if (pos > row_pos && (pos >= end || boundary))
1388                 // Place cursor after char at (logical) position pos - 1
1389                 cursor_vpos = (bidi.level(pos - 1) % 2 == 0)
1390                         ? bidi.log2vis(pos - 1) + 1 : bidi.log2vis(pos - 1);
1391         else
1392                 // Place cursor before char at (logical) position pos
1393                 cursor_vpos = (bidi.level(pos) % 2 == 0)
1394                         ? bidi.log2vis(pos) : bidi.log2vis(pos) + 1;
1395
1396         pos_type body_pos = pit->beginOfBody();
1397         if (body_pos > 0 &&
1398             (body_pos > end || !pit->isLineSeparator(body_pos - 1)))
1399                 body_pos = 0;
1400
1401         for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
1402                 pos_type pos = bidi.vis2log(vpos);
1403                 if (body_pos > 0 && pos == body_pos - 1) {
1404                         x += fill_label_hfill
1405                                 + font_metrics::width(pit->layout()->labelsep,
1406                                                       getLabelFont(pit));
1407                         if (pit->isLineSeparator(body_pos - 1))
1408                                 x -= singleWidth(pit, body_pos - 1);
1409                 }
1410
1411                 if (hfillExpansion(*pit, row, pos)) {
1412                         x += singleWidth(pit, pos);
1413                         if (pos >= body_pos)
1414                                 x += fill_hfill;
1415                         else
1416                                 x += fill_label_hfill;
1417                 } else if (pit->isSeparator(pos)) {
1418                         x += singleWidth(pit, pos);
1419                         if (pos >= body_pos)
1420                                 x += fill_separator;
1421                 } else
1422                         x += singleWidth(pit, pos);
1423         }
1424         return x;
1425 }
1426
1427
1428 void LyXText::setCursorIntern(paroffset_type par,
1429                               pos_type pos, bool setfont, bool boundary)
1430 {
1431         setCursor(cursor, par, pos, boundary);
1432         bv()->x_target(cursor.x() + xo_);
1433         if (setfont)
1434                 setCurrentFont();
1435 }
1436
1437
1438 void LyXText::setCurrentFont()
1439 {
1440         pos_type pos = cursor.pos();
1441         ParagraphList::iterator pit = cursorPar();
1442
1443         if (cursor.boundary() && pos > 0)
1444                 --pos;
1445
1446         if (pos > 0) {
1447                 if (pos == pit->size())
1448                         --pos;
1449                 else // potentional bug... BUG (Lgb)
1450                         if (pit->isSeparator(pos)) {
1451                                 if (pos > pit->getRow(pos)->pos() &&
1452                                     bidi.level(pos) % 2 ==
1453                                     bidi.level(pos - 1) % 2)
1454                                         --pos;
1455                                 else if (pos + 1 < pit->size())
1456                                         ++pos;
1457                         }
1458         }
1459
1460         BufferParams const & bufparams = bv()->buffer()->params();
1461         current_font = pit->getFontSettings(bufparams, pos);
1462         real_current_font = getFont(pit, pos);
1463
1464         if (cursor.pos() == pit->size() &&
1465             bidi.isBoundary(*bv()->buffer(), *pit, cursor.pos()) &&
1466             !cursor.boundary()) {
1467                 Language const * lang =
1468                         pit->getParLanguage(bufparams);
1469                 current_font.setLanguage(lang);
1470                 current_font.setNumber(LyXFont::OFF);
1471                 real_current_font.setLanguage(lang);
1472                 real_current_font.setNumber(LyXFont::OFF);
1473         }
1474 }
1475
1476
1477 // returns the column near the specified x-coordinate of the row
1478 // x is set to the real beginning of this column
1479 pos_type LyXText::getColumnNearX(ParagraphList::iterator pit,
1480         Row const & row, int & x, bool & boundary) const
1481 {
1482         double tmpx             = row.x();
1483         double fill_separator   = row.fill_separator();
1484         double fill_hfill       = row.fill_hfill();
1485         double fill_label_hfill = row.fill_label_hfill();
1486
1487         pos_type vc = row.pos();
1488         pos_type end = row.endpos();
1489         pos_type c = 0;
1490         LyXLayout_ptr const & layout = pit->layout();
1491
1492         bool left_side = false;
1493
1494         pos_type body_pos = pit->beginOfBody();
1495         double last_tmpx = tmpx;
1496
1497         if (body_pos > 0 &&
1498             (body_pos > end ||
1499              !pit->isLineSeparator(body_pos - 1)))
1500                 body_pos = 0;
1501
1502         // check for empty row
1503         if (vc == end) {
1504                 x = int(tmpx);
1505                 return 0;
1506         }
1507
1508         while (vc < end && tmpx <= x) {
1509                 c = bidi.vis2log(vc);
1510                 last_tmpx = tmpx;
1511                 if (body_pos > 0 && c == body_pos - 1) {
1512                         tmpx += fill_label_hfill +
1513                                 font_metrics::width(layout->labelsep, getLabelFont(pit));
1514                         if (pit->isLineSeparator(body_pos - 1))
1515                                 tmpx -= singleWidth(pit, body_pos - 1);
1516                 }
1517
1518                 if (hfillExpansion(*pit, row, c)) {
1519                         tmpx += singleWidth(pit, c);
1520                         if (c >= body_pos)
1521                                 tmpx += fill_hfill;
1522                         else
1523                                 tmpx += fill_label_hfill;
1524                 } else if (pit->isSeparator(c)) {
1525                         tmpx += singleWidth(pit, c);
1526                         if (c >= body_pos)
1527                                 tmpx += fill_separator;
1528                 } else {
1529                         tmpx += singleWidth(pit, c);
1530                 }
1531                 ++vc;
1532         }
1533
1534         if ((tmpx + last_tmpx) / 2 > x) {
1535                 tmpx = last_tmpx;
1536                 left_side = true;
1537         }
1538
1539         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
1540
1541         boundary = false;
1542         // This (rtl_support test) is not needed, but gives
1543         // some speedup if rtl_support == false
1544         bool const lastrow = lyxrc.rtl_support && row.endpos() == pit->size();
1545
1546         // If lastrow is false, we don't need to compute
1547         // the value of rtl.
1548         bool const rtl = (lastrow)
1549                 ? pit->isRightToLeftPar(bv()->buffer()->params())
1550                 : false;
1551         if (lastrow &&
1552                  ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
1553                   (!rtl && !left_side && vc == end  && x > tmpx + 5)))
1554                 c = end;
1555         else if (vc == row.pos()) {
1556                 c = bidi.vis2log(vc);
1557                 if (bidi.level(c) % 2 == 1)
1558                         ++c;
1559         } else {
1560                 c = bidi.vis2log(vc - 1);
1561                 bool const rtl = (bidi.level(c) % 2 == 1);
1562                 if (left_side == rtl) {
1563                         ++c;
1564                         boundary = bidi.isBoundary(*bv()->buffer(), *pit, c);
1565                 }
1566         }
1567
1568         if (row.pos() < end && c >= end && pit->isNewline(end - 1)) {
1569                 if (bidi.level(end -1) % 2 == 0)
1570                         tmpx -= singleWidth(pit, end - 1);
1571                 else
1572                         tmpx += singleWidth(pit, end - 1);
1573                 c = end - 1;
1574         }
1575
1576         c -= row.pos();
1577         x = int(tmpx);
1578         return c;
1579 }
1580
1581
1582 void LyXText::setCursorFromCoordinates(int x, int y)
1583 {
1584         LyXCursor old_cursor = cursor;
1585         setCursorFromCoordinates(cursor, x, y);
1586         setCurrentFont();
1587         deleteEmptyParagraphMechanism(old_cursor);
1588 }
1589
1590 //x,y are coordinates relative to this LyXText
1591 void LyXText::setCursorFromCoordinates(LyXCursor & cur, int x, int y)
1592 {
1593         // Get the row first.
1594         ParagraphList::iterator pit;
1595         Row const & row = *getRowNearY(y, pit);
1596         y = pit->y + row.y_offset();
1597
1598         bool bound = false;
1599         pos_type const column = getColumnNearX(pit, row, x, bound);
1600         cur.par(parOffset(pit));
1601         cur.pos(row.pos() + column);
1602         cur.x(x);
1603         cur.y(y + row.baseline());
1604
1605         cur.boundary(bound);
1606 }
1607
1608
1609
1610 bool LyXText::checkAndActivateInset(bool front)
1611 {
1612         if (cursor.pos() == cursorPar()->size())
1613                 return false;
1614         InsetOld * inset = cursorPar()->getInset(cursor.pos());
1615         if (!isHighlyEditableInset(inset))
1616                 return false;
1617         inset->edit(bv(), front);
1618         return true;
1619 }
1620
1621
1622 DispatchResult LyXText::moveRight()
1623 {
1624         if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1625                 return moveLeftIntern(false, true, false);
1626         else
1627                 return moveRightIntern(true, true, false);
1628 }
1629
1630
1631 DispatchResult LyXText::moveLeft()
1632 {
1633         if (cursorPar()->isRightToLeftPar(bv()->buffer()->params()))
1634                 return moveRightIntern(true, true, false);
1635         else
1636                 return moveLeftIntern(false, true, false);
1637 }
1638
1639
1640 DispatchResult LyXText::moveRightIntern(bool front, bool activate_inset, bool selecting)
1641 {
1642         ParagraphList::iterator c_par = cursorPar();
1643         if (boost::next(c_par) == ownerParagraphs().end()
1644                 && cursor.pos() >= c_par->size())
1645                 return DispatchResult(false, FINISHED_RIGHT);
1646         if (activate_inset && checkAndActivateInset(front))
1647                 return DispatchResult(true, true);
1648         cursorRight(true);
1649         if (!selecting)
1650                 clearSelection();
1651         return DispatchResult(true);
1652 }
1653
1654
1655 DispatchResult LyXText::moveLeftIntern(bool front,
1656                           bool activate_inset, bool selecting)
1657 {
1658         if (cursor.par() == 0 && cursor.pos() <= 0)
1659                 return DispatchResult(false, FINISHED);
1660         cursorLeft(true);
1661         if (!selecting)
1662                 clearSelection();
1663         if (activate_inset && checkAndActivateInset(front))
1664                 return DispatchResult(true, true);
1665         return DispatchResult(true);
1666 }
1667
1668
1669 DispatchResult LyXText::moveUp()
1670 {
1671         if (cursorPar() == firstPar() && cursorRow() == firstRow())
1672                 return DispatchResult(false, FINISHED_UP);
1673         cursorUp(false);
1674         clearSelection();
1675         return DispatchResult(true);
1676 }
1677
1678
1679 DispatchResult LyXText::moveDown()
1680 {
1681         if (cursorPar() == lastPar() && cursorRow() == lastRow())
1682                 return DispatchResult(false, FINISHED_DOWN);
1683         cursorDown(false);
1684         clearSelection();
1685         return DispatchResult(true);
1686 }
1687
1688
1689 bool LyXText::cursorLeft(bool internal)
1690 {
1691         if (cursor.pos() > 0) {
1692                 bool boundary = cursor.boundary();
1693                 setCursor(cursor.par(), cursor.pos() - 1, true, false);
1694                 if (!internal && !boundary &&
1695                     bidi.isBoundary(*bv()->buffer(), *cursorPar(), cursor.pos() + 1))
1696                         setCursor(cursor.par(), cursor.pos() + 1, true, true);
1697                 return true;
1698         }
1699
1700         if (cursor.par() != 0) {
1701                 // steps into the paragraph above
1702                 setCursor(cursor.par() - 1, boost::prior(cursorPar())->size());
1703                 return true;
1704         }
1705
1706         return false;
1707 }
1708
1709
1710 bool LyXText::cursorRight(bool internal)
1711 {
1712         if (!internal && cursor.boundary()) {
1713                 setCursor(cursor.par(), cursor.pos(), true, false);
1714                 return true;
1715         }
1716
1717         if (cursor.pos() != cursorPar()->size()) {
1718                 setCursor(cursor.par(), cursor.pos() + 1, true, false);
1719                 if (!internal && bidi.isBoundary(*bv()->buffer(), *cursorPar(),
1720                                                  cursor.pos()))
1721                         setCursor(cursor.par(), cursor.pos(), true, true);
1722                 return true;
1723         }
1724
1725         if (cursor.par() + 1 != int(ownerParagraphs().size())) {
1726                 setCursor(cursor.par() + 1, 0);
1727                 return true;
1728         }
1729
1730         return false;
1731 }
1732
1733
1734 void LyXText::cursorUp(bool selecting)
1735 {
1736         Row const & row = *cursorRow();
1737         int x = bv()->x_target() - xo_;
1738         int y = cursor.y() - row.baseline() - 1;
1739         setCursorFromCoordinates(x, y);
1740
1741         if (!selecting) {
1742                 int y_abs = y + yo_ - bv()->top_y();
1743                 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1744                 if (inset_hit && isHighlyEditableInset(inset_hit))
1745                         inset_hit->edit(bv(), bv()->x_target(), y_abs);
1746         }
1747 }
1748
1749
1750 void LyXText::cursorDown(bool selecting)
1751 {
1752         Row const & row = *cursorRow();
1753         int x = bv()->x_target() - xo_;
1754         int y = cursor.y() - row.baseline() + row.height() + 1;
1755         setCursorFromCoordinates(x, y);
1756
1757         if (!selecting) {
1758                 int y_abs = y + yo_ - bv()->top_y();
1759                 InsetOld * inset_hit = checkInsetHit(bv()->x_target(), y_abs);
1760                 if (inset_hit && isHighlyEditableInset(inset_hit))
1761                         inset_hit->edit(bv(), bv()->x_target(), y_abs);
1762         }
1763 }
1764
1765
1766 void LyXText::cursorUpParagraph()
1767 {
1768         ParagraphList::iterator cpit = cursorPar();
1769         if (cursor.pos() > 0)
1770                 setCursor(cpit, 0);
1771         else if (cpit != ownerParagraphs().begin())
1772                 setCursor(boost::prior(cpit), 0);
1773 }
1774
1775
1776 void LyXText::cursorDownParagraph()
1777 {
1778         ParagraphList::iterator pit = cursorPar();
1779         ParagraphList::iterator next_pit = boost::next(pit);
1780
1781         if (next_pit != ownerParagraphs().end())
1782                 setCursor(next_pit, 0);
1783         else
1784                 setCursor(pit, pit->size());
1785 }
1786
1787
1788 // fix the cursor `cur' after a characters has been deleted at `where'
1789 // position. Called by deleteEmptyParagraphMechanism
1790 void LyXText::fixCursorAfterDelete(LyXCursor & cur, LyXCursor const & where)
1791 {
1792         // if cursor is not in the paragraph where the delete occured,
1793         // do nothing
1794         if (cur.par() != where.par())
1795                 return;
1796
1797         // if cursor position is after the place where the delete occured,
1798         // update it
1799         if (cur.pos() > where.pos())
1800                 cur.pos(cur.pos()-1);
1801
1802         // check also if we don't want to set the cursor on a spot behind the
1803         // pagragraph because we erased the last character.
1804         if (cur.pos() > getPar(cur)->size())
1805                 cur.pos(getPar(cur)->size());
1806
1807         // recompute row et al. for this cursor
1808         setCursor(cur, cur.par(), cur.pos(), cur.boundary());
1809 }
1810
1811
1812 bool LyXText::deleteEmptyParagraphMechanism(LyXCursor const & old_cursor)
1813 {
1814         // Would be wrong to delete anything if we have a selection.
1815         if (selection.set())
1816                 return false;
1817
1818         // Don't do anything if the cursor is invalid
1819         if (old_cursor.par() == -1)
1820                 return false;
1821
1822         // We allow all kinds of "mumbo-jumbo" when freespacing.
1823         ParagraphList::iterator const old_pit = getPar(old_cursor);
1824         if (old_pit->isFreeSpacing())
1825                 return false;
1826
1827         /* Ok I'll put some comments here about what is missing.
1828            I have fixed BackSpace (and thus Delete) to not delete
1829            double-spaces automagically. I have also changed Cut,
1830            Copy and Paste to hopefully do some sensible things.
1831            There are still some small problems that can lead to
1832            double spaces stored in the document file or space at
1833            the beginning of paragraphs. This happens if you have
1834            the cursor between to spaces and then save. Or if you
1835            cut and paste and the selection have a space at the
1836            beginning and then save right after the paste. I am
1837            sure none of these are very hard to fix, but I will
1838            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1839            that I can get some feedback. (Lgb)
1840         */
1841
1842         // If old_cursor.pos() == 0 and old_cursor.pos()(1) == LineSeparator
1843         // delete the LineSeparator.
1844         // MISSING
1845
1846         // If old_cursor.pos() == 1 and old_cursor.pos()(0) == LineSeparator
1847         // delete the LineSeparator.
1848         // MISSING
1849
1850         // If the pos around the old_cursor were spaces, delete one of them.
1851         if (old_cursor.par() != cursor.par()
1852             || old_cursor.pos() != cursor.pos()) {
1853
1854                 // Only if the cursor has really moved
1855                 if (old_cursor.pos() > 0
1856                     && old_cursor.pos() < old_pit->size()
1857                     && old_pit->isLineSeparator(old_cursor.pos())
1858                     && old_pit->isLineSeparator(old_cursor.pos() - 1)) {
1859                         bool erased = old_pit->erase(old_cursor.pos() - 1);
1860                         redoParagraph(old_pit);
1861
1862                         if (!erased)
1863                                 return false;
1864 #ifdef WITH_WARNINGS
1865 #warning This will not work anymore when we have multiple views of the same buffer
1866 // In this case, we will have to correct also the cursors held by
1867 // other bufferviews. It will probably be easier to do that in a more
1868 // automated way in LyXCursor code. (JMarc 26/09/2001)
1869 #endif
1870                         // correct all cursors held by the LyXText
1871                         fixCursorAfterDelete(cursor, old_cursor);
1872                         fixCursorAfterDelete(selection.cursor, old_cursor);
1873                         fixCursorAfterDelete(selection.start, old_cursor);
1874                         fixCursorAfterDelete(selection.end, old_cursor);
1875                         return false;
1876                 }
1877         }
1878
1879         // don't delete anything if this is the ONLY paragraph!
1880         if (ownerParagraphs().size() == 1)
1881                 return false;
1882
1883         // Do not delete empty paragraphs with keepempty set.
1884         if (old_pit->allowEmpty())
1885                 return false;
1886
1887         // only do our magic if we changed paragraph
1888         if (old_cursor.par() == cursor.par())
1889                 return false;
1890
1891         // record if we have deleted a paragraph
1892         // we can't possibly have deleted a paragraph before this point
1893         bool deleted = false;
1894
1895         if (old_pit->empty()
1896             || (old_pit->size() == 1 && old_pit->isLineSeparator(0))) {
1897                 // ok, we will delete something
1898                 LyXCursor tmpcursor;
1899
1900                 deleted = true;
1901
1902                 bool selection_position_was_oldcursor_position =
1903                         selection.cursor.par() == old_cursor.par()
1904                         && selection.cursor.pos() == old_cursor.pos();
1905
1906                 tmpcursor = cursor;
1907                 cursor = old_cursor; // that undo can restore the right cursor position
1908
1909                 ParagraphList::iterator endpit = boost::next(old_pit);
1910                 while (endpit != ownerParagraphs().end() && endpit->getDepth())
1911                         ++endpit;
1912
1913                 recUndo(parOffset(old_pit), parOffset(endpit) - 1);
1914                 cursor = tmpcursor;
1915
1916                 // cache cursor pit
1917                 ParagraphList::iterator tmppit = cursorPar();
1918                 // delete old par
1919                 ownerParagraphs().erase(old_pit);
1920                 // update cursor par offset
1921                 cursor.par(parOffset(tmppit));
1922                 redoParagraph();
1923
1924                 // correct cursor y
1925                 setCursorIntern(cursor.par(), cursor.pos());
1926
1927                 if (selection_position_was_oldcursor_position) {
1928                         // correct selection
1929                         selection.cursor = cursor;
1930                 }
1931         }
1932
1933         if (deleted)
1934                 return true;
1935
1936         if (old_pit->stripLeadingSpaces()) {
1937                 redoParagraph(old_pit);
1938                 // correct cursor y
1939                 setCursorIntern(cursor.par(), cursor.pos());
1940                 selection.cursor = cursor;
1941         }
1942         return false;
1943 }
1944
1945
1946 ParagraphList & LyXText::ownerParagraphs() const
1947 {
1948         return *paragraphs_;
1949 }
1950
1951
1952 void LyXText::recUndo(paroffset_type first, paroffset_type last) const
1953 {
1954         recordUndo(Undo::ATOMIC, this, first, last);
1955 }
1956
1957
1958 void LyXText::recUndo(lyx::paroffset_type par) const
1959 {
1960         recordUndo(Undo::ATOMIC, this, par, par);
1961 }
1962
1963
1964 bool LyXText::isInInset() const
1965 {
1966         // Sub-level has non-null bv owner and non-null inset owner.
1967         return inset_owner != 0;
1968 }
1969
1970
1971 int defaultRowHeight()
1972 {
1973         LyXFont const font(LyXFont::ALL_SANE);
1974         return int(font_metrics::maxHeight(font) *  1.2);
1975 }