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