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