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