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