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