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