]> git.lyx.org Git - lyx.git/blob - src/text2.C
the spellcheck cleanup
[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 "dispatchresult.h"
33 #include "errorlist.h"
34 #include "Floating.h"
35 #include "FloatList.h"
36 #include "funcrequest.h"
37 #include "gettext.h"
38 #include "language.h"
39 #include "lyxrc.h"
40 #include "lyxrow.h"
41 #include "lyxrow_funcs.h"
42 #include "paragraph.h"
43 #include "paragraph_funcs.h"
44 #include "ParagraphParameters.h"
45 #include "undo.h"
46 #include "vspace.h"
47
48 #include "frontends/font_metrics.h"
49 #include "frontends/LyXView.h"
50
51 #include "insets/insetbibitem.h"
52 #include "insets/insetenv.h"
53 #include "insets/insetfloat.h"
54 #include "insets/insetwrap.h"
55
56 #include "support/lstrings.h"
57 #include "support/textutils.h"
58 #include "support/tostr.h"
59 #include "support/std_sstream.h"
60
61 #include <boost/tuple/tuple.hpp>
62
63 using lyx::pos_type;
64 using lyx::paroffset_type;
65 using lyx::support::bformat;
66
67 using std::endl;
68 using std::ostringstream;
69 using std::string;
70
71
72 LyXText::LyXText(BufferView * bv, InsetText * inset, bool ininset,
73           ParagraphList & paragraphs)
74         : height(0), width(0),
75           inset_owner(inset), the_locking_inset(0), bv_owner(bv),
76           in_inset_(ininset), paragraphs_(&paragraphs),
77                 cache_pos_(-1)
78 {
79 }
80
81
82 void LyXText::init(BufferView * bview)
83 {
84         bv_owner = bview;
85
86         ParagraphList::iterator const beg = ownerParagraphs().begin();
87         ParagraphList::iterator const end = ownerParagraphs().end();
88         for (ParagraphList::iterator pit = beg; pit != end; ++pit)
89                 pit->rows.clear();
90
91         width = 0;
92         height = 0;
93         cache_pos_ = -1;
94
95         current_font = getFont(beg, 0);
96
97         redoParagraphs(beg, end);
98         setCursorIntern(0, 0);
99         selection.cursor = cursor;
100
101         updateCounters();
102 }
103
104
105 // Gets the fully instantiated font at a given position in a paragraph
106 // Basically the same routine as Paragraph::getFont() in paragraph.C.
107 // The difference is that this one is used for displaying, and thus we
108 // are allowed to make cosmetic improvements. For instance make footnotes
109 // smaller. (Asger)
110 LyXFont LyXText::getFont(ParagraphList::iterator pit, pos_type pos) const
111 {
112         BOOST_ASSERT(pos >= 0);
113
114         LyXLayout_ptr const & layout = pit->layout();
115 #warning broken?
116         BufferParams const & params = bv()->buffer()->params();
117         pos_type const body_pos = pit->beginningOfBody();
118
119         // We specialize the 95% common case:
120         if (!pit->getDepth()) {
121                 LyXFont f = pit->getFontSettings(params, pos);
122                 if (pit->inInset())
123                         pit->inInset()->getDrawFont(f);
124                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos)
125                         return f.realize(layout->reslabelfont);
126                 else
127                         return f.realize(layout->resfont);
128         }
129
130         // The uncommon case need not be optimized as much
131         LyXFont layoutfont;
132         if (pos < body_pos)
133                 layoutfont = layout->labelfont;
134         else
135                 layoutfont = layout->font;
136
137         LyXFont font = pit->getFontSettings(params, pos);
138         font.realize(layoutfont);
139
140         if (pit->inInset())
141                 pit->inInset()->getDrawFont(font);
142
143         // Realize with the fonts of lesser depth.
144         //font.realize(outerFont(pit, ownerParagraphs()));
145         font.realize(defaultfont_);
146
147         return font;
148 }
149
150
151 LyXFont LyXText::getLayoutFont(ParagraphList::iterator pit) const
152 {
153         LyXLayout_ptr const & layout = pit->layout();
154
155         if (!pit->getDepth())
156                 return layout->resfont;
157
158         LyXFont font = layout->font;
159         // Realize with the fonts of lesser depth.
160         //font.realize(outerFont(pit, ownerParagraphs()));
161         font.realize(defaultfont_);
162
163         return font;
164 }
165
166
167 LyXFont LyXText::getLabelFont(ParagraphList::iterator pit) const
168 {
169         LyXLayout_ptr const & layout = pit->layout();
170
171         if (!pit->getDepth())
172                 return layout->reslabelfont;
173
174         LyXFont font = layout->labelfont;
175         // Realize with the fonts of lesser depth.
176         font.realize(outerFont(pit, ownerParagraphs()));
177         font.realize(defaultfont_);
178
179         return font;
180 }
181
182
183 void LyXText::setCharFont(ParagraphList::iterator pit,
184                           pos_type pos, LyXFont const & fnt,
185                           bool toggleall)
186 {
187         BufferParams const & params = bv()->buffer()->params();
188         LyXFont font = getFont(pit, pos);
189         font.update(fnt, params.language, toggleall);
190         // Let the insets convert their font
191         if (pit->isInset(pos)) {
192                 InsetOld * inset = pit->getInset(pos);
193                 if (isEditableInset(inset)) {
194                         static_cast<UpdatableInset *>(inset)
195                                 ->setFont(bv(), fnt, toggleall, true);
196                 }
197         }
198
199         // Plug through to version below:
200         setCharFont(pit, pos, font);
201 }
202
203
204 void LyXText::setCharFont(
205         ParagraphList::iterator pit, pos_type pos, LyXFont const & fnt)
206 {
207         LyXFont font = fnt;
208         LyXLayout_ptr const & layout = pit->layout();
209
210         // Get concrete layout font to reduce against
211         LyXFont layoutfont;
212
213         if (pos < pit->beginningOfBody())
214                 layoutfont = layout->labelfont;
215         else
216                 layoutfont = layout->font;
217
218         // Realize against environment font information
219         if (pit->getDepth()) {
220                 ParagraphList::iterator tp = pit;
221                 while (!layoutfont.resolved() &&
222                        tp != ownerParagraphs().end() &&
223                        tp->getDepth()) {
224                         tp = outerHook(tp, ownerParagraphs());
225                         if (tp != ownerParagraphs().end())
226                                 layoutfont.realize(tp->layout()->font);
227                 }
228         }
229
230         layoutfont.realize(defaultfont_);
231
232         // Now, reduce font against full layout font
233         font.reduce(layoutfont);
234
235         pit->setFont(pos, font);
236 }
237
238
239 InsetOld * LyXText::getInset() const
240 {
241         ParagraphList::iterator pit = cursorPar();
242         pos_type const pos = cursor.pos();
243
244         if (pos < pit->size() && pit->isInset(pos)) {
245                 return pit->getInset(pos);
246         }
247         return 0;
248 }
249
250
251 void LyXText::toggleInset()
252 {
253         InsetOld * inset = getInset();
254         // is there an editable inset at cursor position?
255         if (!isEditableInset(inset)) {
256                 // No, try to see if we are inside a collapsable inset
257                 if (inset_owner && inset_owner->owner()
258                     && inset_owner->owner()->isOpen()) {
259                         bv()->unlockInset(inset_owner->owner());
260                         inset_owner->owner()->close(bv());
261                         bv()->getLyXText()->cursorRight(bv());
262                 }
263                 return;
264         }
265         //bv()->owner()->message(inset->editMessage());
266
267         // do we want to keep this?? (JMarc)
268         if (!isHighlyEditableInset(inset))
269                 recUndo(cursor.par());
270
271         if (inset->isOpen())
272                 inset->close(bv());
273         else
274                 inset->open(bv());
275
276         bv()->updateInset(inset);
277 }
278
279
280 /* used in setlayout */
281 // Asger is not sure we want to do this...
282 void LyXText::makeFontEntriesLayoutSpecific(BufferParams const & params,
283                                             Paragraph & par)
284 {
285         LyXLayout_ptr const & layout = par.layout();
286         pos_type const psize = par.size();
287
288         LyXFont layoutfont;
289         for (pos_type pos = 0; pos < psize; ++pos) {
290                 if (pos < par.beginningOfBody())
291                         layoutfont = layout->labelfont;
292                 else
293                         layoutfont = layout->font;
294
295                 LyXFont tmpfont = par.getFontSettings(params, pos);
296                 tmpfont.reduce(layoutfont);
297                 par.setFont(pos, tmpfont);
298         }
299 }
300
301
302 ParagraphList::iterator
303 LyXText::setLayout(LyXCursor & cur, LyXCursor & sstart_cur,
304                    LyXCursor & send_cur,
305                    string const & layout)
306 {
307         ParagraphList::iterator endpit = boost::next(getPar(send_cur));
308         ParagraphList::iterator undoendpit = endpit;
309         ParagraphList::iterator pars_end = ownerParagraphs().end();
310
311         if (endpit != pars_end && endpit->getDepth()) {
312                 while (endpit != pars_end && endpit->getDepth()) {
313                         ++endpit;
314                         undoendpit = endpit;
315                 }
316         } else if (endpit != pars_end) {
317                 // because of parindents etc.
318                 ++endpit;
319         }
320
321         recUndo(sstart_cur.par(), parOffset(undoendpit) - 1);
322
323         // ok we have a selection. This is always between sstart_cur
324         // and sel_end cursor
325         cur = sstart_cur;
326         ParagraphList::iterator pit = getPar(sstart_cur);
327         ParagraphList::iterator epit = boost::next(getPar(send_cur));
328
329         BufferParams const & bufparams = bv()->buffer()->params();
330         LyXLayout_ptr const & lyxlayout =
331                 bufparams.getLyXTextClass()[layout];
332
333         do {
334                 pit->applyLayout(lyxlayout);
335                 makeFontEntriesLayoutSpecific(bufparams, *pit);
336                 pit->params().spaceTop(lyxlayout->fill_top ?
337                                          VSpace(VSpace::VFILL)
338                                          : VSpace(VSpace::NONE));
339                 pit->params().spaceBottom(lyxlayout->fill_bottom ?
340                                             VSpace(VSpace::VFILL)
341                                             : VSpace(VSpace::NONE));
342                 if (lyxlayout->margintype == MARGIN_MANUAL)
343                         pit->setLabelWidthString(lyxlayout->labelstring());
344                 cur.par(std::distance(ownerParagraphs().begin(), pit));
345                 ++pit;
346         } while (pit != epit);
347
348         return endpit;
349 }
350
351
352 // set layout over selection and make a total rebreak of those paragraphs
353 void LyXText::setLayout(string const & layout)
354 {
355         LyXCursor tmpcursor = cursor;  // store the current cursor
356
357         // if there is no selection just set the layout
358         // of the current paragraph
359         if (!selection.set()) {
360                 selection.start = cursor;  // dummy selection
361                 selection.end = cursor;
362         }
363
364         // special handling of new environment insets
365         BufferParams const & params = bv()->buffer()->params();
366         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
367         if (lyxlayout->is_environment) {
368                 // move everything in a new environment inset
369                 lyxerr << "setting layout " << layout << endl;
370                 bv()->owner()->dispatch(FuncRequest(LFUN_HOME));
371                 bv()->owner()->dispatch(FuncRequest(LFUN_ENDSEL));
372                 bv()->owner()->dispatch(FuncRequest(LFUN_CUT));
373                 InsetOld * inset = new InsetEnvironment(params, layout);
374                 if (bv()->insertInset(inset)) {
375                         //inset->edit(bv());
376                         //bv()->owner()->dispatch(FuncRequest(LFUN_PASTE));
377                 }
378                 else
379                         delete inset;
380                 return;
381         }
382
383         ParagraphList::iterator endpit = setLayout(cursor, selection.start,
384                                                    selection.end, layout);
385         redoParagraphs(getPar(selection.start), endpit);
386
387         // we have to reset the selection, because the
388         // geometry could have changed
389         setCursor(selection.start.par(), selection.start.pos(), false);
390         selection.cursor = cursor;
391         setCursor(selection.end.par(), selection.end.pos(), false);
392         updateCounters();
393         clearSelection();
394         setSelection();
395         setCursor(tmpcursor.par(), tmpcursor.pos(), true);
396 }
397
398
399 bool LyXText::changeDepth(bv_funcs::DEPTH_CHANGE type, bool test_only)
400 {
401         ParagraphList::iterator pit = cursorPar();
402         ParagraphList::iterator end = cursorPar();
403         ParagraphList::iterator start = pit;
404
405         if (selection.set()) {
406                 pit = getPar(selection.start);
407                 end = getPar(selection.end);
408                 start = pit;
409         }
410
411         ParagraphList::iterator pastend = boost::next(end);
412
413         if (!test_only)
414                 recUndo(parOffset(start), parOffset(end));
415
416         bool changed = false;
417
418         int prev_after_depth = 0;
419 #warning parlist ... could be nicer ?
420         if (start != ownerParagraphs().begin()) {
421                 prev_after_depth = boost::prior(start)->getMaxDepthAfter();
422         }
423
424         while (true) {
425                 int const depth = pit->params().depth();
426                 if (type == bv_funcs::INC_DEPTH) {
427                         if (depth < prev_after_depth
428                             && pit->layout()->labeltype != LABEL_BIBLIO) {
429                                 changed = true;
430                                 if (!test_only)
431                                         pit->params().depth(depth + 1);
432                         }
433                 } else if (depth) {
434                         changed = true;
435                         if (!test_only)
436                                 pit->params().depth(depth - 1);
437                 }
438
439                 prev_after_depth = pit->getMaxDepthAfter();
440
441                 if (pit == end) {
442                         break;
443                 }
444
445                 ++pit;
446         }
447
448         if (test_only)
449                 return changed;
450
451         redoParagraphs(start, pastend);
452
453         // We need to actually move the text->cursor. I don't
454         // understand why ...
455         LyXCursor tmpcursor = cursor;
456
457         // we have to reset the visual selection because the
458         // geometry could have changed
459         if (selection.set()) {
460                 setCursor(selection.start.par(), selection.start.pos());
461                 selection.cursor = cursor;
462                 setCursor(selection.end.par(), selection.end.pos());
463         }
464
465         // this handles the counter labels, and also fixes up
466         // depth values for follow-on (child) paragraphs
467         updateCounters();
468
469         setSelection();
470         setCursor(tmpcursor.par(), tmpcursor.pos());
471
472         return changed;
473 }
474
475
476 // set font over selection and make a total rebreak of those paragraphs
477 void LyXText::setFont(LyXFont const & font, bool toggleall)
478 {
479         // if there is no selection just set the current_font
480         if (!selection.set()) {
481                 // Determine basis font
482                 LyXFont layoutfont;
483                 if (cursor.pos() < cursorPar()->beginningOfBody()) {
484                         layoutfont = getLabelFont(cursorPar());
485                 } else {
486                         layoutfont = getLayoutFont(cursorPar());
487                 }
488                 // Update current font
489                 real_current_font.update(font,
490                                          bv()->buffer()->params().language,
491                                          toggleall);
492
493                 // Reduce to implicit settings
494                 current_font = real_current_font;
495                 current_font.reduce(layoutfont);
496                 // And resolve it completely
497                 real_current_font.realize(layoutfont);
498
499                 return;
500         }
501
502         LyXCursor tmpcursor = cursor; // store the current cursor
503
504         // ok we have a selection. This is always between sel_start_cursor
505         // and sel_end cursor
506
507         recUndo(selection.start.par(), selection.end.par());
508         freezeUndo();
509         cursor = selection.start;
510         while (cursor.par() != selection.end.par() ||
511                cursor.pos() < selection.end.pos())
512         {
513                 if (cursor.pos() < cursorPar()->size()) {
514                         // an open footnote should behave like a closed one
515                         setCharFont(cursorPar(), cursor.pos(), font, toggleall);
516                         cursor.pos(cursor.pos() + 1);
517                 } else {
518                         cursor.pos(0);
519                         cursor.par(cursor.par() + 1);
520                 }
521         }
522         unFreezeUndo();
523
524         redoParagraph(getPar(selection.start));
525
526         // we have to reset the selection, because the
527         // geometry could have changed, but we keep
528         // it for user convenience
529         setCursor(selection.start.par(), selection.start.pos());
530         selection.cursor = cursor;
531         setCursor(selection.end.par(), selection.end.pos());
532         setSelection();
533         setCursor(tmpcursor.par(), tmpcursor.pos(), true,
534                   tmpcursor.boundary());
535 }
536
537
538 // important for the screen
539
540
541 // the cursor set functions have a special mechanism. When they
542 // realize, that you left an empty paragraph, they will delete it.
543
544 // need the selection cursor:
545 void LyXText::setSelection()
546 {
547         TextCursor::setSelection();
548 }
549
550
551
552 void LyXText::clearSelection()
553 {
554         TextCursor::clearSelection();
555
556         // reset this in the bv_owner!
557         if (bv_owner && bv_owner->text)
558                 bv_owner->text->xsel_cache.set(false);
559 }
560
561
562 void LyXText::cursorHome()
563 {
564         ParagraphList::iterator cpit = cursorPar();
565         setCursor(cpit, cpit->getRow(cursor.pos())->pos());
566 }
567
568
569 void LyXText::cursorEnd()
570 {
571         ParagraphList::iterator cpit = cursorPar();
572         pos_type end = cpit->getRow(cursor.pos())->endpos();
573         /* if not on the last row of the par, put the cursor before
574           the final space */
575         setCursor(cpit, end == cpit->size() ? end : end - 1);
576 }
577
578
579 void LyXText::cursorTop()
580 {
581         setCursor(ownerParagraphs().begin(), 0);
582 }
583
584
585 void LyXText::cursorBottom()
586 {
587         ParagraphList::iterator lastpit =
588                 boost::prior(ownerParagraphs().end());
589         setCursor(lastpit, lastpit->size());
590 }
591
592
593 void LyXText::toggleFree(LyXFont const & font, bool toggleall)
594 {
595         // If the mask is completely neutral, tell user
596         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
597                 // Could only happen with user style
598                 bv()->owner()->message(_("No font change defined. Use Character under the Layout menu to define font change."));
599                 return;
600         }
601
602         // Try implicit word selection
603         // If there is a change in the language the implicit word selection
604         // is disabled.
605         LyXCursor resetCursor = cursor;
606         bool implicitSelection =
607                 font.language() == ignore_language
608                 && font.number() == LyXFont::IGNORE
609                 && selectWordWhenUnderCursor(lyx::WHOLE_WORD_STRICT);
610
611         // Set font
612         setFont(font, toggleall);
613
614         // Implicit selections are cleared afterwards
615         //and cursor is set to the original position.
616         if (implicitSelection) {
617                 clearSelection();
618                 cursor = resetCursor;
619                 setCursor(cursorPar(), cursor.pos());
620                 selection.cursor = cursor;
621         }
622 }
623
624
625 string LyXText::getStringToIndex()
626 {
627         // Try implicit word selection
628         // If there is a change in the language the implicit word selection
629         // is disabled.
630         LyXCursor const reset_cursor = cursor;
631         bool const implicitSelection =
632                 selectWordWhenUnderCursor(lyx::PREVIOUS_WORD);
633
634         string idxstring;
635         if (!selection.set())
636                 bv()->owner()->message(_("Nothing to index!"));
637         else if (selection.start.par() != selection.end.par())
638                 bv()->owner()->message(_("Cannot index more than one paragraph!"));
639         else
640                 idxstring = selectionAsString(*bv()->buffer(), false);
641
642         // Reset cursors to their original position.
643         cursor = reset_cursor;
644         setCursor(cursorPar(), cursor.pos());
645         selection.cursor = cursor;
646
647         // Clear the implicit selection.
648         if (implicitSelection)
649                 clearSelection();
650
651         return idxstring;
652 }
653
654
655 // the DTP switches for paragraphs. LyX will store them in the first
656 // physical paragraph. When a paragraph is broken, the top settings rest,
657 // the bottom settings are given to the new one. So I can make sure,
658 // they do not duplicate themself and you cannnot make dirty things with
659 // them!
660
661 void LyXText::setParagraph(
662                            VSpace const & space_top,
663                            VSpace const & space_bottom,
664                            Spacing const & spacing,
665                            LyXAlignment align,
666                            string const & labelwidthstring,
667                            bool noindent)
668 {
669         LyXCursor tmpcursor = cursor;
670         if (!selection.set()) {
671                 selection.start = cursor;
672                 selection.end = cursor;
673         }
674
675         // make sure that the depth behind the selection are restored, too
676         ParagraphList::iterator endpit = boost::next(getPar(selection.end));
677         ParagraphList::iterator undoendpit = endpit;
678         ParagraphList::iterator pars_end = ownerParagraphs().end();
679
680         if (endpit != pars_end && endpit->getDepth()) {
681                 while (endpit != pars_end && endpit->getDepth()) {
682                         ++endpit;
683                         undoendpit = endpit;
684                 }
685         } else if (endpit != pars_end) {
686                 // because of parindents etc.
687                 ++endpit;
688         }
689
690         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
691
692
693         int tmppit = selection.end.par();
694
695         while (tmppit != selection.start.par() - 1) {
696                 setCursor(tmppit, 0);
697
698                 ParagraphList::iterator const pit = cursorPar();
699                 ParagraphParameters & params = pit->params();
700                 params.spaceTop(space_top);
701                 params.spaceBottom(space_bottom);
702                 params.spacing(spacing);
703
704                 // does the layout allow the new alignment?
705                 LyXLayout_ptr const & layout = pit->layout();
706
707                 if (align == LYX_ALIGN_LAYOUT)
708                         align = layout->align;
709                 if (align & layout->alignpossible) {
710                         if (align == layout->align)
711                                 params.align(LYX_ALIGN_LAYOUT);
712                         else
713                                 params.align(align);
714                 }
715                 pit->setLabelWidthString(labelwidthstring);
716                 params.noindent(noindent);
717                 --tmppit;
718         }
719
720         redoParagraphs(getPar(selection.start), endpit);
721
722         clearSelection();
723         setCursor(selection.start.par(), selection.start.pos());
724         selection.cursor = cursor;
725         setCursor(selection.end.par(), selection.end.pos());
726         setSelection();
727         setCursor(tmpcursor.par(), tmpcursor.pos());
728         if (inset_owner)
729                 bv()->updateInset(inset_owner);
730 }
731
732
733 namespace {
734
735 string expandLabel(LyXTextClass const & textclass,
736         LyXLayout_ptr const & layout, bool appendix)
737 {
738         string fmt = appendix ?
739                 layout->labelstring_appendix() : layout->labelstring();
740
741         // handle 'inherited level parts' in 'fmt',
742         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
743         size_t const i = fmt.find('@', 0);
744         if (i != string::npos) {
745                 size_t const j = fmt.find('@', i + 1);
746                 if (j != string::npos) {
747                         string parent(fmt, i + 1, j - i - 1);
748                         string label = expandLabel(textclass, textclass[parent], appendix);
749                         fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
750                 }
751         }
752
753         return textclass.counters().counterLabel(fmt);
754 }
755
756
757 void incrementItemDepth(ParagraphList::iterator pit,
758                         ParagraphList::iterator first_pit)
759 {
760         int const cur_labeltype = pit->layout()->labeltype;
761
762         if (cur_labeltype != LABEL_ENUMERATE &&
763             cur_labeltype != LABEL_ITEMIZE)
764                 return;
765
766         int const cur_depth = pit->getDepth();
767
768         ParagraphList::iterator prev_pit = boost::prior(pit);
769         while (true) {
770                 int const prev_depth = prev_pit->getDepth();
771                 int const prev_labeltype = prev_pit->layout()->labeltype;
772                 if (prev_depth == 0 && cur_depth > 0) {
773                         if (prev_labeltype == cur_labeltype) {
774                                 pit->itemdepth = prev_pit->itemdepth + 1;
775                         }
776                         break;
777                 } else if (prev_depth < cur_depth) {
778                         if (prev_labeltype == cur_labeltype) {
779                                 pit->itemdepth = prev_pit->itemdepth + 1;
780                                 break;
781                         }
782                 } else if (prev_depth == cur_depth) {
783                         if (prev_labeltype == cur_labeltype) {
784                                 pit->itemdepth = prev_pit->itemdepth;
785                                 break;
786                         }
787                 }
788                 if (prev_pit == first_pit)
789                         break;
790
791                 --prev_pit;
792         }
793 }
794
795
796 void resetEnumCounterIfNeeded(ParagraphList::iterator pit,
797                               ParagraphList::iterator firstpit,
798                               Counters & counters)
799 {
800         if (pit == firstpit)
801                 return;
802
803         int const cur_depth = pit->getDepth();
804         ParagraphList::iterator prev_pit = boost::prior(pit);
805         while (true) {
806                 int const prev_depth = prev_pit->getDepth();
807                 int const prev_labeltype = prev_pit->layout()->labeltype;
808                 if (prev_depth <= cur_depth) {
809                         if (prev_labeltype != LABEL_ENUMERATE) {
810                                 switch (pit->itemdepth) {
811                                 case 0:
812                                         counters.reset("enumi");
813                                 case 1:
814                                         counters.reset("enumii");
815                                 case 2:
816                                         counters.reset("enumiii");
817                                 case 3:
818                                         counters.reset("enumiv");
819                                 }
820                         }
821                         break;
822                 }
823
824                 if (prev_pit == firstpit)
825                         break;
826
827                 --prev_pit;
828         }
829 }
830
831 } // anon namespace
832
833
834 // set the counter of a paragraph. This includes the labels
835 void LyXText::setCounter(Buffer const & buf, ParagraphList::iterator pit)
836 {
837         BufferParams const & bufparams = buf.params();
838         LyXTextClass const & textclass = bufparams.getLyXTextClass();
839         LyXLayout_ptr const & layout = pit->layout();
840         ParagraphList::iterator first_pit = ownerParagraphs().begin();
841         Counters & counters = textclass.counters();
842
843         // Always reset
844         pit->itemdepth = 0;
845
846         if (pit == first_pit) {
847                 pit->params().appendix(pit->params().startOfAppendix());
848         } else {
849                 pit->params().appendix(boost::prior(pit)->params().appendix());
850                 if (!pit->params().appendix() &&
851                     pit->params().startOfAppendix()) {
852                         pit->params().appendix(true);
853                         textclass.counters().reset();
854                 }
855
856                 // Maybe we have to increment the item depth.
857                 incrementItemDepth(pit, first_pit);
858         }
859
860         // erase what was there before
861         pit->params().labelString(string());
862
863         if (layout->margintype == MARGIN_MANUAL) {
864                 if (pit->params().labelWidthString().empty())
865                         pit->setLabelWidthString(layout->labelstring());
866         } else {
867                 pit->setLabelWidthString(string());
868         }
869
870         // is it a layout that has an automatic label?
871         if (layout->labeltype == LABEL_COUNTER) {
872                 BufferParams const & bufparams = buf.params();
873                 LyXTextClass const & textclass = bufparams.getLyXTextClass();
874                 counters.step(layout->counter);
875                 string label = expandLabel(textclass, layout, pit->params().appendix());
876                 pit->params().labelString(label);
877         } else if (layout->labeltype == LABEL_ITEMIZE) {
878                 // At some point of time we should do something more
879                 // clever here, like:
880                 //   pit->params().labelString(
881                 //    bufparams.user_defined_bullet(pit->itemdepth).getText());
882                 // for now, use a simple hardcoded label
883                 string itemlabel;
884                 switch (pit->itemdepth) {
885                 case 0:
886                         itemlabel = "*";
887                         break;
888                 case 1:
889                         itemlabel = "-";
890                         break;
891                 case 2:
892                         itemlabel = "@";
893                         break;
894                 case 3:
895                         itemlabel = "·";
896                         break;
897                 }
898
899                 pit->params().labelString(itemlabel);
900         } else if (layout->labeltype == LABEL_ENUMERATE) {
901                 // Maybe we have to reset the enumeration counter.
902                 resetEnumCounterIfNeeded(pit, first_pit, counters);
903
904                 // FIXME
905                 // Yes I know this is a really, really! bad solution
906                 // (Lgb)
907                 string enumcounter = "enum";
908
909                 switch (pit->itemdepth) {
910                 case 2:
911                         enumcounter += 'i';
912                 case 1:
913                         enumcounter += 'i';
914                 case 0:
915                         enumcounter += 'i';
916                         break;
917                 case 3:
918                         enumcounter += "iv";
919                         break;
920                 default:
921                         // not a valid enumdepth...
922                         break;
923                 }
924
925                 counters.step(enumcounter);
926
927                 pit->params().labelString(counters.enumLabel(enumcounter));
928         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
929                 counters.step("bibitem");
930                 int number = counters.value("bibitem");
931                 if (pit->bibitem()) {
932                         pit->bibitem()->setCounter(number);
933                         pit->params().labelString(layout->labelstring());
934                 }
935                 // In biblio should't be following counters but...
936         } else {
937                 string s = buf.B_(layout->labelstring());
938
939                 // the caption hack:
940                 if (layout->labeltype == LABEL_SENSITIVE) {
941                         ParagraphList::iterator end = ownerParagraphs().end();
942                         ParagraphList::iterator tmppit = pit;
943                         InsetOld * in = 0;
944                         bool isOK = false;
945                         while (tmppit != end && tmppit->inInset()
946                                // the single '=' is intended below
947                                && (in = tmppit->inInset()->owner()))
948                         {
949                                 if (in->lyxCode() == InsetOld::FLOAT_CODE ||
950                                     in->lyxCode() == InsetOld::WRAP_CODE) {
951                                         isOK = true;
952                                         break;
953                                 } else {
954                                         Paragraph const * owner = &ownerPar(buf, in);
955                                         tmppit = first_pit;
956                                         for ( ; tmppit != end; ++tmppit)
957                                                 if (&*tmppit == owner)
958                                                         break;
959                                 }
960                         }
961
962                         if (isOK) {
963                                 string type;
964
965                                 if (in->lyxCode() == InsetOld::FLOAT_CODE)
966                                         type = static_cast<InsetFloat*>(in)->params().type;
967                                 else if (in->lyxCode() == InsetOld::WRAP_CODE)
968                                         type = static_cast<InsetWrap*>(in)->params().type;
969                                 else
970                                         BOOST_ASSERT(false);
971
972                                 Floating const & fl = textclass.floats().getType(type);
973
974                                 counters.step(fl.type());
975
976                                 // Doesn't work... yet.
977                                 s = bformat(_("%1$s #:"), buf.B_(fl.name()));
978                         } else {
979                                 // par->SetLayout(0);
980                                 // s = layout->labelstring;
981                                 s = _("Senseless: ");
982                         }
983                 }
984                 pit->params().labelString(s);
985
986         }
987 }
988
989
990 // Updates all counters. Paragraphs with changed label string will be rebroken
991 void LyXText::updateCounters()
992 {
993         // start over
994         bv()->buffer()->params().getLyXTextClass().counters().reset();
995
996         ParagraphList::iterator beg = ownerParagraphs().begin();
997         ParagraphList::iterator end = ownerParagraphs().end();
998         for (ParagraphList::iterator pit = beg; pit != end; ++pit) {
999                 string const oldLabel = pit->params().labelString();
1000
1001                 size_t maxdepth = 0;
1002                 if (pit != beg)
1003                         maxdepth = boost::prior(pit)->getMaxDepthAfter();
1004
1005                 if (pit->params().depth() > maxdepth)
1006                         pit->params().depth(maxdepth);
1007
1008                 // setCounter can potentially change the labelString.
1009                 setCounter(*bv()->buffer(), pit);
1010
1011                 string const & newLabel = pit->params().labelString();
1012
1013                 if (oldLabel != newLabel)
1014                         redoParagraph(pit);
1015         }
1016 }
1017
1018
1019 void LyXText::insertInset(InsetOld * inset)
1020 {
1021         if (!cursorPar()->insetAllowed(inset->lyxCode()))
1022                 return;
1023         recUndo(cursor.par());
1024         freezeUndo();
1025         cursorPar()->insertInset(cursor.pos(), inset);
1026         // Just to rebreak and refresh correctly.
1027         // The character will not be inserted a second time
1028         insertChar(Paragraph::META_INSET);
1029         // If we enter a highly editable inset the cursor should be before
1030         // the inset. After an Undo LyX tries to call inset->edit(...)
1031         // and fails if the cursor is behind the inset and getInset
1032         // does not return the inset!
1033         if (isHighlyEditableInset(inset))
1034                 cursorLeft(true);
1035         unFreezeUndo();
1036 }
1037
1038
1039 void LyXText::cutSelection(bool doclear, bool realcut)
1040 {
1041         // Stuff what we got on the clipboard. Even if there is no selection.
1042
1043         // There is a problem with having the stuffing here in that the
1044         // larger the selection the slower LyX will get. This can be
1045         // solved by running the line below only when the selection has
1046         // finished. The solution used currently just works, to make it
1047         // faster we need to be more clever and probably also have more
1048         // calls to stuffClipboard. (Lgb)
1049         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1050
1051         // This doesn't make sense, if there is no selection
1052         if (!selection.set())
1053                 return;
1054
1055         // OK, we have a selection. This is always between selection.start
1056         // and selection.end
1057
1058         // make sure that the depth behind the selection are restored, too
1059         ParagraphList::iterator endpit = boost::next(getPar(selection.end.par()));
1060         ParagraphList::iterator undoendpit = endpit;
1061         ParagraphList::iterator pars_end = ownerParagraphs().end();
1062
1063         if (endpit != pars_end && endpit->getDepth()) {
1064                 while (endpit != pars_end && endpit->getDepth()) {
1065                         ++endpit;
1066                         undoendpit = endpit;
1067                 }
1068         } else if (endpit != pars_end) {
1069                 // because of parindents etc.
1070                 ++endpit;
1071         }
1072
1073         recUndo(selection.start.par(), parOffset(undoendpit) - 1);
1074
1075         endpit = getPar(selection.end.par());
1076         int endpos = selection.end.pos();
1077
1078         BufferParams const & bufparams = bv()->buffer()->params();
1079         boost::tie(endpit, endpos) = realcut ?
1080                 CutAndPaste::cutSelection(bufparams,
1081                                           ownerParagraphs(),
1082                                           getPar(selection.start.par()), endpit,
1083                                           selection.start.pos(), endpos,
1084                                           bufparams.textclass,
1085                                           doclear)
1086                 : CutAndPaste::eraseSelection(bufparams,
1087                                               ownerParagraphs(),
1088                                               getPar(selection.start.par()), endpit,
1089                                               selection.start.pos(), endpos,
1090                                               doclear);
1091         // sometimes necessary
1092         if (doclear)
1093                 getPar(selection.start.par())->stripLeadingSpaces();
1094
1095         redoParagraphs(getPar(selection.start.par()), boost::next(endpit));
1096         // cutSelection can invalidate the cursor so we need to set
1097         // it anew. (Lgb)
1098         // we prefer the end for when tracking changes
1099         cursor.pos(endpos);
1100         cursor.par(parOffset(endpit));
1101
1102         // need a valid cursor. (Lgb)
1103         clearSelection();
1104
1105         setCursor(cursorPar(), cursor.pos());
1106         selection.cursor = cursor;
1107         updateCounters();
1108 }
1109
1110
1111 void LyXText::copySelection()
1112 {
1113         // stuff the selection onto the X clipboard, from an explicit copy request
1114         bv()->stuffClipboard(selectionAsString(*bv()->buffer(), true));
1115
1116         // this doesnt make sense, if there is no selection
1117         if (!selection.set())
1118                 return;
1119
1120         // ok we have a selection. This is always between selection.start
1121         // and sel_end cursor
1122
1123         // copy behind a space if there is one
1124         while (getPar(selection.start)->size() > selection.start.pos()
1125                && getPar(selection.start)->isLineSeparator(selection.start.pos())
1126                && (selection.start.par() != selection.end.par()
1127                    || selection.start.pos() < selection.end.pos()))
1128                 selection.start.pos(selection.start.pos() + 1);
1129
1130         CutAndPaste::copySelection(getPar(selection.start.par()),
1131                                    getPar(selection.end.par()),
1132                                    selection.start.pos(), selection.end.pos(),
1133                                    bv()->buffer()->params().textclass);
1134 }
1135
1136
1137 void LyXText::pasteSelection(size_t sel_index)
1138 {
1139         // this does not make sense, if there is nothing to paste
1140         if (!CutAndPaste::checkPastePossible())
1141                 return;
1142
1143         recUndo(cursor.par());
1144
1145         ParagraphList::iterator endpit;
1146         PitPosPair ppp;
1147
1148         ErrorList el;
1149
1150         boost::tie(ppp, endpit) =
1151                 CutAndPaste::pasteSelection(*bv()->buffer(),
1152                                             ownerParagraphs(),
1153                                             cursorPar(), cursor.pos(),
1154                                             bv()->buffer()->params().textclass,
1155                                             sel_index, el);
1156         bufferErrors(*bv()->buffer(), el);
1157         bv()->showErrorList(_("Paste"));
1158
1159         redoParagraphs(cursorPar(), endpit);
1160
1161         setCursor(cursor.par(), cursor.pos());
1162         clearSelection();
1163
1164         selection.cursor = cursor;
1165         setCursor(ppp.first, ppp.second);
1166         setSelection();
1167         updateCounters();
1168 }
1169
1170
1171 void LyXText::setSelectionRange(lyx::pos_type length)
1172 {
1173         if (!length)
1174                 return;
1175
1176         selection.cursor = cursor;
1177         while (length--)
1178                 cursorRight(bv());
1179         setSelection();
1180 }
1181
1182
1183 // simple replacing. The font of the first selected character is used
1184 void LyXText::replaceSelectionWithString(string const & str)
1185 {
1186         recUndo(cursor.par());
1187         freezeUndo();
1188
1189         if (!selection.set()) { // create a dummy selection
1190                 selection.end = cursor;
1191                 selection.start = cursor;
1192         }
1193
1194         // Get font setting before we cut
1195         pos_type pos = selection.end.pos();
1196         LyXFont const font = getPar(selection.start)
1197                 ->getFontSettings(bv()->buffer()->params(),
1198                                   selection.start.pos());
1199
1200         // Insert the new string
1201         string::const_iterator cit = str.begin();
1202         string::const_iterator end = str.end();
1203         for (; cit != end; ++cit) {
1204                 getPar(selection.end)->insertChar(pos, (*cit), font);
1205                 ++pos;
1206         }
1207
1208         // Cut the selection
1209         cutSelection(true, false);
1210
1211         unFreezeUndo();
1212 }
1213
1214
1215 // needed to insert the selection
1216 void LyXText::insertStringAsLines(string const & str)
1217 {
1218         ParagraphList::iterator pit = cursorPar();
1219         pos_type pos = cursor.pos();
1220         ParagraphList::iterator endpit = boost::next(cursorPar());
1221
1222         recUndo(cursor.par());
1223
1224         // only to be sure, should not be neccessary
1225         clearSelection();
1226
1227         bv()->buffer()->insertStringAsLines(pit, pos, current_font, str);
1228
1229         redoParagraphs(cursorPar(), endpit);
1230         setCursor(cursorPar(), cursor.pos());
1231         selection.cursor = cursor;
1232         setCursor(pit, pos);
1233         setSelection();
1234 }
1235
1236
1237 // turns double-CR to single CR, others where converted into one
1238 // blank. Then InsertStringAsLines is called
1239 void LyXText::insertStringAsParagraphs(string const & str)
1240 {
1241         string linestr(str);
1242         bool newline_inserted = false;
1243         string::size_type const siz = linestr.length();
1244
1245         for (string::size_type i = 0; i < siz; ++i) {
1246                 if (linestr[i] == '\n') {
1247                         if (newline_inserted) {
1248                                 // we know that \r will be ignored by
1249                                 // InsertStringA. Of course, it is a dirty
1250                                 // trick, but it works...
1251                                 linestr[i - 1] = '\r';
1252                                 linestr[i] = '\n';
1253                         } else {
1254                                 linestr[i] = ' ';
1255                                 newline_inserted = true;
1256                         }
1257                 } else if (IsPrintable(linestr[i])) {
1258                         newline_inserted = false;
1259                 }
1260         }
1261         insertStringAsLines(linestr);
1262 }
1263
1264
1265 void LyXText::setCursor(ParagraphList::iterator pit, pos_type pos)
1266 {
1267         setCursor(parOffset(pit), pos);
1268 }
1269
1270
1271 bool LyXText::setCursor(paroffset_type par, pos_type pos, bool setfont, bool boundary)
1272 {
1273         LyXCursor old_cursor = cursor;
1274         setCursorIntern(par, pos, setfont, boundary);
1275         return deleteEmptyParagraphMechanism(old_cursor);
1276 }
1277
1278
1279 void LyXText::redoCursor()
1280 {
1281 #warning maybe the same for selections?
1282         setCursor(cursor, cursor.par(), cursor.pos(), cursor.boundary());
1283 }
1284
1285
1286 void LyXText::setCursor(LyXCursor & cur, paroffset_type par,
1287         pos_type pos, bool boundary)
1288 {
1289         BOOST_ASSERT(par != int(ownerParagraphs().size()));
1290
1291         cur.par(par);
1292         cur.pos(pos);
1293         cur.boundary(boundary);
1294
1295         // no rows, no fun...
1296         if (ownerParagraphs().begin()->rows.empty())
1297                 return;
1298
1299         // get the cursor y position in text
1300
1301         ParagraphList::iterator pit = getPar(par);
1302         Row const & row = *pit->getRow(pos);
1303         int y = pit->y + row.y_offset();
1304
1305         // y is now the beginning of the cursor row
1306         y += row.baseline();
1307         // y is now the cursor baseline
1308         cur.y(y);
1309
1310         pos_type const end = row.endpos();
1311
1312         // None of these should happen, but we're scaredy-cats
1313         if (pos < 0) {
1314                 lyxerr << "dont like -1" << endl;
1315                 pos = 0;
1316                 cur.pos(0);
1317                 BOOST_ASSERT(false);
1318         } else if (pos > pit->size()) {
1319                 lyxerr << "dont like 1, pos: " << pos
1320                        << " size: " << pit->size()
1321                        << " row.pos():" << row.pos()
1322                        << " paroffset: " << par << endl;
1323                 pos = 0;
1324                 cur.pos(0);
1325                 BOOST_ASSERT(false);
1326         } else if (pos > end) {
1327                 lyxerr << "dont like 2 please report" << endl;
1328                 // This shouldn't happen.
1329                 pos = end;
1330                 cur.pos(pos);
1331                 BOOST_ASSERT(false);
1332         } else if (pos < row.pos()) {
1333                 lyxerr << "dont like 3 please report pos:" << pos
1334                        << " size: " << pit->size()
1335                        << " row.pos():" << row.pos()
1336                        << " paroffset: " << par << endl;
1337                 pos = row.pos();
1338                 cur.pos(pos);
1339                 BOOST_ASSERT(false);
1340         }
1341         // now get the cursors x position
1342         cur.x(int(getCursorX(pit, row, pos, boundary)));
1343         bv()->x_target(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 = bv()->x_target();
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(bv()->x_target(),
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 = bv()->x_target();
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(bv()->x_target(),
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 }