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