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