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