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