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