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