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