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