]> git.lyx.org Git - lyx.git/blob - src/text2.C
1988bd3e665937f3b5f07474153a29364318ea0f
[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         setCursor(cur, cur.par(), cur.pos() + 1, false, cur.boundary());
863 }
864
865
866 // needed to insert the selection
867 void LyXText::insertStringAsLines(LCursor & cur, string const & str)
868 {
869         par_type pit = cur.par();
870         par_type endpit = cur.par() + 1;
871         pos_type pos = cur.pos();
872         recordUndo(cur);
873
874         // only to be sure, should not be neccessary
875         cur.clearSelection();
876         cur.buffer().insertStringAsLines(pars_, pit, pos, current_font, str);
877
878         redoParagraphs(cur.par(), endpit);
879         cur.resetAnchor();
880         setCursor(cur, cur.par(), pos);
881         cur.setSelection();
882 }
883
884
885 // turn double CR to single CR, others are converted into one
886 // blank. Then insertStringAsLines is called
887 void LyXText::insertStringAsParagraphs(LCursor & cur, string const & str)
888 {
889         string linestr = str;
890         bool newline_inserted = false;
891
892         for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
893                 if (linestr[i] == '\n') {
894                         if (newline_inserted) {
895                                 // we know that \r will be ignored by
896                                 // insertStringAsLines. Of course, it is a dirty
897                                 // trick, but it works...
898                                 linestr[i - 1] = '\r';
899                                 linestr[i] = '\n';
900                         } else {
901                                 linestr[i] = ' ';
902                                 newline_inserted = true;
903                         }
904                 } else if (IsPrintable(linestr[i])) {
905                         newline_inserted = false;
906                 }
907         }
908         insertStringAsLines(cur, linestr);
909 }
910
911
912 bool LyXText::setCursor(LCursor & cur, par_type par, pos_type pos,
913         bool setfont, bool boundary)
914 {
915         LCursor old = cur;
916         setCursorIntern(cur, par, pos, setfont, boundary);
917         return deleteEmptyParagraphMechanism(cur, old);
918 }
919
920
921 void LyXText::setCursor(CursorSlice & cur, par_type par,
922         pos_type pos, bool boundary)
923 {
924         BOOST_ASSERT(par != int(paragraphs().size()));
925
926         cur.par() = par;
927         cur.pos() = pos;
928         cur.boundary() = boundary;
929
930         // no rows, no fun...
931         if (paragraphs().begin()->rows.empty())
932                 return;
933
934         // now some strict checking
935         Paragraph & para = getPar(par);
936         Row const & row = *para.getRow(pos);
937         pos_type const end = row.endpos();
938
939         // None of these should happen, but we're scaredy-cats
940         if (pos < 0) {
941                 lyxerr << "dont like -1" << endl;
942                 BOOST_ASSERT(false);
943         }
944
945         if (pos > para.size()) {
946                 lyxerr << "dont like 1, pos: " << pos
947                        << " size: " << para.size()
948                        << " row.pos():" << row.pos()
949                        << " par: " << par << endl;
950                 BOOST_ASSERT(false);
951         }
952
953         if (pos > end) {
954                 lyxerr << "dont like 2, pos: " << pos
955                        << " size: " << para.size()
956                        << " row.pos():" << row.pos()
957                        << " par: " << par << endl;
958                 // This shouldn't happen.
959                 BOOST_ASSERT(false);
960         }
961
962         if (pos < row.pos()) {
963                 lyxerr << "dont like 3 please report pos:" << pos
964                        << " size: " << para.size()
965                        << " row.pos():" << row.pos()
966                        << " par: " << par << endl;
967                 BOOST_ASSERT(false);
968         }
969 }
970
971
972 void LyXText::setCursorIntern(LCursor & cur,
973         par_type par, pos_type pos, bool setfont, bool boundary)
974 {
975         setCursor(cur.top(), par, pos, boundary);
976         cur.x_target() = cursorX(cur.top());
977         if (setfont)
978                 setCurrentFont(cur);
979 }
980
981
982 void LyXText::setCurrentFont(LCursor & cur)
983 {
984         BOOST_ASSERT(this == cur.text());
985         pos_type pos = cur.pos();
986         par_type pit = cur.par();
987
988         if (cur.boundary() && pos > 0)
989                 --pos;
990
991         if (pos > 0) {
992                 if (pos == cur.lastpos())
993                         --pos;
994                 else // potentional bug... BUG (Lgb)
995                         if (pars_[pit].isSeparator(pos)) {
996                                 if (pos > cur.textRow().pos() &&
997                                     bidi.level(pos) % 2 ==
998                                     bidi.level(pos - 1) % 2)
999                                         --pos;
1000                                 else if (pos + 1 < cur.lastpos())
1001                                         ++pos;
1002                         }
1003         }
1004
1005         BufferParams const & bufparams = cur.buffer().params();
1006         current_font = pars_[pit].getFontSettings(bufparams, pos);
1007         real_current_font = getFont(pit, pos);
1008
1009         if (cur.pos() == cur.lastpos()
1010             && bidi.isBoundary(cur.buffer(), pars_[pit], cur.pos())
1011             && !cur.boundary()) {
1012                 Language const * lang = pars_[pit].getParLanguage(bufparams);
1013                 current_font.setLanguage(lang);
1014                 current_font.setNumber(LyXFont::OFF);
1015                 real_current_font.setLanguage(lang);
1016                 real_current_font.setNumber(LyXFont::OFF);
1017         }
1018 }
1019
1020
1021 // x is an absolute screen coord
1022 // returns the column near the specified x-coordinate of the row
1023 // x is set to the real beginning of this column
1024 pos_type LyXText::getColumnNearX(par_type pit,
1025         Row const & row, int & x, bool & boundary) const
1026 {
1027         x -= xo_;
1028         RowMetrics const r = computeRowMetrics(pit, row);
1029
1030         pos_type vc = row.pos();
1031         pos_type end = row.endpos();
1032         pos_type c = 0;
1033         LyXLayout_ptr const & layout = pars_[pit].layout();
1034
1035         bool left_side = false;
1036
1037         pos_type body_pos = pars_[pit].beginOfBody();
1038
1039         double tmpx = r.x;
1040         double last_tmpx = tmpx;
1041
1042         if (body_pos > 0 &&
1043             (body_pos > end || !pars_[pit].isLineSeparator(body_pos - 1)))
1044                 body_pos = 0;
1045
1046         // check for empty row
1047         if (vc == end) {
1048                 x = int(tmpx) + xo_;
1049                 return 0;
1050         }
1051
1052         while (vc < end && tmpx <= x) {
1053                 c = bidi.vis2log(vc);
1054                 last_tmpx = tmpx;
1055                 if (body_pos > 0 && c == body_pos - 1) {
1056                         tmpx += r.label_hfill +
1057                                 font_metrics::width(layout->labelsep, getLabelFont(pit));
1058                         if (pars_[pit].isLineSeparator(body_pos - 1))
1059                                 tmpx -= singleWidth(pit, body_pos - 1);
1060                 }
1061
1062                 if (hfillExpansion(pars_[pit], row, c)) {
1063                         tmpx += singleWidth(pit, c);
1064                         if (c >= body_pos)
1065                                 tmpx += r.hfill;
1066                         else
1067                                 tmpx += r.label_hfill;
1068                 } else if (pars_[pit].isSeparator(c)) {
1069                         tmpx += singleWidth(pit, c);
1070                         if (c >= body_pos)
1071                                 tmpx += r.separator;
1072                 } else {
1073                         tmpx += singleWidth(pit, c);
1074                 }
1075                 ++vc;
1076         }
1077
1078         if ((tmpx + last_tmpx) / 2 > x) {
1079                 tmpx = last_tmpx;
1080                 left_side = true;
1081         }
1082
1083         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
1084
1085         boundary = false;
1086         // This (rtl_support test) is not needed, but gives
1087         // some speedup if rtl_support == false
1088         bool const lastrow = lyxrc.rtl_support && row.endpos() == pars_[pit].size();
1089
1090         // If lastrow is false, we don't need to compute
1091         // the value of rtl.
1092         bool const rtl = lastrow ? isRTL(pars_[pit]) : false;
1093         if (lastrow &&
1094                  ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
1095                   (!rtl && !left_side && vc == end  && x > tmpx + 5)))
1096                 c = end;
1097         else if (vc == row.pos()) {
1098                 c = bidi.vis2log(vc);
1099                 if (bidi.level(c) % 2 == 1)
1100                         ++c;
1101         } else {
1102                 c = bidi.vis2log(vc - 1);
1103                 bool const rtl = (bidi.level(c) % 2 == 1);
1104                 if (left_side == rtl) {
1105                         ++c;
1106                         boundary = bidi.isBoundary(*bv()->buffer(), pars_[pit], c);
1107                 }
1108         }
1109
1110         if (row.pos() < end && c >= end && pars_[pit].isNewline(end - 1)) {
1111                 if (bidi.level(end -1) % 2 == 0)
1112                         tmpx -= singleWidth(pit, end - 1);
1113                 else
1114                         tmpx += singleWidth(pit, end - 1);
1115                 c = end - 1;
1116         }
1117
1118         x = int(tmpx) + xo_;
1119         return c - row.pos();
1120 }
1121
1122
1123 // x,y are absolute coordinates
1124 void LyXText::setCursorFromCoordinates(LCursor & cur, int x, int y)
1125 {
1126         x -= xo_;
1127         y -= yo_;
1128         par_type pit;
1129         Row const & row = getRowNearY(y, pit);
1130         lyxerr[Debug::DEBUG] << "setCursorFromCoordinates:: hit row at: "
1131                              << row.pos() << endl;
1132         bool bound = false;
1133         int xx = x + xo_; // getRowNearX get absolute x coords
1134         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1135         setCursor(cur, pit, pos, true, bound);
1136 }
1137
1138
1139 // x,y are absolute screen coordinates
1140 InsetBase * LyXText::editXY(LCursor & cur, int x, int y)
1141 {
1142         par_type pit;
1143         Row const & row = getRowNearY(y - yo_, pit);
1144         bool bound = false;
1145
1146         int xx = x; // is modified by getColumnNearX
1147         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1148         cur.par() = pit;
1149         cur.pos() = pos;
1150         cur.boundary() = bound;
1151
1152         // try to descend into nested insets
1153         InsetBase * inset = checkInsetHit(x, y);
1154         lyxerr[Debug::DEBUG] << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1155         if (!inset)
1156                 return 0;
1157
1158         // This should be just before or just behind the
1159         // cursor position set above.
1160         BOOST_ASSERT((pos != 0 && inset == pars_[pit].getInset(pos - 1))
1161                      || inset == pars_[pit].getInset(pos));
1162         // Make sure the cursor points to the position before
1163         // this inset.
1164         if (inset == pars_[pit].getInset(pos - 1))
1165                 --cur.pos();
1166         return inset->editXY(cur, x, y);
1167 }
1168
1169
1170 bool LyXText::checkAndActivateInset(LCursor & cur, bool front)
1171 {
1172         if (cur.selection())
1173                 return false;
1174         if (cur.pos() == cur.lastpos())
1175                 return false;
1176         InsetBase * inset = cur.nextInset();
1177         if (!isHighlyEditableInset(inset))
1178                 return false;
1179         inset->edit(cur, front);
1180         return true;
1181 }
1182
1183
1184 void LyXText::cursorLeft(LCursor & cur)
1185 {
1186         if (cur.pos() != 0) {
1187                 bool boundary = cur.boundary();
1188                 setCursor(cur, cur.par(), cur.pos() - 1, true, false);
1189                 if (!checkAndActivateInset(cur, false)) {
1190                         if (false && !boundary &&
1191                                         bidi.isBoundary(cur.buffer(), cur.paragraph(), cur.pos() + 1))
1192                                 setCursor(cur, cur.par(), cur.pos() + 1, true, true);
1193                 }
1194                 return;
1195         }
1196
1197         if (cur.par() != 0) {
1198                 // steps into the paragraph above
1199                 setCursor(cur, cur.par() - 1, getPar(cur.par() - 1).size());
1200         }
1201 }
1202
1203
1204 void LyXText::cursorRight(LCursor & cur)
1205 {
1206         if (false && cur.boundary()) {
1207                 setCursor(cur, cur.par(), cur.pos(), true, false);
1208                 return;
1209         }
1210
1211         if (cur.pos() != cur.lastpos()) {
1212                 if (!checkAndActivateInset(cur, true)) {
1213                         setCursor(cur, cur.par(), cur.pos() + 1, true, false);
1214                         if (false && bidi.isBoundary(cur.buffer(), cur.paragraph(),
1215                                                          cur.pos()))
1216                                 setCursor(cur, cur.par(), cur.pos(), true, true);
1217                 }
1218                 return;
1219         }
1220
1221         if (cur.par() != cur.lastpar())
1222                 setCursor(cur, cur.par() + 1, 0);
1223 }
1224
1225
1226 void LyXText::cursorUp(LCursor & cur)
1227 {
1228         Row const & row = cur.textRow();
1229         int x = cur.x_target();
1230         int y = cursorY(cur.top()) - row.baseline() - 1;
1231         setCursorFromCoordinates(cur, x, y);
1232
1233         if (!cur.selection()) {
1234                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1235                 if (inset_hit && isHighlyEditableInset(inset_hit))
1236                         inset_hit->editXY(cur, cur.x_target(), y);
1237         }
1238 }
1239
1240
1241 void LyXText::cursorDown(LCursor & cur)
1242 {
1243         Row const & row = cur.textRow();
1244         int x = cur.x_target();
1245         int y = cursorY(cur.top()) - row.baseline() + row.height() + 1;
1246         setCursorFromCoordinates(cur, x, y);
1247
1248         if (!cur.selection()) {
1249                 InsetBase * inset_hit = checkInsetHit(cur.x_target(), y);
1250                 if (inset_hit && isHighlyEditableInset(inset_hit))
1251                         inset_hit->editXY(cur, cur.x_target(), y);
1252         }
1253 }
1254
1255
1256 void LyXText::cursorUpParagraph(LCursor & cur)
1257 {
1258         if (cur.pos() > 0)
1259                 setCursor(cur, cur.par(), 0);
1260         else if (cur.par() != 0)
1261                 setCursor(cur, cur.par() - 1, 0);
1262 }
1263
1264
1265 void LyXText::cursorDownParagraph(LCursor & cur)
1266 {
1267         if (cur.par() != cur.lastpar())
1268                 setCursor(cur, cur.par() + 1, 0);
1269         else
1270                 setCursor(cur, cur.par(), cur.lastpos());
1271 }
1272
1273
1274 // fix the cursor `cur' after a characters has been deleted at `where'
1275 // position. Called by deleteEmptyParagraphMechanism
1276 void LyXText::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
1277 {
1278         // do notheing if cursor is not in the paragraph where the
1279         // deletion occured,
1280         if (cur.par() != where.par())
1281                 return;
1282
1283         // if cursor position is after the deletion place update it
1284         if (cur.pos() > where.pos())
1285                 --cur.pos();
1286
1287         // check also if we don't want to set the cursor on a spot behind the
1288         // pagragraph because we erased the last character.
1289         if (cur.pos() > cur.lastpos())
1290                 cur.pos() = cur.lastpos();
1291 }
1292
1293
1294 bool LyXText::deleteEmptyParagraphMechanism(LCursor & cur, LCursor const & old)
1295 {
1296         BOOST_ASSERT(cur.size() == old.size());
1297         // Would be wrong to delete anything if we have a selection.
1298         if (cur.selection())
1299                 return false;
1300
1301         //lyxerr[Debug::DEBUG] << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
1302         Paragraph const & oldpar = pars_[old.par()];
1303
1304         // We allow all kinds of "mumbo-jumbo" when freespacing.
1305         if (oldpar.isFreeSpacing())
1306                 return false;
1307
1308         /* Ok I'll put some comments here about what is missing.
1309            I have fixed BackSpace (and thus Delete) to not delete
1310            double-spaces automagically. I have also changed Cut,
1311            Copy and Paste to hopefully do some sensible things.
1312            There are still some small problems that can lead to
1313            double spaces stored in the document file or space at
1314            the beginning of paragraphs(). This happens if you have
1315            the cursor between to spaces and then save. Or if you
1316            cut and paste and the selection have a space at the
1317            beginning and then save right after the paste. I am
1318            sure none of these are very hard to fix, but I will
1319            put out 1.1.4pre2 with FIX_DOUBLE_SPACE defined so
1320            that I can get some feedback. (Lgb)
1321         */
1322
1323         // If old.pos() == 0 and old.pos()(1) == LineSeparator
1324         // delete the LineSeparator.
1325         // MISSING
1326
1327         // If old.pos() == 1 and old.pos()(0) == LineSeparator
1328         // delete the LineSeparator.
1329         // MISSING
1330
1331         // If the chars around the old cursor were spaces, delete one of them.
1332         if (old.par() != cur.par() || old.pos() != cur.pos()) {
1333
1334                 // Only if the cursor has really moved.
1335                 if (old.pos() > 0
1336                     && old.pos() < oldpar.size()
1337                     && oldpar.isLineSeparator(old.pos())
1338                     && oldpar.isLineSeparator(old.pos() - 1)) {
1339                         pars_[old.par()].erase(old.pos() - 1);
1340 #ifdef WITH_WARNINGS
1341 #warning This will not work anymore when we have multiple views of the same buffer
1342 // In this case, we will have to correct also the cursors held by
1343 // other bufferviews. It will probably be easier to do that in a more
1344 // automated way in CursorSlice code. (JMarc 26/09/2001)
1345 #endif
1346                         // correct all cursor parts
1347                         fixCursorAfterDelete(cur.top(), old.top());
1348 #ifdef WITH_WARNINGS
1349 #warning DEPM, look here
1350 #endif
1351                         //fixCursorAfterDelete(cur.anchor(), old.top());
1352                         return false;
1353                 }
1354         }
1355
1356         // only do our magic if we changed paragraph
1357         if (old.par() == cur.par())
1358                 return false;
1359
1360         // don't delete anything if this is the ONLY paragraph!
1361         if (pars_.size() == 1)
1362                 return false;
1363
1364         // Do not delete empty paragraphs with keepempty set.
1365         if (oldpar.allowEmpty())
1366                 return false;
1367
1368         // record if we have deleted a paragraph
1369         // we can't possibly have deleted a paragraph before this point
1370         bool deleted = false;
1371
1372         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
1373                 // ok, we will delete something
1374                 CursorSlice tmpcursor;
1375
1376                 deleted = true;
1377
1378                 bool selection_position_was_oldcursor_position =
1379                         cur.anchor().par() == old.par() && cur.anchor().pos() == old.pos();
1380
1381                 // This is a bit of a overkill. We change the old and the cur par
1382                 // at max, certainly not everything in between...
1383                 recUndo(old.par(), cur.par());
1384
1385                 // Delete old par.
1386                 pars_.erase(pars_.begin() + old.par());
1387
1388                 // Update cursor par offset if necessary.
1389                 // Some 'iterator registration' would be nice that takes care of
1390                 // such events. Maybe even signal/slot?
1391                 if (cur.par() > old.par())
1392                         --cur.par();
1393 #ifdef WITH_WARNINGS
1394 #warning DEPM, look here
1395 #endif
1396 //              if (cur.anchor().par() > old.par())
1397 //                      --cur.anchor().par();
1398
1399                 if (selection_position_was_oldcursor_position) {
1400                         // correct selection
1401                         cur.resetAnchor();
1402                 }
1403         }
1404
1405         if (deleted)
1406                 return true;
1407
1408         if (pars_[old.par()].stripLeadingSpaces())
1409                 cur.resetAnchor();
1410
1411         return false;
1412 }
1413
1414
1415 ParagraphList & LyXText::paragraphs() const
1416 {
1417         return const_cast<ParagraphList &>(pars_);
1418 }
1419
1420
1421 void LyXText::recUndo(par_type first, par_type last) const
1422 {
1423         recordUndo(bv()->cursor(), Undo::ATOMIC, first, last);
1424 }
1425
1426
1427 void LyXText::recUndo(par_type par) const
1428 {
1429         recordUndo(bv()->cursor(), Undo::ATOMIC, par, par);
1430 }
1431
1432
1433 int defaultRowHeight()
1434 {
1435         return int(font_metrics::maxHeight(LyXFont(LyXFont::ALL_SANE)) *  1.2);
1436 }