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