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