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