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