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