]> git.lyx.org Git - lyx.git/blob - src/text2.C
305e1942fcb67ef4f186f97164817ae6f16d1f0f
[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         BufferView & bv = cur.bv();
284         BufferParams const & params = bv.buffer()->params();
285         LyXLayout_ptr const & lyxlayout = params.getLyXTextClass()[layout];
286         if (lyxlayout->is_environment) {
287                 // move everything in a new environment inset
288                 lyxerr[Debug::DEBUG] << "setting layout " << layout << endl;
289                 bv.owner()->dispatch(FuncRequest(LFUN_HOME));
290                 bv.owner()->dispatch(FuncRequest(LFUN_ENDSEL));
291                 bv.owner()->dispatch(FuncRequest(LFUN_CUT));
292                 InsetBase * inset = new InsetEnvironment(params, layout);
293                 insertInset(cur, inset);
294                 //inset->edit(cur, true);
295                 //bv.owner()->dispatch(FuncRequest(LFUN_PASTE));
296                 return;
297         }
298
299         par_type start = cur.selBegin().par();
300         par_type end = cur.selEnd().par() + 1;
301         par_type endpit = setLayout(start, end, layout);
302         redoParagraphs(start, endpit);
303         updateCounters();
304 }
305
306
307 namespace {
308
309
310 void getSelectionSpan(LCursor & cur, par_type & beg, par_type & end)
311 {
312         if (!cur.selection()) {
313                 beg = cur.par();
314                 end = cur.par() + 1;
315         } else {
316                 beg = cur.selBegin().par();
317                 end = cur.selEnd().par() + 1;
318         }
319 }
320
321
322 bool changeDepthAllowed(LyXText::DEPTH_CHANGE type,
323         Paragraph const & par, int max_depth)
324 {
325         if (par.layout()->labeltype == LABEL_BIBLIO)
326                 return false;
327         int const depth = par.params().depth();
328         if (type == LyXText::INC_DEPTH && depth < max_depth)
329                 return true;
330         if (type == LyXText::DEC_DEPTH && depth > 0)
331                 return true;
332         return false;
333 }
334
335
336 }
337
338
339 bool LyXText::changeDepthAllowed(LCursor & cur, DEPTH_CHANGE type) const
340 {
341         BOOST_ASSERT(this == cur.text());
342         par_type beg, end;
343         getSelectionSpan(cur, beg, end);
344         int max_depth = 0;
345         if (beg != 0)
346                 max_depth = pars_[beg - 1].getMaxDepthAfter();
347
348         for (par_type pit = beg; pit != end; ++pit) {
349                 if (::changeDepthAllowed(type, pars_[pit], max_depth))
350                         return true;
351                 max_depth = pars_[pit].getMaxDepthAfter();
352         }
353         return false;
354 }
355
356
357 void LyXText::changeDepth(LCursor & cur, DEPTH_CHANGE type)
358 {
359         BOOST_ASSERT(this == cur.text());
360         par_type beg, end;
361         getSelectionSpan(cur, beg, end);
362         recordUndoSelection(cur);
363
364         int max_depth = 0;
365         if (beg != 0)
366                 max_depth = pars_[beg - 1].getMaxDepthAfter();
367
368         for (par_type pit = beg; pit != end; ++pit) {
369                 if (::changeDepthAllowed(type, pars_[pit], max_depth)) {
370                         int const depth = pars_[pit].params().depth();
371                         if (type == INC_DEPTH)
372                                 pars_[pit].params().depth(depth + 1);
373                         else
374                                 pars_[pit].params().depth(depth - 1);
375                 }
376                 max_depth = pars_[pit].getMaxDepthAfter();
377         }
378         // this handles the counter labels, and also fixes up
379         // depth values for follow-on (child) paragraphs
380         updateCounters();
381 }
382
383
384 // set font over selection
385 void LyXText::setFont(LCursor & cur, LyXFont const & font, bool toggleall)
386 {
387         BOOST_ASSERT(this == cur.text());
388         // if there is no selection just set the current_font
389         if (!cur.selection()) {
390                 // Determine basis font
391                 LyXFont layoutfont;
392                 par_type pit = cur.par();
393                 if (cur.pos() < pars_[pit].beginOfBody())
394                         layoutfont = getLabelFont(pit);
395                 else
396                         layoutfont = getLayoutFont(pit);
397
398                 // Update current font
399                 real_current_font.update(font,
400                                          cur.buffer().params().language,
401                                          toggleall);
402
403                 // Reduce to implicit settings
404                 current_font = real_current_font;
405                 current_font.reduce(layoutfont);
406                 // And resolve it completely
407                 real_current_font.realize(layoutfont);
408
409                 return;
410         }
411
412         // Ok, we have a selection.
413         recordUndoSelection(cur);
414
415         par_type const beg = cur.selBegin().par();
416         par_type const end = cur.selEnd().par();
417
418         DocIterator pos = cur.selectionBegin();
419         DocIterator posend = cur.selectionEnd();
420
421         lyxerr[Debug::DEBUG] << "pos: " << pos << " posend: " << posend
422                              << endl;
423
424         BufferParams const & params = cur.buffer().params();
425
426         // Don't use forwardChar here as posend might have
427         // pos() == lastpos() and forwardChar would miss it.
428         for (; pos != posend; pos.forwardPos()) {
429                 if (pos.pos() != pos.lastpos()) {
430                         LyXFont f = getFont(pos.par(), pos.pos());
431                         f.update(font, params.language, toggleall);
432                         setCharFont(pos.par(), pos.pos(), f);
433                 }
434         }
435
436         redoParagraphs(beg, end + 1);
437 }
438
439
440 // the cursor set functions have a special mechanism. When they
441 // realize you left an empty paragraph, they will delete it.
442
443 void LyXText::cursorHome(LCursor & cur)
444 {
445         BOOST_ASSERT(this == cur.text());
446         setCursor(cur, cur.par(), cur.textRow().pos());
447 }
448
449
450 void LyXText::cursorEnd(LCursor & cur)
451 {
452         BOOST_ASSERT(this == cur.text());
453         // if not on the last row of the par, put the cursor before
454         // the final space
455         pos_type const end = cur.textRow().endpos();
456         setCursor(cur, cur.par(), end == cur.lastpos() ? end : end - 1);
457 }
458
459
460 void LyXText::cursorTop(LCursor & cur)
461 {
462         BOOST_ASSERT(this == cur.text());
463         setCursor(cur, 0, 0);
464 }
465
466
467 void LyXText::cursorBottom(LCursor & cur)
468 {
469         BOOST_ASSERT(this == cur.text());
470         setCursor(cur, cur.lastpar(), boost::prior(paragraphs().end())->size());
471 }
472
473
474 void LyXText::toggleFree(LCursor & cur, LyXFont const & font, bool toggleall)
475 {
476         BOOST_ASSERT(this == cur.text());
477         // If the mask is completely neutral, tell user
478         if (font == LyXFont(LyXFont::ALL_IGNORE)) {
479                 // Could only happen with user style
480                 cur.message(_("No font change defined. "
481                         "Use Character under the Layout menu to define font change."));
482                 return;
483         }
484
485         // Try implicit word selection
486         // If there is a change in the language the implicit word selection
487         // is disabled.
488         CursorSlice resetCursor = cur.top();
489         bool implicitSelection =
490                 font.language() == ignore_language
491                 && font.number() == LyXFont::IGNORE
492                 && selectWordWhenUnderCursor(cur, lyx::WHOLE_WORD_STRICT);
493
494         // Set font
495         setFont(cur, font, toggleall);
496
497         // Implicit selections are cleared afterwards
498         // and cursor is set to the original position.
499         if (implicitSelection) {
500                 cur.clearSelection();
501                 cur.top() = resetCursor;
502                 cur.resetAnchor();
503         }
504 }
505
506
507 string LyXText::getStringToIndex(LCursor & cur)
508 {
509         BOOST_ASSERT(this == cur.text());
510         // Try implicit word selection
511         // If there is a change in the language the implicit word selection
512         // is disabled.
513         CursorSlice const reset_cursor = cur.top();
514         bool const implicitSelection =
515                 selectWordWhenUnderCursor(cur, lyx::PREVIOUS_WORD);
516
517         string idxstring;
518         if (!cur.selection())
519                 cur.message(_("Nothing to index!"));
520         else if (cur.selBegin().par() != cur.selEnd().par())
521                 cur.message(_("Cannot index more than one paragraph!"));
522         else
523                 idxstring = cur.selectionAsString(false);
524
525         // Reset cursors to their original position.
526         cur.top() = reset_cursor;
527         cur.resetAnchor();
528
529         // Clear the implicit selection.
530         if (implicitSelection)
531                 cur.clearSelection();
532
533         return idxstring;
534 }
535
536
537 void LyXText::setParagraph(LCursor & cur,
538         Spacing const & spacing, LyXAlignment align,
539         string const & labelwidthstring, bool noindent)
540 {
541         BOOST_ASSERT(cur.text());
542         // make sure that the depth behind the selection are restored, too
543         par_type undopit = undoSpan(cur.selEnd().par());
544         recUndo(cur.selBegin().par(), undopit - 1);
545
546         for (par_type pit = cur.selBegin().par(), end = cur.selEnd().par();
547                         pit <= end; ++pit) {
548                 Paragraph & par = pars_[pit];
549                 ParagraphParameters & params = par.params();
550                 params.spacing(spacing);
551
552                 // does the layout allow the new alignment?
553                 LyXLayout_ptr const & layout = par.layout();
554
555                 if (align == LYX_ALIGN_LAYOUT)
556                         align = layout->align;
557                 if (align & layout->alignpossible) {
558                         if (align == layout->align)
559                                 params.align(LYX_ALIGN_LAYOUT);
560                         else
561                                 params.align(align);
562                 }
563                 par.setLabelWidthString(labelwidthstring);
564                 params.noindent(noindent);
565         }
566
567         redoParagraphs(cur.selBegin().par(), undopit);
568 }
569
570
571 string expandLabel(LyXTextClass const & textclass,
572         LyXLayout_ptr const & layout, bool appendix)
573 {
574         string fmt = appendix ?
575                 layout->labelstring_appendix() : layout->labelstring();
576
577         // handle 'inherited level parts' in 'fmt',
578         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
579         size_t const i = fmt.find('@', 0);
580         if (i != string::npos) {
581                 size_t const j = fmt.find('@', i + 1);
582                 if (j != string::npos) {
583                         string parent(fmt, i + 1, j - i - 1);
584                         string label = expandLabel(textclass, textclass[parent], appendix);
585                         fmt = string(fmt, 0, i) + label + string(fmt, j + 1, string::npos);
586                 }
587         }
588
589         return textclass.counters().counterLabel(fmt);
590 }
591
592
593 namespace {
594
595 void incrementItemDepth(ParagraphList & pars, par_type pit, par_type first_pit)
596 {
597         int const cur_labeltype = pars[pit].layout()->labeltype;
598
599         if (cur_labeltype != LABEL_ENUMERATE && cur_labeltype != LABEL_ITEMIZE)
600                 return;
601
602         int const cur_depth = pars[pit].getDepth();
603
604         par_type prev_pit = pit - 1;
605         while (true) {
606                 int const prev_depth = pars[prev_pit].getDepth();
607                 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
608                 if (prev_depth == 0 && cur_depth > 0) {
609                         if (prev_labeltype == cur_labeltype) {
610                                 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
611                         }
612                         break;
613                 } else if (prev_depth < cur_depth) {
614                         if (prev_labeltype == cur_labeltype) {
615                                 pars[pit].itemdepth = pars[prev_pit].itemdepth + 1;
616                                 break;
617                         }
618                 } else if (prev_depth == cur_depth) {
619                         if (prev_labeltype == cur_labeltype) {
620                                 pars[pit].itemdepth = pars[prev_pit].itemdepth;
621                                 break;
622                         }
623                 }
624                 if (prev_pit == first_pit)
625                         break;
626
627                 --prev_pit;
628         }
629 }
630
631
632 void resetEnumCounterIfNeeded(ParagraphList & pars, par_type pit,
633         par_type firstpit, Counters & counters)
634 {
635         if (pit == firstpit)
636                 return;
637
638         int const cur_depth = pars[pit].getDepth();
639         par_type prev_pit = pit - 1;
640         while (true) {
641                 int const prev_depth = pars[prev_pit].getDepth();
642                 int const prev_labeltype = pars[prev_pit].layout()->labeltype;
643                 if (prev_depth <= cur_depth) {
644                         if (prev_labeltype != LABEL_ENUMERATE) {
645                                 switch (pars[pit].itemdepth) {
646                                 case 0:
647                                         counters.reset("enumi");
648                                 case 1:
649                                         counters.reset("enumii");
650                                 case 2:
651                                         counters.reset("enumiii");
652                                 case 3:
653                                         counters.reset("enumiv");
654                                 }
655                         }
656                         break;
657                 }
658
659                 if (prev_pit == firstpit)
660                         break;
661
662                 --prev_pit;
663         }
664 }
665
666 } // anon namespace
667
668
669 // set the counter of a paragraph. This includes the labels
670 void LyXText::setCounter(Buffer const & buf, par_type pit)
671 {
672         BufferParams const & bufparams = buf.params();
673         LyXTextClass const & textclass = bufparams.getLyXTextClass();
674         LyXLayout_ptr const & layout = pars_[pit].layout();
675         par_type first_pit = 0;
676         Counters & counters = textclass.counters();
677
678         // Always reset
679         pars_[pit].itemdepth = 0;
680
681         if (pit == first_pit) {
682                 pars_[pit].params().appendix(pars_[pit].params().startOfAppendix());
683         } else {
684                 pars_[pit].params().appendix(pars_[pit - 1].params().appendix());
685                 if (!pars_[pit].params().appendix() &&
686                     pars_[pit].params().startOfAppendix()) {
687                         pars_[pit].params().appendix(true);
688                         textclass.counters().reset();
689                 }
690
691                 // Maybe we have to increment the item depth.
692                 incrementItemDepth(pars_, pit, first_pit);
693         }
694
695         // erase what was there before
696         pars_[pit].params().labelString(string());
697
698         if (layout->margintype == MARGIN_MANUAL) {
699                 if (pars_[pit].params().labelWidthString().empty())
700                         pars_[pit].setLabelWidthString(layout->labelstring());
701         } else {
702                 pars_[pit].setLabelWidthString(string());
703         }
704
705         // is it a layout that has an automatic label?
706         if (layout->labeltype == LABEL_COUNTER) {
707                 BufferParams const & bufparams = buf.params();
708                 LyXTextClass const & textclass = bufparams.getLyXTextClass();
709                 counters.step(layout->counter);
710                 string label = expandLabel(textclass, layout, pars_[pit].params().appendix());
711                 pars_[pit].params().labelString(label);
712         } else if (layout->labeltype == LABEL_ITEMIZE) {
713                 // At some point of time we should do something more
714                 // clever here, like:
715                 //   pars_[pit].params().labelString(
716                 //    bufparams.user_defined_bullet(pars_[pit].itemdepth).getText());
717                 // for now, use a simple hardcoded label
718                 string itemlabel;
719                 switch (pars_[pit].itemdepth) {
720                 case 0:
721                         itemlabel = "*";
722                         break;
723                 case 1:
724                         itemlabel = "-";
725                         break;
726                 case 2:
727                         itemlabel = "@";
728                         break;
729                 case 3:
730                         itemlabel = "·";
731                         break;
732                 }
733
734                 pars_[pit].params().labelString(itemlabel);
735         } else if (layout->labeltype == LABEL_ENUMERATE) {
736                 // Maybe we have to reset the enumeration counter.
737                 resetEnumCounterIfNeeded(pars_, pit, first_pit, counters);
738
739                 // FIXME
740                 // Yes I know this is a really, really! bad solution
741                 // (Lgb)
742                 string enumcounter = "enum";
743
744                 switch (pars_[pit].itemdepth) {
745                 case 2:
746                         enumcounter += 'i';
747                 case 1:
748                         enumcounter += 'i';
749                 case 0:
750                         enumcounter += 'i';
751                         break;
752                 case 3:
753                         enumcounter += "iv";
754                         break;
755                 default:
756                         // not a valid enumdepth...
757                         break;
758                 }
759
760                 counters.step(enumcounter);
761
762                 pars_[pit].params().labelString(counters.enumLabel(enumcounter));
763         } else if (layout->labeltype == LABEL_BIBLIO) {// ale970302
764                 counters.step("bibitem");
765                 int number = counters.value("bibitem");
766                 if (pars_[pit].bibitem()) {
767                         pars_[pit].bibitem()->setCounter(number);
768                         pars_[pit].params().labelString(layout->labelstring());
769                 }
770                 // In biblio should't be following counters but...
771         } else {
772                 string s = buf.B_(layout->labelstring());
773
774                 // the caption hack:
775                 if (layout->labeltype == LABEL_SENSITIVE) {
776                         par_type end = paragraphs().size();
777                         par_type tmppit = pit;
778                         InsetBase * in = 0;
779                         bool isOK = false;
780                         while (tmppit != end) {
781                                 in = pars_[tmppit].inInset();
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[Debug::DEBUG] << "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         cur.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 = cur.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(cur.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[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1130                              << row.pos() << endl;
1131         bool bound = false;
1132         int xx = x + xo_; // getRowNearX get absolute x coords
1133         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1134         setCursor(cur, pit, pos, true, bound);
1135 }
1136
1137
1138 // x,y are absolute screen coordinates
1139 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1140 {
1141         par_type pit;
1142         Row const & row = getRowNearY(y - yo_, pit);
1143         bool bound = false;
1144
1145         int xx = x; // is modified by getColumnNearX
1146         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1147         cur.par() = pit;
1148         cur.pos() = pos;
1149         cur.boundary() = bound;
1150
1151         // try to descend into nested insets
1152         InsetBase * inset = checkInsetHit(x, y);
1153         lyxerr[Debug::DEBUG] << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1154         if (!inset)
1155                 return 0;
1156
1157         // This should be just before or just behind the
1158         // cursor position set above.
1159         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1160                      || inset == pars_[pit].getInset(pos));
1161         // Make sure the cursor points to the position before
1162         // this inset.
1163         if (inset == pars_[pit].getInset(pos - 1))
1164                 --cur.pos();
1165         return inset->editXY(cur, x, y);
1166 }
1167
1168
1169 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1170 {
1171         if (cur.selection())
1172                 return false;
1173         if (cur.pos() == cur.lastpos())
1174                 return false;
1175         InsetBase * inset = cur.nextInset();
1176         if (!isHighlyEditableInset(inset))
1177                 return false;
1178         inset->edit(cur, front);
1179         return true;
1180 }
1181
1182
1183 void LyXText::cursorLeft(LCursor & cur)
1184 {
1185         if (cur.pos() != 0) {
1186                 bool boundary = cur.boundary();
1187                 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1188                 if (!checkAndActivateInset(cur, false)) {
1189                         if (false && !boundary &&
1190                                         bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1191                                 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1192                 }
1193                 return;
1194         }
1195
1196         if (cur.par() != 0) {
1197                 // steps into the paragraph above
1198                 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1199         }
1200 }
1201
1202
1203 void LyXText::cursorRight(LCursor & cur)
1204 {
1205         if (false && cur.boundary()) {
1206                 setCursor(cur, cur.par(), cur.pos(), true, false);
1207                 return;
1208         }
1209
1210         if (cur.pos() != cur.lastpos()) {
1211                 if (!checkAndActivateInset(cur, true)) {
1212                         setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1213                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1214                                                          cur.pos()))
1215                                 setCursor(cur, cur.par(), cur.pos(), true, true);
1216                 }
1217                 return;
1218         }
1219
1220         if (cur.par() != cur.lastpar())
1221                 setCursor(cur, cur.par() + 1, 0);
1222 }
1223
1224
1225 void LyXText::cursorUp(LCursor & cur)
1226 {
1227         Row const & row = cur.textRow();
1228         int x = cur.x_target();
1229         int y = cursorY(cur.top()) - row.baseline() - 1;
1230         setCursorFromCoordinates(cur, x, y);
1231
1232         if (!cur.selection()) {
1233                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1234                 if (inset_hit && isHighlyEditableInset(inset_hit))
1235                         inset_hit->editXY(cur, cur.x_target(), y);
1236         }
1237 }
1238
1239
1240 void LyXText::cursorDown(LCursor & cur)
1241 {
1242         Row const & row = cur.textRow();
1243         int x = cur.x_target();
1244         int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1245         setCursorFromCoordinates(cur, x, y);
1246
1247         if (!cur.selection()) {
1248                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1249                 if (inset_hit && isHighlyEditableInset(inset_hit))
1250                         inset_hit->editXY(cur, cur.x_target(), y);
1251         }
1252 }
1253
1254
1255 void LyXText::cursorUpParagraph(LCursor & cur)
1256 {
1257         if (cur.pos() > 0)
1258                 setCursor(cur, cur.par(), 0);
1259         else if (cur.par() != 0)
1260                 setCursor(cur, cur.par() - 1, 0);
1261 }
1262
1263
1264 void LyXText::cursorDownParagraph(LCursor & cur)
1265 {
1266         if (cur.par() != cur.lastpar())
1267                 setCursor(cur, cur.par() + 1, 0);
1268         else
1269                 setCursor(cur, cur.par(), cur.lastpos());
1270 }
1271
1272
1273 // fix the cursor `cur' after a characters has been deleted at `where'
1274 // position. Called by deleteEmptyParagraphMechanism
1275 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1276 {
1277         // do notheing if cursor is not in the paragraph where the
1278         // deletion occured,
1279         if (cur.par() != where.par())
1280                 return;
1281
1282         // if cursor position is after the deletion place update it
1283         if (cur.pos() > where.pos())
1284                 --cur.pos();
1285
1286         // check also if we don't want to set the cursor on a spot behind the
1287         // pagragraph because we erased the last character.
1288         if (cur.pos() > cur.lastpos())
1289                 cur.pos() = cur.lastpos();
1290 }
1291
1292
1293 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1294 {
1295         BOOST_ASSERT(cur.size() == old.size());
1296         // Would be wrong to delete anything if we have a selection.
1297         if (cur.selection())
1298                 return false;
1299
1300         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1301         Paragraph const & oldpar = pars_[old.par()];
1302
1303         // We allow all kinds of "mumbo-jumbo" when freespacing.
1304         if (oldpar.isFreeSpacing())
1305                 return false;
1306
1307         /* Ok I'll put some comments here about what is missing.
1308            I have fixed BackSpace (and thus Delete) to not delete
1309            double-spaces automagically. I have also changed Cut,
1310            Copy and Paste to hopefully do some sensible things.
1311            There are still some small problems that can lead to
1312            double spaces stored in the document file or space at
1313            the beginning of paragraphs(). This happens if you have
1314            the cursor between to spaces and then save. Or if you
1315            cut and paste and the selection have a space at the
1316            beginning and then save right after the paste. I am
1317            sure none of these are very hard to fix, but I will
1318            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1319            that I can get some feedback. (Lgb)
1320         */
1321
1322         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1323         // delete the LineSeparator.
1324         // MISSING
1325
1326         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1327         // delete the LineSeparator.
1328         // MISSING
1329
1330         // If the chars around the old cursor were spaces, delete one of them.
1331         if (old.par() != cur.par() || old.pos() != cur.pos()) {
1332
1333                 // Only if the cursor has really moved.
1334                 if (old.pos() > 0
1335                     && old.pos() < oldpar.size()
1336                     && oldpar.isLineSeparator(old.pos())
1337                     && oldpar.isLineSeparator(old.pos() - 1)) {
1338                         pars_[old.par()].erase(old.pos() - 1);
1339 #ifdef WITH_WARNINGS
1340 #warning This will not work anymore when we have multiple views of the same buffer
1341 // In this case, we will have to correct also the cursors held by
1342 // other bufferviews. It will probably be easier to do that in a more
1343 // automated way in CursorSlice code. (JMarc 26/09/2001)
1344 #endif
1345                         // correct all cursor parts
1346                         fixCursorAfterDelete(cur.top(), old.top());
1347 #ifdef WITH_WARNINGS
1348 #warning DEPM, look here
1349 #endif
1350                         //fixCursorAfterDelete(cur.anchor(), old.top());
1351                         return false;
1352                 }
1353         }
1354
1355         // only do our magic if we changed paragraph
1356         if (old.par() == cur.par())
1357                 return false;
1358
1359         // don't delete anything if this is the ONLY paragraph!
1360         if (pars_.size() == 1)
1361                 return false;
1362
1363         // Do not delete empty paragraphs with keepempty set.
1364         if (oldpar.allowEmpty())
1365                 return false;
1366
1367         // record if we have deleted a paragraph
1368         // we can't possibly have deleted a paragraph before this point
1369         bool deleted = false;
1370
1371         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1372                 // ok, we will delete something
1373                 CursorSlice tmpcursor;
1374
1375                 deleted = true;
1376
1377                 bool selection_position_was_oldcursor_position =
1378                         cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1379
1380                 // This is a bit of a overkill. We change the old and the cur par
1381                 // at max, certainly not everything in between...
1382                 recUndo(old.par(), cur.par());
1383
1384                 // Delete old par.
1385                 pars_.erase(pars_.begin() + old.par());
1386
1387                 // Update cursor par offset if necessary.
1388                 // Some 'iterator registration' would be nice that takes care of
1389                 // such events. Maybe even signal/slot?
1390                 if (cur.par() > old.par())
1391                         --cur.par();
1392 #ifdef WITH_WARNINGS
1393 #warning DEPM, look here
1394 #endif
1395 //              if (cur.anchor().par() > old.par())
1396 //                      --cur.anchor().par();
1397
1398                 if (selection_position_was_oldcursor_position) {
1399                         // correct selection
1400                         cur.resetAnchor();
1401                 }
1402         }
1403
1404         if (deleted)
1405                 return true;
1406
1407         if (pars_[old.par()].stripLeadingSpaces())
1408                 cur.resetAnchor();
1409
1410         return false;
1411 }
1412
1413
1414 ParagraphList & LyXText::paragraphs() const
1415 {
1416         return const_cast<ParagraphList &>(pars_);
1417 }
1418
1419
1420 void LyXText::recUndo(par_type first, par_type last) const
1421 {
1422         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1423 }
1424
1425
1426 void LyXText::recUndo(par_type par) const
1427 {
1428         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1429 }
1430
1431
1432 int defaultRowHeight()
1433 {
1434         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1435 }