]> git.lyx.org Git - lyx.git/blob - src/text.C
remove broken operator==() for Paragraphs
[lyx.git] / src / text.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 "lyxrow.h"
15 #include "paragraph.h"
16 #include "gettext.h"
17 #include "bufferparams.h"
18 #include "buffer.h"
19 #include "debug.h"
20 #include "intl.h"
21 #include "lyxrc.h"
22 #include "encoding.h"
23 #include "frontends/LyXView.h"
24 #include "frontends/Painter.h"
25 #include "frontends/font_metrics.h"
26 #include "frontends/screen.h"
27 #include "frontends/WorkArea.h"
28 #include "bufferview_funcs.h"
29 #include "BufferView.h"
30 #include "language.h"
31 #include "ParagraphParameters.h"
32 #include "undo_funcs.h"
33 #include "text_funcs.h"
34 #include "WordLangTuple.h"
35 #include "paragraph_funcs.h"
36 #include "rowpainter.h"
37 #include "lyxrow_funcs.h"
38 #include "metricsinfo.h"
39
40 #include "insets/insettext.h"
41
42 #include "support/textutils.h"
43 #include "support/LAssert.h"
44 #include "support/lstrings.h"
45
46 #include <algorithm>
47
48 using namespace lyx::support;
49
50 using std::max;
51 using std::min;
52 using std::endl;
53 using std::pair;
54
55 using lyx::pos_type;
56 using lyx::word_location;
57
58 using namespace bv_funcs;
59
60 /// top, right, bottom pixel margin
61 extern int const PAPER_MARGIN = 20;
62 /// margin for changebar
63 extern int const CHANGEBAR_MARGIN = 10;
64 /// left margin
65 extern int const LEFT_MARGIN = PAPER_MARGIN + CHANGEBAR_MARGIN;
66
67 int bibitemMaxWidth(BufferView *, LyXFont const &);
68
69
70 BufferView * LyXText::bv()
71 {
72         Assert(bv_owner != 0);
73         return bv_owner;
74 }
75
76
77 BufferView * LyXText::bv() const
78 {
79         Assert(bv_owner != 0);
80         return bv_owner;
81 }
82
83
84 void LyXText::updateRowPositions()
85 {
86         RowList::iterator rit = rows().begin();
87         RowList::iterator rend = rows().end();
88         for (int y = 0; rit != rend ; ++rit) {
89                 rit->y(y);
90                 y += rit->height();
91         }
92 }
93
94
95 int LyXText::top_y() const
96 {
97         if (anchor_row_ == rowlist_.end())
98                 return 0;
99
100         return anchor_row_->y() + anchor_row_offset_;
101 }
102
103
104 void LyXText::top_y(int newy)
105 {
106         if (rows().empty())
107                 return;
108
109         if (isInInset()) {
110                 anchor_row_ = rows().begin();
111                 anchor_row_offset_ = newy;
112                 return;
113         }
114
115         lyxerr[Debug::GUI] << "setting top y = " << newy << endl;
116
117         int y = newy;
118         RowList::iterator rit = getRowNearY(y);
119
120         if (rit == anchor_row_ && anchor_row_offset_ == newy - y) {
121                 lyxerr[Debug::GUI] << "top_y to same value, skipping update" << endl;
122                 return;
123         }
124
125         anchor_row_ = rit;
126         anchor_row_offset_ = newy - y;
127         lyxerr[Debug::GUI] << "changing reference to row: " << &*anchor_row_
128                << " offset: " << anchor_row_offset_ << endl;
129         postPaint();
130 }
131
132
133 void LyXText::anchor_row(RowList::iterator rit)
134 {
135         int old_y = top_y();
136         anchor_row_offset_ = 0;
137         anchor_row_ = rit;
138         anchor_row_offset_ = old_y - top_y();
139         lyxerr[Debug::GUI] << "anchor_row(): changing reference to row: "
140                            << &*anchor_row_ << " offset: "
141                            << anchor_row_offset_ << endl;
142 }
143
144
145 int LyXText::workWidth() const
146 {
147         return inset_owner ? inset_owner->textWidth() : bv()->workWidth();
148 }
149
150
151 int LyXText::workWidth(InsetOld const * inset) const
152 {
153         ParagraphList::iterator par = ownerParagraphs().begin();
154         ParagraphList::iterator end = ownerParagraphs().end();
155         for ( ; par != end; ++par)
156                 if (&*par == inset->parOwner())
157                         break;
158
159         if (par == ownerParagraphs().end()) {
160                 lyxerr << "LyXText::workWidth: unexpected\n";
161                 return -1;
162         }
163
164         pos_type pos = par->getPositionOfInset(inset);
165         Assert(pos != -1);
166
167         LyXLayout_ptr const & layout = par->layout();
168
169         if (layout->margintype != MARGIN_RIGHT_ADDRESS_BOX) {
170                 // Optimization here: in most cases, the real row is
171                 // not needed, but only the par/pos values. So we just
172                 // construct a dummy row for leftMargin. (JMarc)
173                 return workWidth() - leftMargin(Row(par, pos));
174         }
175
176         RowList::iterator row = getRow(par, pos);
177         RowList::iterator frow = row;
178         RowList::iterator beg = rowlist_.begin();
179
180         while (frow != beg && frow->par() == boost::prior(frow)->par())
181                 --frow;
182
183         // FIXME: I don't understand this code - jbl
184
185         unsigned int maxw = 0;
186         while (!isParEnd(*this, frow)) {
187                 if (frow != row && maxw < frow->width())
188                         maxw = frow->width();
189                 ++frow;
190         }
191         return maxw ? maxw : workWidth();
192 }
193
194
195 int LyXText::getRealCursorX() const
196 {
197         int x = cursor.x();
198         if (the_locking_inset && (the_locking_inset->getLyXText(bv())!= this))
199                 x = the_locking_inset->getLyXText(bv())->getRealCursorX();
200         return x;
201 }
202
203
204 #warning FIXME  This function seems to belong outside of LyxText.
205 unsigned char LyXText::transformChar(unsigned char c, Paragraph const & par,
206                                      pos_type pos) const
207 {
208         if (!Encodings::is_arabic(c))
209                 if (lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 && IsDigit(c))
210                         return c + (0xb0 - '0');
211                 else
212                         return c;
213
214         unsigned char const prev_char = pos > 0 ? par.getChar(pos - 1) : ' ';
215         unsigned char next_char = ' ';
216
217         pos_type const par_size = par.size();
218
219         for (pos_type i = pos + 1; i < par_size; ++i) {
220                 unsigned char const par_char = par.getChar(i);
221                 if (!Encodings::IsComposeChar_arabic(par_char)) {
222                         next_char = par_char;
223                         break;
224                 }
225         }
226
227         if (Encodings::is_arabic(next_char)) {
228                 if (Encodings::is_arabic(prev_char) &&
229                         !Encodings::is_arabic_special(prev_char))
230                         return Encodings::TransformChar(c, Encodings::FORM_MEDIAL);
231                 else
232                         return Encodings::TransformChar(c, Encodings::FORM_INITIAL);
233         } else {
234                 if (Encodings::is_arabic(prev_char) &&
235                         !Encodings::is_arabic_special(prev_char))
236                         return Encodings::TransformChar(c, Encodings::FORM_FINAL);
237                 else
238                         return Encodings::TransformChar(c, Encodings::FORM_ISOLATED);
239         }
240 }
241
242 // This is the comments that some of the warnings below refers to.
243 // There are some issues in this file and I don't think they are
244 // really related to the FIX_DOUBLE_SPACE patch. I'd rather think that
245 // this is a problem that has been here almost from day one and that a
246 // larger userbase with differenct access patters triggers the bad
247 // behaviour. (segfaults.) What I think happen is: In several places
248 // we store the paragraph in the current cursor and then moves the
249 // cursor. This movement of the cursor will delete paragraph at the
250 // old position if it is now empty. This will make the temporary
251 // pointer to the old cursor paragraph invalid and dangerous to use.
252 // And is some cases this will trigger a segfault. I have marked some
253 // of the cases where this happens with a warning, but I am sure there
254 // are others in this file and in text2.C. There is also a note in
255 // Delete() that you should read. In Delete I store the paragraph->id
256 // instead of a pointer to the paragraph. I am pretty sure this faulty
257 // use of temporary pointers to paragraphs that might have gotten
258 // invalidated (through a cursor movement) before they are used, are
259 // the cause of the strange crashes we get reported often.
260 //
261 // It is very tiresom to change this code, especially when it is as
262 // hard to read as it is. Help to fix all the cases where this is done
263 // would be greately appreciated.
264 //
265 // Lgb
266
267 int LyXText::singleWidth(ParagraphList::iterator pit, pos_type pos) const
268 {
269         if (pos >= pit->size())
270                 return 0;
271
272         char const c = pit->getChar(pos);
273         return singleWidth(pit, pos, c);
274 }
275
276
277 int LyXText::singleWidth(ParagraphList::iterator pit,
278                          pos_type pos, char c) const
279 {
280         if (pos >= pit->size())
281                 return 0;
282
283         LyXFont const & font = getFont(pit, pos);
284
285         // The most common case is handled first (Asger)
286         if (IsPrintable(c)) {
287                 if (font.language()->RightToLeft()) {
288                         if ((lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 ||
289                              lyxrc.font_norm_type == LyXRC::ISO_10646_1)
290                             && font.language()->lang() == "arabic") {
291                                 if (Encodings::IsComposeChar_arabic(c))
292                                         return 0;
293                                 else
294                                         c = transformChar(c, *pit, pos);
295                         } else if (font.language()->lang() == "hebrew" &&
296                                  Encodings::IsComposeChar_hebrew(c))
297                                 return 0;
298                 }
299                 return font_metrics::width(c, font);
300         }
301
302         if (c == Paragraph::META_INSET) {
303                 InsetOld * tmpinset = pit->getInset(pos);
304                 if (tmpinset) {
305                         if (tmpinset->lyxCode() == InsetOld::HFILL_CODE) {
306                                 // Because of the representation as vertical lines
307                                 return 3;
308                         }
309 #if 0
310 #warning enabling this fixes the 'insets of width 0 on load' problem
311                         // this IS needed otherwise on initialitation we don't get the fill
312                         // of the row right (ONLY on initialization if we read a file!)
313                         // should be changed! (Jug 20011204)
314                         //tmpinset->update(bv());
315                         Dimension dim;
316                         MetricsInfo mi(bv(), font, workWidth());
317                         tmpinset->metrics(mi, dim);
318                         return dim.wid;
319 #else
320                         return tmpinset->width();
321 #endif
322                 }
323                 return 0;
324         }
325
326         if (IsSeparatorChar(c))
327                 c = ' ';
328         return font_metrics::width(c, font);
329 }
330
331
332 lyx::pos_type LyXText::log2vis(lyx::pos_type pos) const
333 {
334         if (bidi_start == -1)
335                 return pos;
336         else
337                 return log2vis_list[pos - bidi_start];
338 }
339
340
341 lyx::pos_type LyXText::vis2log(lyx::pos_type pos) const
342 {
343         if (bidi_start == -1)
344                 return pos;
345         else
346                 return vis2log_list[pos - bidi_start];
347 }
348
349
350 lyx::pos_type LyXText::bidi_level(lyx::pos_type pos) const
351 {
352         if (bidi_start == -1)
353                 return 0;
354         else
355                 return bidi_levels[pos - bidi_start];
356 }
357
358
359 bool LyXText::bidi_InRange(lyx::pos_type pos) const
360 {
361         return bidi_start == -1 ||
362                 (bidi_start <= pos && pos <= bidi_end);
363 }
364
365
366 void LyXText::computeBidiTables(Buffer const * buf,
367                                 RowList::iterator row) const
368 {
369         bidi_same_direction = true;
370         if (!lyxrc.rtl_support) {
371                 bidi_start = -1;
372                 return;
373         }
374
375         ParagraphList::iterator row_par = row->par();
376
377         InsetOld * inset = row_par->inInset();
378         if (inset && inset->owner() &&
379             inset->owner()->lyxCode() == InsetOld::ERT_CODE) {
380                 bidi_start = -1;
381                 return;
382         }
383
384         bidi_start = row->pos();
385         bidi_end = lastPrintablePos(*this, row);
386
387         if (bidi_start > bidi_end) {
388                 bidi_start = -1;
389                 return;
390         }
391
392         if (bidi_end + 2 - bidi_start >
393             static_cast<pos_type>(log2vis_list.size())) {
394                 pos_type new_size =
395                         (bidi_end + 2 - bidi_start < 500) ?
396                         500 : 2 * (bidi_end + 2 - bidi_start);
397                 log2vis_list.resize(new_size);
398                 vis2log_list.resize(new_size);
399                 bidi_levels.resize(new_size);
400         }
401
402         vis2log_list[bidi_end + 1 - bidi_start] = -1;
403         log2vis_list[bidi_end + 1 - bidi_start] = -1;
404
405         pos_type stack[2];
406         bool const rtl_par =
407                 row_par->isRightToLeftPar(buf->params);
408         int level = 0;
409         bool rtl = false;
410         bool rtl0 = false;
411         pos_type const body_pos = row_par->beginningOfBody();
412
413         for (pos_type lpos = bidi_start; lpos <= bidi_end; ++lpos) {
414                 bool is_space = row_par->isLineSeparator(lpos);
415                 pos_type const pos =
416                         (is_space && lpos + 1 <= bidi_end &&
417                          !row_par->isLineSeparator(lpos + 1) &&
418                          !row_par->isNewline(lpos + 1))
419                         ? lpos + 1 : lpos;
420                 LyXFont font = row_par->getFontSettings(buf->params, pos);
421                 if (pos != lpos && 0 < lpos && rtl0 && font.isRightToLeft() &&
422                     font.number() == LyXFont::ON &&
423                     row_par->getFontSettings(buf->params, lpos - 1).number()
424                     == LyXFont::ON) {
425                         font = row_par->getFontSettings(buf->params, lpos);
426                         is_space = false;
427                 }
428
429
430                 bool new_rtl = font.isVisibleRightToLeft();
431                 bool new_rtl0 = font.isRightToLeft();
432                 int new_level;
433
434                 if (lpos == body_pos - 1
435                     && row->pos() < body_pos - 1
436                     && is_space) {
437                         new_level = (rtl_par) ? 1 : 0;
438                         new_rtl = new_rtl0 = rtl_par;
439                 } else if (new_rtl0)
440                         new_level = (new_rtl) ? 1 : 2;
441                 else
442                         new_level = (rtl_par) ? 2 : 0;
443
444                 if (is_space && new_level >= level) {
445                         new_level = level;
446                         new_rtl = rtl;
447                         new_rtl0 = rtl0;
448                 }
449
450                 int new_level2 = new_level;
451
452                 if (level == new_level && rtl0 != new_rtl0) {
453                         --new_level2;
454                         log2vis_list[lpos - bidi_start] = (rtl) ? 1 : -1;
455                 } else if (level < new_level) {
456                         log2vis_list[lpos - bidi_start] =  (rtl) ? -1 : 1;
457                         if (new_level > rtl_par)
458                                 bidi_same_direction = false;
459                 } else
460                         log2vis_list[lpos - bidi_start] = (new_rtl) ? -1 : 1;
461                 rtl = new_rtl;
462                 rtl0 = new_rtl0;
463                 bidi_levels[lpos - bidi_start] = new_level;
464
465                 while (level > new_level2) {
466                         pos_type old_lpos = stack[--level];
467                         int delta = lpos - old_lpos - 1;
468                         if (level % 2)
469                                 delta = -delta;
470                         log2vis_list[lpos - bidi_start] += delta;
471                         log2vis_list[old_lpos - bidi_start] += delta;
472                 }
473                 while (level < new_level)
474                         stack[level++] = lpos;
475         }
476
477         while (level > 0) {
478                 pos_type const old_lpos = stack[--level];
479                 int delta = bidi_end - old_lpos;
480                 if (level % 2)
481                         delta = -delta;
482                 log2vis_list[old_lpos - bidi_start] += delta;
483         }
484
485         pos_type vpos = bidi_start - 1;
486         for (pos_type lpos = bidi_start;
487              lpos <= bidi_end; ++lpos) {
488                 vpos += log2vis_list[lpos - bidi_start];
489                 vis2log_list[vpos - bidi_start] = lpos;
490                 log2vis_list[lpos - bidi_start] = vpos;
491         }
492 }
493
494
495 // This method requires a previous call to ComputeBidiTables()
496 bool LyXText::isBoundary(Buffer const * buf, Paragraph const & par,
497                          pos_type pos) const
498 {
499         if (!lyxrc.rtl_support || pos == 0)
500                 return false;
501
502         if (!bidi_InRange(pos - 1)) {
503                 /// This can happen if pos is the first char of a row.
504                 /// Returning false in this case is incorrect!
505                 return false;
506         }
507
508         bool const rtl = bidi_level(pos - 1) % 2;
509         bool const rtl2 = bidi_InRange(pos)
510                 ? bidi_level(pos) % 2
511                 : par.isRightToLeftPar(buf->params);
512         return rtl != rtl2;
513 }
514
515
516 bool LyXText::isBoundary(Buffer const * buf, Paragraph const & par,
517                          pos_type pos, LyXFont const & font) const
518 {
519         if (!lyxrc.rtl_support)
520                 return false;    // This is just for speedup
521
522         bool const rtl = font.isVisibleRightToLeft();
523         bool const rtl2 = bidi_InRange(pos)
524                 ? bidi_level(pos) % 2
525                 : par.isRightToLeftPar(buf->params);
526         return rtl != rtl2;
527 }
528
529
530 int LyXText::leftMargin(Row const & row) const
531 {
532         InsetOld * ins;
533
534         if (row.pos() < row.par()->size())
535                 if ((row.par()->getChar(row.pos()) == Paragraph::META_INSET) &&
536                     (ins = row.par()->getInset(row.pos())) &&
537                     (ins->needFullRow() || ins->display()))
538                         return LEFT_MARGIN;
539
540         LyXTextClass const & tclass =
541                 bv()->buffer()->params.getLyXTextClass();
542         LyXLayout_ptr const & layout = row.par()->layout();
543
544         string parindent = layout->parindent;
545
546         int x = LEFT_MARGIN;
547
548         x += font_metrics::signedWidth(tclass.leftmargin(), tclass.defaultfont());
549
550         // this is the way, LyX handles the LaTeX-Environments.
551         // I have had this idea very late, so it seems to be a
552         // later added hack and this is true
553         if (!row.par()->getDepth()) {
554                 if (row.par()->layout() == tclass.defaultLayout()) {
555                         // find the previous same level paragraph
556                         if (row.par() != ownerParagraphs().begin()) {
557                                 ParagraphList::iterator newpit =
558                                         depthHook(row.par(), ownerParagraphs(),
559                                                   row.par()->getDepth());
560                                 if (newpit == row.par() &&
561                                     newpit->layout()->nextnoindent)
562                                         parindent.erase();
563                         }
564                 }
565         } else {
566                 // find the next level paragraph
567
568                 ParagraphList::iterator newpar = outerHook(row.par(),
569                                                            ownerParagraphs());
570
571                 // make a corresponding row. Needed to call leftMargin()
572
573                 // check wether it is a sufficent paragraph
574                 if (newpar != ownerParagraphs().end() &&
575                     newpar->layout()->isEnvironment()) {
576                         Row dummyrow;
577                         dummyrow.par(newpar);
578                         dummyrow.pos(newpar->size());
579                         x = leftMargin(dummyrow);
580                 }
581
582                 if (newpar != ownerParagraphs().end() &&
583                     row.par()->layout() == tclass.defaultLayout()) {
584                         if (newpar->params().noindent())
585                                 parindent.erase();
586                         else {
587                                 parindent = newpar->layout()->parindent;
588                         }
589
590                 }
591         }
592
593         LyXFont const labelfont = getLabelFont(row.par());
594         switch (layout->margintype) {
595         case MARGIN_DYNAMIC:
596                 if (!layout->leftmargin.empty()) {
597                         x += font_metrics::signedWidth(layout->leftmargin,
598                                                   tclass.defaultfont());
599                 }
600                 if (!row.par()->getLabelstring().empty()) {
601                         x += font_metrics::signedWidth(layout->labelindent,
602                                                   labelfont);
603                         x += font_metrics::width(row.par()->getLabelstring(),
604                                             labelfont);
605                         x += font_metrics::width(layout->labelsep, labelfont);
606                 }
607                 break;
608         case MARGIN_MANUAL:
609                 x += font_metrics::signedWidth(layout->labelindent, labelfont);
610                 // The width of an empty par, even with manual label, should be 0
611                 if (!row.par()->empty() && row.pos() >= row.par()->beginningOfBody()) {
612                         if (!row.par()->getLabelWidthString().empty()) {
613                                 x += font_metrics::width(row.par()->getLabelWidthString(),
614                                                labelfont);
615                                 x += font_metrics::width(layout->labelsep, labelfont);
616                         }
617                 }
618                 break;
619         case MARGIN_STATIC:
620                 x += font_metrics::signedWidth(layout->leftmargin, tclass.defaultfont()) * 4
621                         / (row.par()->getDepth() + 4);
622                 break;
623         case MARGIN_FIRST_DYNAMIC:
624                 if (layout->labeltype == LABEL_MANUAL) {
625                         if (row.pos() >= row.par()->beginningOfBody()) {
626                                 x += font_metrics::signedWidth(layout->leftmargin,
627                                                           labelfont);
628                         } else {
629                                 x += font_metrics::signedWidth(layout->labelindent,
630                                                           labelfont);
631                         }
632                 } else if (row.pos()
633                            // Special case to fix problems with
634                            // theorems (JMarc)
635                            || (layout->labeltype == LABEL_STATIC
636                                && layout->latextype == LATEX_ENVIRONMENT
637                                && !isFirstInSequence(row.par(), ownerParagraphs()))) {
638                         x += font_metrics::signedWidth(layout->leftmargin,
639                                                   labelfont);
640                 } else if (layout->labeltype != LABEL_TOP_ENVIRONMENT
641                            && layout->labeltype != LABEL_BIBLIO
642                            && layout->labeltype !=
643                            LABEL_CENTERED_TOP_ENVIRONMENT) {
644                         x += font_metrics::signedWidth(layout->labelindent,
645                                                   labelfont);
646                         x += font_metrics::width(layout->labelsep, labelfont);
647                         x += font_metrics::width(row.par()->getLabelstring(),
648                                             labelfont);
649                 }
650                 break;
651
652         case MARGIN_RIGHT_ADDRESS_BOX:
653         {
654                 // ok, a terrible hack. The left margin depends on the widest
655                 // row in this paragraph. Do not care about footnotes, they
656                 // are *NOT* allowed in the LaTeX realisation of this layout.
657
658                 // find the first row of this paragraph
659                 RowList::iterator tmprit = rowlist_.begin();
660                 while (tmprit != rowlist_.end()
661                        && tmprit->par() != row.par())
662                         ++tmprit;
663
664                 int minfill = tmprit->fill();
665                 while (boost::next(tmprit) != rowlist_.end() &&
666                        boost::next(tmprit)->par() == row.par()) {
667                         ++tmprit;
668                         if (tmprit->fill() < minfill)
669                                 minfill = tmprit->fill();
670                 }
671
672                 x += font_metrics::signedWidth(layout->leftmargin,
673                         tclass.defaultfont());
674                 x += minfill;
675         }
676         break;
677         }
678
679         if ((workWidth() > 0) &&
680                 !row.par()->params().leftIndent().zero())
681         {
682                 LyXLength const len = row.par()->params().leftIndent();
683                 int const tw = inset_owner ?
684                         inset_owner->latexTextWidth(bv()) : workWidth();
685                 x += len.inPixels(tw);
686         }
687
688         LyXAlignment align;
689
690         if (row.par()->params().align() == LYX_ALIGN_LAYOUT)
691                 align = layout->align;
692         else
693                 align = row.par()->params().align();
694
695         // set the correct parindent
696         if (row.pos() == 0) {
697                 if ((layout->labeltype == LABEL_NO_LABEL
698                      || layout->labeltype == LABEL_TOP_ENVIRONMENT
699                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT
700                      || (layout->labeltype == LABEL_STATIC
701                          && layout->latextype == LATEX_ENVIRONMENT
702                          && !isFirstInSequence(row.par(), ownerParagraphs())))
703                     && align == LYX_ALIGN_BLOCK
704                     && !row.par()->params().noindent()
705                         // in tabulars and ert paragraphs are never indented!
706                         && (!row.par()->inInset() || !row.par()->inInset()->owner() ||
707                                 (row.par()->inInset()->owner()->lyxCode() != InsetOld::TABULAR_CODE &&
708                                  row.par()->inInset()->owner()->lyxCode() != InsetOld::ERT_CODE))
709                     && (row.par()->layout() != tclass.defaultLayout() ||
710                         bv()->buffer()->params.paragraph_separation ==
711                         BufferParams::PARSEP_INDENT)) {
712                         x += font_metrics::signedWidth(parindent,
713                                                   tclass.defaultfont());
714                 } else if (layout->labeltype == LABEL_BIBLIO) {
715                         // ale970405 Right width for bibitems
716                         x += bibitemMaxWidth(bv(), tclass.defaultfont());
717                 }
718         }
719
720         return x;
721 }
722
723
724 int LyXText::rightMargin(Buffer const & buf, Row const & row) const
725 {
726         InsetOld * ins;
727
728         if (row.pos() < row.par()->size())
729                 if ((row.par()->getChar(row.pos()) == Paragraph::META_INSET) &&
730                     (ins = row.par()->getInset(row.pos())) &&
731                     (ins->needFullRow() || ins->display()))
732                         return PAPER_MARGIN;
733
734         LyXTextClass const & tclass = buf.params.getLyXTextClass();
735         LyXLayout_ptr const & layout = row.par()->layout();
736
737         return PAPER_MARGIN
738                 + font_metrics::signedWidth(tclass.rightmargin(),
739                                        tclass.defaultfont());
740                 + font_metrics::signedWidth(layout->rightmargin,
741                                        tclass.defaultfont())
742                 * 4 / (row.par()->getDepth() + 4);
743 }
744
745
746 int LyXText::labelEnd(Row const & row) const
747 {
748         if (row.par()->layout()->margintype == MARGIN_MANUAL) {
749                 Row tmprow = row;
750                 tmprow.pos(row.par()->size());
751                 // return the beginning of the body
752                 return leftMargin(tmprow);
753         }
754
755         // LabelEnd is only needed if the layout
756         // fills a flushleft label.
757         return 0;
758 }
759
760
761 namespace {
762
763 // this needs special handling - only newlines count as a break point
764 pos_type addressBreakPoint(pos_type i, Paragraph const & par)
765 {
766         for (; i < par.size(); ++i) {
767                 if (par.isNewline(i))
768                         return i;
769         }
770
771         return par.size();
772 }
773
774 };
775
776
777 pos_type LyXText::rowBreakPoint(Row const & row) const
778 {
779         ParagraphList::iterator pit = row.par();
780
781         // maximum pixel width of a row.
782         int width = workWidth() - rightMargin(*bv()->buffer(), row);
783
784         // inset->textWidth() returns -1 via workWidth(),
785         // but why ?
786         if (width < 0)
787                 return pit->size();
788
789         LyXLayout_ptr const & layout = pit->layout();
790
791         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX)
792                 return addressBreakPoint(row.pos(), *pit);
793
794         pos_type const pos = row.pos();
795         pos_type const body_pos = pit->beginningOfBody();
796         pos_type const last = pit->size();
797         pos_type point = last;
798
799         if (pos == last)
800                 return last;
801
802         // Now we iterate through until we reach the right margin
803         // or the end of the par, then choose the possible break
804         // nearest that.
805
806         int const left = leftMargin(row);
807         int x = left;
808
809         // pixel width since last breakpoint
810         int chunkwidth = 0;
811         bool fullrow = false;
812
813         pos_type i = pos;
814
815         // We re-use the font resolution for the entire font span when possible
816         LyXFont font = getFont(pit, i);
817         lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
818
819         for (; i < last; ++i) {
820                 if (pit->isNewline(i)) {
821                         point = i;
822                         break;
823                 }
824
825                 char const c = pit->getChar(i);
826
827                 int thiswidth;
828
829                 // add the auto-hfill from label end to the body
830                 if (body_pos && i == body_pos) {
831                         thiswidth = font_metrics::width(layout->labelsep, getLabelFont(pit));
832                         if (pit->isLineSeparator(i - 1))
833                                 thiswidth -= singleWidth(pit, i - 1);
834                         int left_margin = labelEnd(row);
835                         if (thiswidth + x < left_margin)
836                                 thiswidth = left_margin - x;
837                         thiswidth += singleWidth(pit, i, c);
838                 } else {
839                         // Manual inlined optimised version of common case of "thiswidth = singleWidth(pit, i, c);"
840                         if (IsPrintable(c)) {
841                                 if (pos > endPosOfFontSpan) {
842                                         // We need to get the next font
843                                         font = getFont(pit, i);
844                                         endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
845                                 }
846                                 if (! font.language()->RightToLeft()) {
847                                         thiswidth = font_metrics::width(c, font);
848                                 } else {
849                                         // Fall-back to normal case
850                                         thiswidth = singleWidth(pit, i, c);
851                                         // And flush font cache
852                                         endPosOfFontSpan = 0;
853                                 }
854                         } else {
855                                 // Fall-back to normal case
856                                 thiswidth = singleWidth(pit, i, c);
857                                 // And flush font cache
858                                 endPosOfFontSpan = 0;
859                         }
860                 }
861
862                 x += thiswidth;
863                 chunkwidth += thiswidth;
864
865                 InsetOld * in = pit->isInset(i) ? pit->getInset(i) : 0;
866                 fullrow = in && (in->display() || in->needFullRow());
867
868                 // break before a character that will fall off
869                 // the right of the row
870                 if (x >= width) {
871                         // if no break before or we are at an inset
872                         // that will take up a row, break here
873                         if (point == last || fullrow || chunkwidth >= (width - left)) {
874                                 if (pos < i)
875                                         point = i - 1;
876                                 else
877                                         point = i;
878                         }
879                         break;
880                 }
881
882                 if (!in || in->isChar()) {
883                         // some insets are line separators too
884                         if (pit->isLineSeparator(i)) {
885                                 point = i;
886                                 chunkwidth = 0;
887                         }
888                         continue;
889                 }
890
891                 if (!fullrow)
892                         continue;
893
894                 // full row insets start at a new row
895                 if (i == pos) {
896                         if (pos < last - 1) {
897                                 point = i;
898                                 if (pit->isLineSeparator(i + 1))
899                                         ++point;
900                         } else {
901                                 // to avoid extra rows
902                                 point = last;
903                         }
904                 } else {
905                         point = i - 1;
906                 }
907
908                 return point;
909         }
910
911         if (point == last && x >= width) {
912                 // didn't find one, break at the point we reached the edge
913                 point = i;
914         } else if (i == last && x < width) {
915                 // found one, but we fell off the end of the par, so prefer
916                 // that.
917                 point = last;
918         }
919
920         // manual labels cannot be broken in LaTeX. But we
921         // want to make our on-screen rendering of footnotes
922         // etc. still break
923         if (!fullrow && body_pos && point < body_pos)
924                 point = body_pos - 1;
925
926         return point;
927 }
928
929
930 // returns the minimum space a row needs on the screen in pixel
931 int LyXText::fill(RowList::iterator row, int paper_width) const
932 {
933         if (paper_width < 0)
934                 return 0;
935
936         int w;
937         // get the pure distance
938         pos_type const last = lastPrintablePos(*this, row);
939
940         ParagraphList::iterator pit = row->par();
941         LyXLayout_ptr const & layout = pit->layout();
942
943         // special handling of the right address boxes
944         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
945                 int const tmpfill = row->fill();
946                 row->fill(0); // the minfill in MarginLeft()
947                 w = leftMargin(*row);
948                 row->fill(tmpfill);
949         } else
950                 w = leftMargin(*row);
951
952         pos_type const body_pos = pit->beginningOfBody();
953         pos_type i = row->pos();
954
955         if (! pit->empty() && i <= last) {
956                 // We re-use the font resolution for the entire span when possible
957                 LyXFont font = getFont(pit, i);
958                 lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
959                 while (i <= last) {
960                         if (body_pos > 0 && i == body_pos) {
961                                 w += font_metrics::width(layout->labelsep, getLabelFont(pit));
962                                 if (pit->isLineSeparator(i - 1))
963                                         w -= singleWidth(pit, i - 1);
964                                 int left_margin = labelEnd(*row);
965                                 if (w < left_margin)
966                                         w = left_margin;
967                         }
968                         { // Manual inlined an optimised version of the common case of "w += singleWidth(pit, i);"
969                                 char const c = pit->getChar(i);
970
971                                 if (IsPrintable(c)) {
972                                         if (i > endPosOfFontSpan) {
973                                                 // We need to get the next font
974                                                 font = getFont(pit, i);
975                                                 endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
976                                         }
977                                         if (!font.language()->RightToLeft()) {
978                                                 w += font_metrics::width(c, font);
979                                         } else {
980                                                 // Fall-back to the normal case
981                                                 w += singleWidth(pit, i, c);
982                                                 // And flush font cache
983                                                 endPosOfFontSpan = 0;
984                                         }
985                                 } else {
986                                         // Fall-back to the normal case
987                                         w += singleWidth(pit, i, c);
988                                         // And flush font cache
989                                         endPosOfFontSpan = 0;
990                                 }
991                         }
992                         ++i;
993                 }
994         }
995         if (body_pos > 0 && body_pos > last) {
996                 w += font_metrics::width(layout->labelsep, getLabelFont(pit));
997                 if (last >= 0 && pit->isLineSeparator(last))
998                         w -= singleWidth(pit, last);
999                 int const left_margin = labelEnd(*row);
1000                 if (w < left_margin)
1001                         w = left_margin;
1002         }
1003
1004         int const fill = paper_width - w - rightMargin(*bv()->buffer(), *row);
1005
1006         // If this case happens, it means that our calculation
1007         // of the widths of the chars when we do rowBreakPoint()
1008         // went wrong for some reason. Typically in list bodies.
1009         // Things just about hobble on anyway, though you'll end
1010         // up with a "fill_separator" less than zero, which corresponds
1011         // to inter-word spacing being too small. Hopefully this problem
1012         // will die when the label hacks die.
1013         if (lyxerr.debugging() && fill < 0) {
1014                 lyxerr[Debug::GUI] << "Eek, fill() was < 0: " << fill
1015                         << " w " << w << " paper_width " << paper_width
1016                         << " right margin " << rightMargin(*bv()->buffer(), *row) << endl;
1017         }
1018         return fill;
1019 }
1020
1021
1022 // returns the minimum space a manual label needs on the screen in pixel
1023 int LyXText::labelFill(Row const & row) const
1024 {
1025         ParagraphList::iterator pit = row.par();
1026
1027         pos_type last = pit->beginningOfBody();
1028
1029         Assert(last > 0);
1030
1031         // -1 because a label ends either with a space that is in the label,
1032         // or with the beginning of a footnote that is outside the label.
1033         --last;
1034
1035         // a separator at this end does not count
1036         if (pit->isLineSeparator(last))
1037                 --last;
1038
1039         int w = 0;
1040         pos_type i = row.pos();
1041         while (i <= last) {
1042                 w += singleWidth(pit, i);
1043                 ++i;
1044         }
1045
1046         int fill = 0;
1047         string const & labwidstr = pit->params().labelWidthString();
1048         if (!labwidstr.empty()) {
1049                 LyXFont const labfont = getLabelFont(pit);
1050                 int const labwidth = font_metrics::width(labwidstr, labfont);
1051                 fill = max(labwidth - w, 0);
1052         }
1053
1054         return fill;
1055 }
1056
1057
1058 LColor::color LyXText::backgroundColor() const
1059 {
1060         if (inset_owner)
1061                 return inset_owner->backgroundColor();
1062         else
1063                 return LColor::background;
1064 }
1065
1066
1067 void LyXText::setHeightOfRow(RowList::iterator rit)
1068 {
1069         Assert(rit != rows().end());
1070
1071         // get the maximum ascent and the maximum descent
1072         double layoutasc = 0;
1073         double layoutdesc = 0;
1074         double tmptop = 0;
1075
1076         // ok, let us initialize the maxasc and maxdesc value.
1077         // Only the fontsize count. The other properties
1078         // are taken from the layoutfont. Nicer on the screen :)
1079         ParagraphList::iterator pit = rit->par();
1080
1081         LyXLayout_ptr const & layout = pit->layout();
1082
1083         // as max get the first character of this row then it can increase but not
1084         // decrease the height. Just some point to start with so we don't have to
1085         // do the assignment below too often.
1086         LyXFont font = getFont(pit, rit->pos());
1087         LyXFont::FONT_SIZE const tmpsize = font.size();
1088         font = getLayoutFont(pit);
1089         LyXFont::FONT_SIZE const size = font.size();
1090         font.setSize(tmpsize);
1091
1092         LyXFont labelfont = getLabelFont(pit);
1093
1094         double spacing_val = 1.0;
1095         if (!pit->params().spacing().isDefault())
1096                 spacing_val = pit->params().spacing().getValue();
1097         else
1098                 spacing_val = bv()->buffer()->params.spacing.getValue();
1099         //lyxerr << "spacing_val = " << spacing_val << endl;
1100
1101         int maxasc  = int(font_metrics::maxAscent(font) *
1102                           layout->spacing.getValue() * spacing_val);
1103         int maxdesc = int(font_metrics::maxDescent(font) *
1104                           layout->spacing.getValue() * spacing_val);
1105
1106         pos_type const pos_end = lastPos(*this, rit);
1107         int labeladdon = 0;
1108         int maxwidth = 0;
1109
1110         if (!pit->empty()) {
1111                 // We re-use the font resolution for the entire font span when possible
1112                 LyXFont font = getFont(pit, rit->pos());
1113                 lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(rit->pos());
1114
1115                 // Optimisation
1116                 Paragraph const & par = *pit;
1117
1118                 // Check if any insets are larger
1119                 for (pos_type pos = rit->pos(); pos <= pos_end; ++pos) {
1120                         // Manual inlined optimised version of common case of
1121                         // "maxwidth += singleWidth(pit, pos);"
1122                         char const c = par.getChar(pos);
1123
1124                         if (IsPrintable(c)) {
1125                                 if (pos > endPosOfFontSpan) {
1126                                         // We need to get the next font
1127                                         font = getFont(pit, pos);
1128                                         endPosOfFontSpan = par.getEndPosOfFontSpan(pos);
1129                                 }
1130                                 if (! font.language()->RightToLeft()) {
1131                                         maxwidth += font_metrics::width(c, font);
1132                                 } else {
1133                                         // Fall-back to normal case
1134                                         maxwidth += singleWidth(pit, pos, c);
1135                                         // And flush font cache
1136                                         endPosOfFontSpan = 0;
1137                                 }
1138                         } else {
1139                                 // Special handling of insets - are any larger?
1140                                 if (par.isInset(pos)) {
1141                                         LyXFont const tmpfont = getFont(pit, pos);
1142                                         InsetOld const * tmpinset = par.getInset(pos);
1143                                         if (tmpinset) {
1144 #if 1 // this is needed for deep update on initialitation
1145 #warning inset->update FIXME
1146                                                 //tmpinset->update(bv());
1147                                                 Dimension dim;
1148                                                 MetricsInfo mi(bv(), tmpfont, workWidth());
1149                                                 tmpinset->metrics(mi, dim);
1150                                                 maxwidth += dim.wid;
1151                                                 maxasc = max(maxasc, dim.asc);
1152                                                 maxdesc = max(maxdesc, dim.des);
1153 #else
1154                                                 maxwidth += tmpinset->width();
1155                                                 maxasc = max(maxasc, tmpinset->ascent());
1156                                                 maxdesc = max(maxdesc, tmpinset->descent());
1157 #endif
1158                                         }
1159                                 } else {
1160                                         // Fall-back to normal case
1161                                         maxwidth += singleWidth(pit, pos, c);
1162                                         // And flush font cache
1163                                         endPosOfFontSpan = 0;
1164                                 }
1165                         }
1166                 }
1167         }
1168
1169         // Check if any custom fonts are larger (Asger)
1170         // This is not completely correct, but we can live with the small,
1171         // cosmetic error for now.
1172         LyXFont::FONT_SIZE maxsize =
1173                 pit->highestFontInRange(rit->pos(), pos_end, size);
1174         if (maxsize > font.size()) {
1175                 font.setSize(maxsize);
1176                 maxasc = max(maxasc, font_metrics::maxAscent(font));
1177                 maxdesc = max(maxdesc, font_metrics::maxDescent(font));
1178         }
1179
1180         // This is nicer with box insets:
1181         ++maxasc;
1182         ++maxdesc;
1183
1184         rit->ascent_of_text(maxasc);
1185
1186         // is it a top line?
1187         if (!rit->pos()) {
1188
1189                 // some parksips VERY EASY IMPLEMENTATION
1190                 if (bv()->buffer()->params.paragraph_separation ==
1191                         BufferParams::PARSEP_SKIP)
1192                 {
1193                         if (layout->isParagraph()
1194                                 && pit->getDepth() == 0
1195                                 && pit != ownerParagraphs().begin())
1196                         {
1197                                 maxasc += bv()->buffer()->params.getDefSkip().inPixels(*bv());
1198                         } else if (pit != ownerParagraphs().begin() &&
1199                                    boost::prior(pit)->layout()->isParagraph() &&
1200                                    boost::prior(pit)->getDepth() == 0)
1201                         {
1202                                 // is it right to use defskip here too? (AS)
1203                                 maxasc += bv()->buffer()->params.getDefSkip().inPixels(*bv());
1204                         }
1205                 }
1206
1207                 // the top margin
1208                 if (pit == ownerParagraphs().begin() && !isInInset())
1209                         maxasc += PAPER_MARGIN;
1210
1211                 // add the vertical spaces, that the user added
1212                 maxasc += getLengthMarkerHeight(*bv(), pit->params().spaceTop());
1213
1214                 // do not forget the DTP-lines!
1215                 // there height depends on the font of the nearest character
1216                 if (pit->params().lineTop())
1217
1218                         maxasc += 2 * font_metrics::ascent('x', getFont(pit, 0));
1219                 // and now the pagebreaks
1220                 if (pit->params().pagebreakTop())
1221                         maxasc += 3 * defaultRowHeight();
1222
1223                 if (pit->params().startOfAppendix())
1224                         maxasc += 3 * defaultRowHeight();
1225
1226                 // This is special code for the chapter, since the label of this
1227                 // layout is printed in an extra row
1228                 if (layout->labeltype == LABEL_COUNTER_CHAPTER
1229                         && bv()->buffer()->params.secnumdepth >= 0)
1230                 {
1231                         float spacing_val = 1.0;
1232                         if (!pit->params().spacing().isDefault()) {
1233                                 spacing_val = pit->params().spacing().getValue();
1234                         } else {
1235                                 spacing_val = bv()->buffer()->params.spacing.getValue();
1236                         }
1237
1238                         labeladdon = int(font_metrics::maxDescent(labelfont) *
1239                                          layout->spacing.getValue() *
1240                                          spacing_val)
1241                                 + int(font_metrics::maxAscent(labelfont) *
1242                                       layout->spacing.getValue() *
1243                                       spacing_val);
1244                 }
1245
1246                 // special code for the top label
1247                 if ((layout->labeltype == LABEL_TOP_ENVIRONMENT
1248                      || layout->labeltype == LABEL_BIBLIO
1249                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)
1250                     && isFirstInSequence(pit, ownerParagraphs())
1251                     && !pit->getLabelstring().empty())
1252                 {
1253                         float spacing_val = 1.0;
1254                         if (!pit->params().spacing().isDefault()) {
1255                                 spacing_val = pit->params().spacing().getValue();
1256                         } else {
1257                                 spacing_val = bv()->buffer()->params.spacing.getValue();
1258                         }
1259
1260                         labeladdon = int(
1261                                 (font_metrics::maxAscent(labelfont) *
1262                                  layout->spacing.getValue() *
1263                                  spacing_val)
1264                                 +(font_metrics::maxDescent(labelfont) *
1265                                   layout->spacing.getValue() *
1266                                   spacing_val)
1267                                 + layout->topsep * defaultRowHeight()
1268                                 + layout->labelbottomsep * defaultRowHeight());
1269                 }
1270
1271                 // And now the layout spaces, for example before and after
1272                 // a section, or between the items of a itemize or enumerate
1273                 // environment.
1274
1275                 if (!pit->params().pagebreakTop()) {
1276                         ParagraphList::iterator prev =
1277                                 depthHook(pit, ownerParagraphs(),
1278                                           pit->getDepth());
1279                         if (prev != pit && prev->layout() == layout &&
1280                                 prev->getDepth() == pit->getDepth() &&
1281                                 prev->getLabelWidthString() == pit->getLabelWidthString())
1282                         {
1283                                 layoutasc = (layout->itemsep * defaultRowHeight());
1284                         } else if (rit != rows().begin()) {
1285                                 tmptop = layout->topsep;
1286
1287                                 if (boost::prior(pit)->getDepth() >= pit->getDepth())
1288                                         tmptop -= boost::prior(rit)->par()->layout()->bottomsep;
1289
1290                                 if (tmptop > 0)
1291                                         layoutasc = (tmptop * defaultRowHeight());
1292                         } else if (pit->params().lineTop()) {
1293                                 tmptop = layout->topsep;
1294
1295                                 if (tmptop > 0)
1296                                         layoutasc = (tmptop * defaultRowHeight());
1297                         }
1298
1299                         prev = outerHook(pit, ownerParagraphs());
1300                         if (prev != ownerParagraphs().end())  {
1301                                 maxasc += int(prev->layout()->parsep * defaultRowHeight());
1302                         } else if (pit != ownerParagraphs().begin()) {
1303                                 ParagraphList::iterator prior_pit = boost::prior(pit);
1304                                 if (prior_pit->getDepth() != 0 ||
1305                                     prior_pit->layout() == layout) {
1306                                         maxasc += int(layout->parsep * defaultRowHeight());
1307                                 }
1308                         }
1309                 }
1310         }
1311
1312         // is it a bottom line?
1313         RowList::iterator next_rit = boost::next(rit);
1314         if (next_rit == rows().end() || next_rit->par() != pit) {
1315                 // the bottom margin
1316                 ParagraphList::iterator nextpit = boost::next(pit);
1317                 if (nextpit == ownerParagraphs().end() &&
1318                     !isInInset())
1319                         maxdesc += PAPER_MARGIN;
1320
1321                 // add the vertical spaces, that the user added
1322                 maxdesc += getLengthMarkerHeight(*bv(), pit->params().spaceBottom());
1323
1324                 // do not forget the DTP-lines!
1325                 // there height depends on the font of the nearest character
1326                 if (pit->params().lineBottom())
1327                         maxdesc += 2 * font_metrics::ascent('x',
1328                                         getFont(pit, max(pos_type(0), pit->size() - 1)));
1329
1330                 // and now the pagebreaks
1331                 if (pit->params().pagebreakBottom())
1332                         maxdesc += 3 * defaultRowHeight();
1333
1334                 // and now the layout spaces, for example before and after
1335                 // a section, or between the items of a itemize or enumerate
1336                 // environment
1337                 if (!pit->params().pagebreakBottom()
1338                     && nextpit != ownerParagraphs().end()) {
1339                         ParagraphList::iterator comparepit = pit;
1340                         float usual = 0;
1341                         float unusual = 0;
1342
1343                         if (comparepit->getDepth() > nextpit->getDepth()) {
1344                                 usual = (comparepit->layout()->bottomsep * defaultRowHeight());
1345                                 comparepit = depthHook(comparepit, ownerParagraphs(), nextpit->getDepth());
1346                                 if (comparepit->layout()!= nextpit->layout()
1347                                         || nextpit->getLabelWidthString() !=
1348                                         comparepit->getLabelWidthString())
1349                                 {
1350                                         unusual = (comparepit->layout()->bottomsep * defaultRowHeight());
1351                                 }
1352                                 if (unusual > usual)
1353                                         layoutdesc = unusual;
1354                                 else
1355                                         layoutdesc = usual;
1356                         } else if (comparepit->getDepth() ==  nextpit->getDepth()) {
1357
1358                                 if (comparepit->layout() != nextpit->layout()
1359                                         || nextpit->getLabelWidthString() !=
1360                                         comparepit->getLabelWidthString())
1361                                         layoutdesc = int(comparepit->layout()->bottomsep * defaultRowHeight());
1362                         }
1363                 }
1364         }
1365
1366         // incalculate the layout spaces
1367         maxasc += int(layoutasc * 2 / (2 + pit->getDepth()));
1368         maxdesc += int(layoutdesc * 2 / (2 + pit->getDepth()));
1369
1370         // calculate the new height of the text
1371         height -= rit->height();
1372
1373         rit->height(maxasc + maxdesc + labeladdon);
1374         rit->baseline(maxasc + labeladdon);
1375
1376         height += rit->height();
1377
1378         rit->top_of_text(rit->baseline() - font_metrics::maxAscent(font));
1379
1380         double x = 0;
1381         if (layout->margintype != MARGIN_RIGHT_ADDRESS_BOX) {
1382                 // this IS needed
1383                 rit->width(maxwidth);
1384                 double dummy;
1385                 prepareToPrint(rit, x, dummy, dummy, dummy, false);
1386         }
1387         rit->width(int(maxwidth + x));
1388         if (inset_owner) {
1389                 width = max(0, workWidth());
1390                 RowList::iterator it = rows().begin();
1391                 RowList::iterator end = rows().end();
1392                 for (; it != end; ++it)
1393                         if (it->width() > width)
1394                                 width = it->width();
1395         }
1396 }
1397
1398
1399 // Appends the implicit specified paragraph before the specified row,
1400 // start at the implicit given position
1401 void LyXText::appendParagraph(RowList::iterator rowit)
1402 {
1403         Assert(rowit != rowlist_.end());
1404
1405         pos_type const last = rowit->par()->size();
1406         bool done = false;
1407
1408         do {
1409                 pos_type z = rowBreakPoint(*rowit);
1410
1411                 RowList::iterator tmprow = rowit;
1412
1413                 if (z < last) {
1414                         ++z;
1415                         Row newrow(rowit->par(), z);
1416                         rowit = rowlist_.insert(boost::next(rowit), newrow);
1417                 } else {
1418                         done = true;
1419                 }
1420
1421                 // Set the dimensions of the row
1422                 // fixed fill setting now by calling inset->update() in
1423                 // SingleWidth when needed!
1424                 tmprow->fill(fill(tmprow, workWidth()));
1425                 setHeightOfRow(tmprow);
1426
1427         } while (!done);
1428 }
1429
1430
1431 void LyXText::breakAgain(RowList::iterator rit)
1432 {
1433         Assert(rit != rows().end());
1434
1435         bool not_ready = true;
1436
1437         do  {
1438                 pos_type z = rowBreakPoint(*rit);
1439                 RowList::iterator tmprit = rit;
1440                 RowList::iterator end = rows().end();
1441
1442                 if (z < rit->par()->size()) {
1443                         RowList::iterator next_rit = boost::next(rit);
1444
1445                         if (next_rit == end ||
1446                             (next_rit != end &&
1447                              next_rit->par() != rit->par())) {
1448                                 // insert a new row
1449                                 ++z;
1450                                 Row newrow(rit->par(), z);
1451                                 rit = rowlist_.insert(next_rit, newrow);
1452                         } else  {
1453                                 ++rit;
1454                                 ++z;
1455                                 if (rit->pos() == z)
1456                                         not_ready = false; // the rest will not change
1457                                 else {
1458                                         rit->pos(z);
1459                                 }
1460                         }
1461                 } else {
1462                         // if there are some rows too much, delete them
1463                         // only if you broke the whole paragraph!
1464                         RowList::iterator tmprit2 = rit;
1465                         while (boost::next(tmprit2) != end
1466                                && boost::next(tmprit2)->par() == rit->par()) {
1467                                 ++tmprit2;
1468                         }
1469                         while (tmprit2 != rit) {
1470                                 --tmprit2;
1471                                 removeRow(boost::next(tmprit2));
1472                         }
1473                         not_ready = false;
1474                 }
1475
1476                 // set the dimensions of the row
1477                 tmprit->fill(fill(tmprit, workWidth()));
1478                 setHeightOfRow(tmprit);
1479         } while (not_ready);
1480 }
1481
1482
1483 // this is just a little changed version of break again
1484 void LyXText::breakAgainOneRow(RowList::iterator rit)
1485 {
1486         Assert(rit != rows().end());
1487
1488         pos_type z = rowBreakPoint(*rit);
1489         RowList::iterator tmprit = rit;
1490         RowList::iterator end = rows().end();
1491
1492         if (z < rit->par()->size()) {
1493                 RowList::iterator next_rit = boost::next(rit);
1494
1495                 if (next_rit == end ||
1496                     (next_rit != end &&
1497                      next_rit->par() != rit->par())) {
1498                         // insert a new row
1499                         ++z;
1500                         Row newrow(rit->par(), z);
1501                         rit = rowlist_.insert(next_rit, newrow);
1502                 } else  {
1503                         ++rit;
1504                         ++z;
1505                         if (rit->pos() != z)
1506                                 rit->pos(z);
1507                 }
1508         } else {
1509                 // if there are some rows too much, delete them
1510                 // only if you broke the whole paragraph!
1511                 RowList::iterator tmprit2 = rit;
1512                 while (boost::next(tmprit2) != end
1513                        && boost::next(tmprit2)->par() == rit->par()) {
1514                         ++tmprit2;
1515                 }
1516                 while (tmprit2 != rit) {
1517                         --tmprit2;
1518                         removeRow(boost::next(tmprit2));
1519                 }
1520         }
1521
1522         // set the dimensions of the row
1523         tmprit->fill(fill(tmprit, workWidth()));
1524         setHeightOfRow(tmprit);
1525 }
1526
1527
1528 void LyXText::breakParagraph(ParagraphList & paragraphs, char keep_layout)
1529 {
1530         // allow only if at start or end, or all previous is new text
1531         if (cursor.pos() && cursor.pos() != cursor.par()->size()
1532                 && cursor.par()->isChangeEdited(0, cursor.pos()))
1533                 return;
1534
1535         LyXTextClass const & tclass =
1536                 bv()->buffer()->params.getLyXTextClass();
1537         LyXLayout_ptr const & layout = cursor.par()->layout();
1538
1539         // this is only allowed, if the current paragraph is not empty or caption
1540         // and if it has not the keepempty flag active
1541         if (cursor.par()->empty() && !cursor.par()->allowEmpty()
1542            && layout->labeltype != LABEL_SENSITIVE)
1543                 return;
1544
1545         recordUndo(bv(), Undo::ATOMIC, cursor.par());
1546
1547         // Always break behind a space
1548         //
1549         // It is better to erase the space (Dekel)
1550         if (cursor.pos() < cursor.par()->size()
1551              && cursor.par()->isLineSeparator(cursor.pos()))
1552            cursor.par()->erase(cursor.pos());
1553         // cursor.pos(cursor.pos() + 1);
1554
1555         // break the paragraph
1556         if (keep_layout)
1557                 keep_layout = 2;
1558         else
1559                 keep_layout = layout->isEnvironment();
1560
1561         // we need to set this before we insert the paragraph. IMO the
1562         // breakParagraph call should return a bool if it inserts the
1563         // paragraph before or behind and we should react on that one
1564         // but we can fix this in 1.3.0 (Jug 20020509)
1565         bool const isempty = (cursor.par()->allowEmpty() && cursor.par()->empty());
1566         ::breakParagraph(bv()->buffer()->params, paragraphs, cursor.par(),
1567                          cursor.pos(), keep_layout);
1568
1569         // well this is the caption hack since one caption is really enough
1570         if (layout->labeltype == LABEL_SENSITIVE) {
1571                 if (!cursor.pos())
1572                         // set to standard-layout
1573                         cursor.par()->applyLayout(tclass.defaultLayout());
1574                 else
1575                         // set to standard-layout
1576                         boost::next(cursor.par())->applyLayout(tclass.defaultLayout());
1577         }
1578
1579         // if the cursor is at the beginning of a row without prior newline,
1580         // move one row up!
1581         // This touches only the screen-update. Otherwise we would may have
1582         // an empty row on the screen
1583         if (cursor.pos() && cursorRow()->pos() == cursor.pos()
1584             && !cursorRow()->par()->isNewline(cursor.pos() - 1))
1585         {
1586                 cursorLeft(bv());
1587         }
1588
1589         postPaint();
1590
1591         removeParagraph(cursorRow());
1592
1593         // set the dimensions of the cursor row
1594         cursorRow()->fill(fill(cursorRow(), workWidth()));
1595
1596         setHeightOfRow(cursorRow());
1597
1598 #warning Trouble Point! (Lgb)
1599         // When ::breakParagraph is called from within an inset we must
1600         // ensure that the correct ParagraphList is used. Today that is not
1601         // the case and the Buffer::paragraphs is used. Not good. (Lgb)
1602         ParagraphList::iterator next_par = boost::next(cursor.par());
1603
1604         while (!next_par->empty() && next_par->isNewline(0))
1605                 next_par->erase(0);
1606
1607         insertParagraph(next_par, boost::next(cursorRow()));
1608         updateCounters();
1609
1610         // This check is necessary. Otherwise the new empty paragraph will
1611         // be deleted automatically. And it is more friendly for the user!
1612         if (cursor.pos() || isempty)
1613                 setCursor(next_par, 0);
1614         else
1615                 setCursor(cursor.par(), 0);
1616
1617         if (boost::next(cursorRow()) != rows().end())
1618                 breakAgain(boost::next(cursorRow()));
1619 }
1620
1621
1622 // Just a macro to make some thing easier.
1623 void LyXText::redoParagraph()
1624 {
1625         clearSelection();
1626         redoParagraphs(cursor, boost::next(cursor.par()));
1627         setCursorIntern(cursor.par(), cursor.pos());
1628 }
1629
1630
1631 // insert a character, moves all the following breaks in the
1632 // same Paragraph one to the right and make a rebreak
1633 void LyXText::insertChar(char c)
1634 {
1635         recordUndo(bv(), Undo::INSERT, cursor.par());
1636
1637         // When the free-spacing option is set for the current layout,
1638         // disable the double-space checking
1639
1640         bool const freeSpacing = cursorRow()->par()->layout()->free_spacing ||
1641                 cursorRow()->par()->isFreeSpacing();
1642
1643         if (lyxrc.auto_number) {
1644                 static string const number_operators = "+-/*";
1645                 static string const number_unary_operators = "+-";
1646                 static string const number_seperators = ".,:";
1647
1648                 if (current_font.number() == LyXFont::ON) {
1649                         if (!IsDigit(c) && !contains(number_operators, c) &&
1650                             !(contains(number_seperators, c) &&
1651                               cursor.pos() >= 1 &&
1652                               cursor.pos() < cursor.par()->size() &&
1653                               getFont(cursor.par(), cursor.pos()).number() == LyXFont::ON &&
1654                               getFont(cursor.par(), cursor.pos() - 1).number() == LyXFont::ON)
1655                            )
1656                                 number(bv()); // Set current_font.number to OFF
1657                 } else if (IsDigit(c) &&
1658                            real_current_font.isVisibleRightToLeft()) {
1659                         number(bv()); // Set current_font.number to ON
1660
1661                         if (cursor.pos() > 0) {
1662                                 char const c = cursor.par()->getChar(cursor.pos() - 1);
1663                                 if (contains(number_unary_operators, c) &&
1664                                     (cursor.pos() == 1 ||
1665                                      cursor.par()->isSeparator(cursor.pos() - 2) ||
1666                                      cursor.par()->isNewline(cursor.pos() - 2))
1667                                   ) {
1668                                         setCharFont(
1669                                                     cursor.par(),
1670                                                     cursor.pos() - 1,
1671                                                     current_font);
1672                                 } else if (contains(number_seperators, c) &&
1673                                            cursor.pos() >= 2 &&
1674                                            getFont(
1675                                                    cursor.par(),
1676                                                    cursor.pos() - 2).number() == LyXFont::ON) {
1677                                         setCharFont(
1678                                                     cursor.par(),
1679                                                     cursor.pos() - 1,
1680                                                     current_font);
1681                                 }
1682                         }
1683                 }
1684         }
1685
1686
1687         // First check, if there will be two blanks together or a blank at
1688         // the beginning of a paragraph.
1689         // I decided to handle blanks like normal characters, the main
1690         // difference are the special checks when calculating the row.fill
1691         // (blank does not count at the end of a row) and the check here
1692
1693         // The bug is triggered when we type in a description environment:
1694         // The current_font is not changed when we go from label to main text
1695         // and it should (along with realtmpfont) when we type the space.
1696         // CHECK There is a bug here! (Asger)
1697
1698         LyXFont realtmpfont = real_current_font;
1699         LyXFont rawtmpfont = current_font;
1700         // store the current font.  This is because of the use of cursor
1701         // movements. The moving cursor would refresh the current font
1702
1703         // Get the font that is used to calculate the baselineskip
1704         pos_type const lastpos = cursor.par()->size();
1705         LyXFont rawparfont =
1706                 cursor.par()->getFontSettings(bv()->buffer()->params,
1707                                               lastpos - 1);
1708
1709         bool jumped_over_space = false;
1710
1711         if (!freeSpacing && IsLineSeparatorChar(c)) {
1712                 if ((cursor.pos() > 0
1713                      && cursor.par()->isLineSeparator(cursor.pos() - 1))
1714                     || (cursor.pos() > 0
1715                         && cursor.par()->isNewline(cursor.pos() - 1))
1716                     || (cursor.pos() == 0)) {
1717                         static bool sent_space_message = false;
1718                         if (!sent_space_message) {
1719                                 if (cursor.pos() == 0)
1720                                         bv()->owner()->message(_("You cannot insert a space at the beginning of a paragraph. Please read the Tutorial."));
1721                                 else
1722                                         bv()->owner()->message(_("You cannot type two spaces this way. Please read the Tutorial."));
1723                                 sent_space_message = true;
1724                         }
1725                         charInserted();
1726                         return;
1727                 }
1728         }
1729
1730         // the display inset stuff
1731         if (cursorRow()->pos() < cursorRow()->par()->size()
1732             && cursorRow()->par()->isInset(cursorRow()->pos())) {
1733                 InsetOld * inset = cursorRow()->par()->getInset(cursorRow()->pos());
1734                 if (inset && (inset->display() || inset->needFullRow())) {
1735                         // force a new break
1736                         cursorRow()->fill(-1); // to force a new break
1737                 }
1738         }
1739
1740         // get the cursor row fist
1741         RowList::iterator row = cursorRow();
1742         if (c != Paragraph::META_INSET) {
1743                 // Here case LyXText::InsertInset  already inserted the character
1744                 cursor.par()->insertChar(cursor.pos(), c);
1745         }
1746         setCharFont(cursor.par(), cursor.pos(), rawtmpfont);
1747
1748         if (!jumped_over_space) {
1749                 // refresh the positions
1750                 RowList::iterator tmprow = row;
1751                 while (boost::next(tmprow) != rows().end() &&
1752                        boost::next(tmprow)->par() == row->par()) {
1753                         ++tmprow;
1754                         tmprow->pos(tmprow->pos() + 1);
1755                 }
1756         }
1757
1758         // Is there a break one row above
1759         if (row != rows().begin() &&
1760             boost::prior(row)->par() == row->par()
1761             && (cursor.par()->isLineSeparator(cursor.pos())
1762                 || cursor.par()->isNewline(cursor.pos())
1763                 || ((cursor.pos() + 1 < cursor.par()->size()) &&
1764                     cursor.par()->isInset(cursor.pos() + 1))
1765                 || cursorRow()->fill() == -1))
1766         {
1767                 pos_type z = rowBreakPoint(*boost::prior(row));
1768
1769                 if (z >= row->pos()) {
1770                         row->pos(z + 1);
1771
1772                         // set the dimensions of the row above
1773                         boost::prior(row)->fill(fill(
1774                                                    boost::prior(row),
1775                                                    workWidth()));
1776
1777                         setHeightOfRow(boost::prior(row));
1778
1779                         postPaint();
1780
1781                         breakAgainOneRow(row);
1782
1783                         current_font = rawtmpfont;
1784                         real_current_font = realtmpfont;
1785                         setCursor(cursor.par(), cursor.pos() + 1,
1786                                   false, cursor.boundary());
1787
1788                         // cursor MUST be in row now.
1789                         // check, wether the last characters font has changed.
1790                         if (cursor.pos() && cursor.pos() == cursor.par()->size()
1791                             && rawparfont != rawtmpfont)
1792                                 redoHeightOfParagraph();
1793
1794                         charInserted();
1795                         return;
1796                 }
1797         }
1798
1799         // recalculate the fill of the row
1800         if (row->fill() >= 0) {
1801                 // needed because a newline will set fill to -1. Otherwise
1802                 // we would not get a rebreak!
1803                 row->fill(fill(row, workWidth()));
1804         }
1805
1806         if (c == Paragraph::META_INSET || row->fill() < 0) {
1807                 postPaint();
1808                 breakAgainOneRow(row);
1809
1810                 RowList::iterator next_row = boost::next(row);
1811
1812                 // will the cursor be in another row now?
1813                 if (lastPos(*this, row) <= cursor.pos() + 1 &&
1814                     next_row != rows().end()) {
1815                         if (next_row != rows().end() &&
1816                             next_row->par() == row->par()) {
1817                                 // this should always be true
1818                                 ++row;
1819                         }
1820
1821                         breakAgainOneRow(row);
1822                 }
1823                 current_font = rawtmpfont;
1824                 real_current_font = realtmpfont;
1825
1826                 setCursor(cursor.par(), cursor.pos() + 1, false,
1827                           cursor.boundary());
1828                 if (isBoundary(bv()->buffer(), *cursor.par(), cursor.pos())
1829                     != cursor.boundary())
1830                         setCursor(cursor.par(), cursor.pos(), false,
1831                           !cursor.boundary());
1832         } else {
1833                 // FIXME: similar code is duplicated all over - make resetHeightOfRow
1834                 setHeightOfRow(row);
1835                 postPaint();
1836
1837                 current_font = rawtmpfont;
1838                 real_current_font = realtmpfont;
1839                 setCursor(cursor.par(), cursor.pos() + 1, false,
1840                           cursor.boundary());
1841         }
1842
1843         // check, wether the last characters font has changed.
1844         if (cursor.pos() && cursor.pos() == cursor.par()->size()
1845             && rawparfont != rawtmpfont) {
1846                 redoHeightOfParagraph();
1847         }
1848
1849         charInserted();
1850 }
1851
1852
1853 void LyXText::charInserted()
1854 {
1855         // Here we could call FinishUndo for every 20 characters inserted.
1856         // This is from my experience how emacs does it. (Lgb)
1857         static unsigned int counter;
1858         if (counter < 20) {
1859                 ++counter;
1860         } else {
1861                 finishUndo();
1862                 counter = 0;
1863         }
1864 }
1865
1866
1867 void LyXText::prepareToPrint(RowList::iterator rit, double & x,
1868                              double & fill_separator,
1869                              double & fill_hfill,
1870                              double & fill_label_hfill,
1871                              bool bidi) const
1872 {
1873         double w = rit->fill();
1874         fill_hfill = 0;
1875         fill_label_hfill = 0;
1876         fill_separator = 0;
1877         fill_label_hfill = 0;
1878
1879         ParagraphList::iterator pit = rit->par();
1880
1881         bool const is_rtl =
1882                 pit->isRightToLeftPar(bv()->buffer()->params);
1883         if (is_rtl)
1884                 x = workWidth() > 0 ? rightMargin(*bv()->buffer(), *rit) : 0;
1885         else
1886                 x = workWidth() > 0 ? leftMargin(*rit) : 0;
1887
1888         // is there a manual margin with a manual label
1889         LyXLayout_ptr const & layout = pit->layout();
1890
1891         if (layout->margintype == MARGIN_MANUAL
1892             && layout->labeltype == LABEL_MANUAL) {
1893                 /// We might have real hfills in the label part
1894                 int nlh = numberOfLabelHfills(*this, rit);
1895
1896                 // A manual label par (e.g. List) has an auto-hfill
1897                 // between the label text and the body of the
1898                 // paragraph too.
1899                 // But we don't want to do this auto hfill if the par
1900                 // is empty.
1901                 if (!pit->empty())
1902                         ++nlh;
1903
1904                 if (nlh && !pit->getLabelWidthString().empty()) {
1905                         fill_label_hfill = labelFill(*rit) / double(nlh);
1906                 }
1907         }
1908
1909         // are there any hfills in the row?
1910         int const nh = numberOfHfills(*this, rit);
1911
1912         if (nh) {
1913                 if (w > 0)
1914                         fill_hfill = w / nh;
1915         // we don't have to look at the alignment if it is ALIGN_LEFT and
1916         // if the row is already larger then the permitted width as then
1917         // we force the LEFT_ALIGN'edness!
1918         } else if (int(rit->width()) < workWidth()) {
1919                 // is it block, flushleft or flushright?
1920                 // set x how you need it
1921                 int align;
1922                 if (pit->params().align() == LYX_ALIGN_LAYOUT) {
1923                         align = layout->align;
1924                 } else {
1925                         align = pit->params().align();
1926                 }
1927
1928                 // center displayed insets
1929                 InsetOld * inset = 0;
1930                 if (rit->pos() < pit->size()
1931                     && pit->isInset(rit->pos())
1932                     && (inset = pit->getInset(rit->pos()))
1933                     && (inset->display())) // || (inset->scroll() < 0)))
1934                     align = (inset->lyxCode() == InsetOld::MATHMACRO_CODE)
1935                         ? LYX_ALIGN_BLOCK : LYX_ALIGN_CENTER;
1936                 // ERT insets should always be LEFT ALIGNED on screen
1937                 inset = pit->inInset();
1938                 if (inset && inset->owner() &&
1939                         inset->owner()->lyxCode() == InsetOld::ERT_CODE)
1940                 {
1941                         align = LYX_ALIGN_LEFT;
1942                 }
1943
1944                 switch (align) {
1945             case LYX_ALIGN_BLOCK:
1946             {
1947                         int const ns = numberOfSeparators(*this, rit);
1948                         RowList::iterator next_row = boost::next(rit);
1949                         ParagraphList::iterator next_pit = next_row->par();
1950
1951                         if (ns && next_row != rowlist_.end() &&
1952                             next_pit == pit &&
1953                             !(next_pit->isNewline(next_row->pos() - 1))
1954                             && !(next_pit->isInset(next_row->pos()) &&
1955                                  next_pit->getInset(next_row->pos()) &&
1956                                  next_pit->getInset(next_row->pos())->display())
1957                                 ) {
1958                                 fill_separator = w / ns;
1959                         } else if (is_rtl) {
1960                                 x += w;
1961                         }
1962                         break;
1963             }
1964             case LYX_ALIGN_RIGHT:
1965                         x += w;
1966                         break;
1967             case LYX_ALIGN_CENTER:
1968                         x += w / 2;
1969                         break;
1970                 }
1971         }
1972         if (!bidi)
1973                 return;
1974
1975         computeBidiTables(bv()->buffer(), rit);
1976         if (is_rtl) {
1977                 pos_type body_pos = pit->beginningOfBody();
1978                 pos_type last = lastPos(*this, rit);
1979
1980                 if (body_pos > 0 &&
1981                     (body_pos - 1 > last ||
1982                      !pit->isLineSeparator(body_pos - 1))) {
1983                         x += font_metrics::width(layout->labelsep, getLabelFont(pit));
1984                         if (body_pos - 1 <= last)
1985                                 x += fill_label_hfill;
1986                 }
1987         }
1988 }
1989
1990
1991 // important for the screen
1992
1993
1994 // the cursor set functions have a special mechanism. When they
1995 // realize, that you left an empty paragraph, they will delete it.
1996 // They also delete the corresponding row
1997
1998 void LyXText::cursorRightOneWord()
1999 {
2000         ::cursorRightOneWord(cursor, ownerParagraphs());
2001         setCursor(cursor.par(), cursor.pos());
2002 }
2003
2004
2005 // Skip initial whitespace at end of word and move cursor to *start*
2006 // of prior word, not to end of next prior word.
2007 void LyXText::cursorLeftOneWord()
2008 {
2009         LyXCursor tmpcursor = cursor;
2010         ::cursorLeftOneWord(tmpcursor, ownerParagraphs());
2011         setCursor(tmpcursor.par(), tmpcursor.pos());
2012 }
2013
2014
2015 void LyXText::selectWord(word_location loc)
2016 {
2017         LyXCursor from = cursor;
2018         LyXCursor to;
2019         ::getWord(from, to, loc, ownerParagraphs());
2020         if (cursor != from)
2021                 setCursor(from.par(), from.pos());
2022         if (to == from)
2023                 return;
2024         selection.cursor = cursor;
2025         setCursor(to.par(), to.pos());
2026         setSelection();
2027 }
2028
2029
2030 // Select the word currently under the cursor when no
2031 // selection is currently set
2032 bool LyXText::selectWordWhenUnderCursor(word_location loc)
2033 {
2034         if (!selection.set()) {
2035                 selectWord(loc);
2036                 return selection.set();
2037         }
2038         return false;
2039 }
2040
2041
2042 void LyXText::acceptChange()
2043 {
2044         if (!selection.set() && cursor.par()->size())
2045                 return;
2046
2047         if (selection.start.par() == selection.end.par()) {
2048                 LyXCursor & startc = selection.start;
2049                 LyXCursor & endc = selection.end;
2050                 recordUndo(bv(), Undo::INSERT, startc.par());
2051                 startc.par()->acceptChange(startc.pos(), endc.pos());
2052                 finishUndo();
2053                 clearSelection();
2054                 redoParagraphs(startc, boost::next(startc.par()));
2055                 setCursorIntern(startc.par(), 0);
2056         }
2057 #warning handle multi par selection
2058 }
2059
2060
2061 void LyXText::rejectChange()
2062 {
2063         if (!selection.set() && cursor.par()->size())
2064                 return;
2065
2066         if (selection.start.par() == selection.end.par()) {
2067                 LyXCursor & startc = selection.start;
2068                 LyXCursor & endc = selection.end;
2069                 recordUndo(bv(), Undo::INSERT, startc.par());
2070                 startc.par()->rejectChange(startc.pos(), endc.pos());
2071                 finishUndo();
2072                 clearSelection();
2073                 redoParagraphs(startc, boost::next(startc.par()));
2074                 setCursorIntern(startc.par(), 0);
2075         }
2076 #warning handle multi par selection
2077 }
2078
2079
2080 // This function is only used by the spellchecker for NextWord().
2081 // It doesn't handle LYX_ACCENTs and probably never will.
2082 WordLangTuple const
2083 LyXText::selectNextWordToSpellcheck(float & value)
2084 {
2085         if (the_locking_inset) {
2086                 WordLangTuple word = the_locking_inset->selectNextWordToSpellcheck(bv(), value);
2087                 if (!word.word().empty()) {
2088                         value += float(cursor.y());
2089                         value /= float(height);
2090                         return word;
2091                 }
2092                 // we have to go on checking so move cursor to the next char
2093                 if (cursor.pos() == cursor.par()->size()) {
2094                         if (boost::next(cursor.par()) == ownerParagraphs().end())
2095                                 return word;
2096                         cursor.par(boost::next(cursor.par()));
2097                         cursor.pos(0);
2098                 } else
2099                         cursor.pos(cursor.pos() + 1);
2100         }
2101         ParagraphList::iterator tmppit = cursor.par();
2102
2103         // If this is not the very first word, skip rest of
2104         // current word because we are probably in the middle
2105         // of a word if there is text here.
2106         if (cursor.pos() || cursor.par() != ownerParagraphs().begin()) {
2107                 while (cursor.pos() < cursor.par()->size()
2108                        && cursor.par()->isLetter(cursor.pos()))
2109                         cursor.pos(cursor.pos() + 1);
2110         }
2111
2112         // Now, skip until we have real text (will jump paragraphs)
2113         while (true) {
2114                 ParagraphList::iterator cpit = cursor.par();
2115                 pos_type const cpos(cursor.pos());
2116
2117                 if (cpos == cpit->size()) {
2118                         if (boost::next(cpit) != ownerParagraphs().end()) {
2119                                 cursor.par(boost::next(cpit));
2120                                 cursor.pos(0);
2121                                 continue;
2122                         }
2123                         break;
2124                 }
2125
2126                 bool const is_good_inset = cpit->isInset(cpos)
2127                         && cpit->getInset(cpos)->allowSpellcheck();
2128
2129                 if (!isDeletedText(*cpit, cpos)
2130                     && (is_good_inset || cpit->isLetter(cpos)))
2131                         break;
2132
2133                 cursor.pos(cpos + 1);
2134         }
2135
2136         // now check if we hit an inset so it has to be a inset containing text!
2137         if (cursor.pos() < cursor.par()->size() &&
2138             cursor.par()->isInset(cursor.pos())) {
2139                 // lock the inset!
2140                 FuncRequest cmd(bv(), LFUN_INSET_EDIT, "left");
2141                 cursor.par()->getInset(cursor.pos())->localDispatch(cmd);
2142                 // now call us again to do the above trick
2143                 // but obviously we have to start from down below ;)
2144                 return bv()->text->selectNextWordToSpellcheck(value);
2145         }
2146
2147         // Update the value if we changed paragraphs
2148         if (cursor.par() != tmppit) {
2149                 setCursor(cursor.par(), cursor.pos());
2150                 value = float(cursor.y())/float(height);
2151         }
2152
2153         // Start the selection from here
2154         selection.cursor = cursor;
2155
2156         string lang_code = getFont(cursor.par(), cursor.pos()).language()->code();
2157         // and find the end of the word (insets like optional hyphens
2158         // and ligature break are part of a word)
2159         while (cursor.pos() < cursor.par()->size()
2160                && cursor.par()->isLetter(cursor.pos())
2161                && !isDeletedText(*cursor.par(), cursor.pos()))
2162                 cursor.pos(cursor.pos() + 1);
2163
2164         // Finally, we copy the word to a string and return it
2165         string str;
2166         if (selection.cursor.pos() < cursor.pos()) {
2167                 pos_type i;
2168                 for (i = selection.cursor.pos(); i < cursor.pos(); ++i) {
2169                         if (!cursor.par()->isInset(i))
2170                                 str += cursor.par()->getChar(i);
2171                 }
2172         }
2173         return WordLangTuple(str, lang_code);
2174 }
2175
2176
2177 // This one is also only for the spellchecker
2178 void LyXText::selectSelectedWord()
2179 {
2180         if (the_locking_inset) {
2181                 the_locking_inset->selectSelectedWord(bv());
2182                 return;
2183         }
2184         // move cursor to the beginning
2185         setCursor(selection.cursor.par(), selection.cursor.pos());
2186
2187         // set the sel cursor
2188         selection.cursor = cursor;
2189
2190         // now find the end of the word
2191         while (cursor.pos() < cursor.par()->size()
2192                && (cursor.par()->isLetter(cursor.pos())))
2193                 cursor.pos(cursor.pos() + 1);
2194
2195         setCursor(cursor.par(), cursor.pos());
2196
2197         // finally set the selection
2198         setSelection();
2199 }
2200
2201
2202 // Delete from cursor up to the end of the current or next word.
2203 void LyXText::deleteWordForward()
2204 {
2205         if (cursor.par()->empty())
2206                 cursorRight(bv());
2207         else {
2208                 LyXCursor tmpcursor = cursor;
2209                 selection.set(true); // to avoid deletion
2210                 cursorRightOneWord();
2211                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
2212                 selection.cursor = cursor;
2213                 cursor = tmpcursor;
2214                 setSelection();
2215
2216                 // Great, CutSelection() gets rid of multiple spaces.
2217                 cutSelection(true, false);
2218         }
2219 }
2220
2221
2222 // Delete from cursor to start of current or prior word.
2223 void LyXText::deleteWordBackward()
2224 {
2225         if (cursor.par()->empty())
2226                 cursorLeft(bv());
2227         else {
2228                 LyXCursor tmpcursor = cursor;
2229                 selection.set(true); // to avoid deletion
2230                 cursorLeftOneWord();
2231                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
2232                 selection.cursor = cursor;
2233                 cursor = tmpcursor;
2234                 setSelection();
2235                 cutSelection(true, false);
2236         }
2237 }
2238
2239
2240 // Kill to end of line.
2241 void LyXText::deleteLineForward()
2242 {
2243         if (cursor.par()->empty())
2244                 // Paragraph is empty, so we just go to the right
2245                 cursorRight(bv());
2246         else {
2247                 LyXCursor tmpcursor = cursor;
2248                 // We can't store the row over a regular setCursor
2249                 // so we set it to 0 and reset it afterwards.
2250                 selection.set(true); // to avoid deletion
2251                 cursorEnd();
2252                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
2253                 selection.cursor = cursor;
2254                 cursor = tmpcursor;
2255                 setSelection();
2256                 // What is this test for ??? (JMarc)
2257                 if (!selection.set()) {
2258                         deleteWordForward();
2259                 } else {
2260                         cutSelection(true, false);
2261                 }
2262         }
2263 }
2264
2265
2266 void LyXText::changeCase(LyXText::TextCase action)
2267 {
2268         LyXCursor from;
2269         LyXCursor to;
2270
2271         if (selection.set()) {
2272                 from = selection.start;
2273                 to = selection.end;
2274         } else {
2275                 from = cursor;
2276                 ::getWord(from, to, lyx::PARTIAL_WORD, ownerParagraphs());
2277                 setCursor(to.par(), to.pos() + 1);
2278         }
2279
2280         recordUndo(bv(), Undo::ATOMIC, from.par(), to.par());
2281
2282         pos_type pos = from.pos();
2283         ParagraphList::iterator pit = from.par();
2284
2285         while (pit != ownerParagraphs().end() &&
2286                (pos != to.pos() || pit != to.par())) {
2287                 if (pos == pit->size()) {
2288                         ++pit;
2289                         pos = 0;
2290                         continue;
2291                 }
2292                 unsigned char c = pit->getChar(pos);
2293                 if (!IsInsetChar(c)) {
2294                         switch (action) {
2295                         case text_lowercase:
2296                                 c = lowercase(c);
2297                                 break;
2298                         case text_capitalization:
2299                                 c = uppercase(c);
2300                                 action = text_lowercase;
2301                                 break;
2302                         case text_uppercase:
2303                                 c = uppercase(c);
2304                                 break;
2305                         }
2306                 }
2307 #warning changes
2308                 pit->setChar(pos, c);
2309                 checkParagraph(pit, pos);
2310
2311                 ++pos;
2312         }
2313
2314         if (getRow(to) != getRow(from))
2315                 postPaint();
2316 }
2317
2318
2319 void LyXText::Delete()
2320 {
2321         // this is a very easy implementation
2322
2323         LyXCursor old_cursor = cursor;
2324         int const old_cur_par_id = old_cursor.par()->id();
2325         int const old_cur_par_prev_id =
2326                 (old_cursor.par() != ownerParagraphs().begin() ?
2327                  boost::prior(old_cursor.par())->id() : -1);
2328
2329         // just move to the right
2330         cursorRight(bv());
2331
2332         // CHECK Look at the comment here.
2333         // This check is not very good...
2334         // The cursorRightIntern calls DeleteEmptyParagrapgMechanism
2335         // and that can very well delete the par or par->previous in
2336         // old_cursor. Will a solution where we compare paragraph id's
2337         //work better?
2338         if ((cursor.par() != ownerParagraphs().begin() ? boost::prior(cursor.par())->id() : -1)
2339             == old_cur_par_prev_id
2340             && cursor.par()->id() != old_cur_par_id) {
2341                 // delete-empty-paragraph-mechanism has done it
2342                 return;
2343         }
2344
2345         // if you had success make a backspace
2346         if (old_cursor.par() != cursor.par() || old_cursor.pos() != cursor.pos()) {
2347                 LyXCursor tmpcursor = cursor;
2348                 // to make sure undo gets the right cursor position
2349                 cursor = old_cursor;
2350                 recordUndo(bv(), Undo::DELETE, cursor.par());
2351                 cursor = tmpcursor;
2352                 backspace();
2353         }
2354 }
2355
2356
2357 void LyXText::backspace()
2358 {
2359         // Get the font that is used to calculate the baselineskip
2360         pos_type lastpos = cursor.par()->size();
2361         LyXFont rawparfont =
2362                 cursor.par()->getFontSettings(bv()->buffer()->params,
2363                                               lastpos - 1);
2364
2365         if (cursor.pos() == 0) {
2366                 // The cursor is at the beginning of a paragraph,
2367                 // so the the backspace will collapse two paragraphs into one.
2368
2369                 // but it's not allowed unless it's new
2370                 if (cursor.par()->isChangeEdited(0, cursor.par()->size()))
2371                         return;
2372
2373                 // we may paste some paragraphs
2374
2375                 // is it an empty paragraph?
2376
2377                 if ((lastpos == 0
2378                      || (lastpos == 1 && cursor.par()->isSeparator(0)))) {
2379                         // This is an empty paragraph and we delete it just by moving the cursor one step
2380                         // left and let the DeleteEmptyParagraphMechanism handle the actual deletion
2381                         // of the paragraph.
2382
2383                         if (cursor.par() != ownerParagraphs().begin()) {
2384                                 ParagraphList::iterator tmppit = boost::prior(cursor.par());
2385                                 if (cursor.par()->layout() == tmppit->layout()
2386                                     && cursor.par()->getAlign() == tmppit->getAlign()) {
2387                                         // Inherit bottom DTD from the paragraph below.
2388                                         // (the one we are deleting)
2389                                         tmppit->params().lineBottom(cursor.par()->params().lineBottom());
2390                                         tmppit->params().spaceBottom(cursor.par()->params().spaceBottom());
2391                                         tmppit->params().pagebreakBottom(cursor.par()->params().pagebreakBottom());
2392                                 }
2393
2394                                 cursorLeft(bv());
2395
2396                                 // the layout things can change the height of a row !
2397                                 int const tmpheight = cursorRow()->height();
2398                                 setHeightOfRow(cursorRow());
2399                                 if (cursorRow()->height() != tmpheight)
2400                                         postPaint();
2401                                 return;
2402                         }
2403                 }
2404
2405                 if (cursor.par() != ownerParagraphs().begin()) {
2406                         recordUndo(bv(), Undo::DELETE,
2407                                 boost::prior(cursor.par()),
2408                                 cursor.par());
2409                 }
2410
2411                 ParagraphList::iterator tmppit = cursor.par();
2412                 RowList::iterator tmprow = cursorRow();
2413
2414                 // We used to do cursorLeftIntern() here, but it is
2415                 // not a good idea since it triggers the auto-delete
2416                 // mechanism. So we do a cursorLeftIntern()-lite,
2417                 // without the dreaded mechanism. (JMarc)
2418                 if (cursor.par() != ownerParagraphs().begin()) {
2419                         // steps into the above paragraph.
2420                         setCursorIntern(boost::prior(cursor.par()),
2421                                         boost::prior(cursor.par())->size(),
2422                                         false);
2423                 }
2424
2425                 // Pasting is not allowed, if the paragraphs have different
2426                 // layout. I think it is a real bug of all other
2427                 // word processors to allow it. It confuses the user.
2428                 // Even so with a footnote paragraph and a non-footnote
2429                 // paragraph. I will not allow pasting in this case,
2430                 // because the user would be confused if the footnote behaves
2431                 // different wether it is open or closed.
2432
2433                 //      Correction: Pasting is always allowed with standard-layout
2434                 LyXTextClass const & tclass =
2435                         bv()->buffer()->params.getLyXTextClass();
2436
2437                 if (cursor.par() != tmppit
2438                     && (cursor.par()->layout() == tmppit->layout()
2439                         || tmppit->layout() == tclass.defaultLayout())
2440                     && cursor.par()->getAlign() == tmppit->getAlign()) {
2441                         removeParagraph(tmprow);
2442                         removeRow(tmprow);
2443                         mergeParagraph(bv()->buffer()->params, bv()->buffer()->paragraphs, cursor.par());
2444
2445                         if (!cursor.pos() || !cursor.par()->isSeparator(cursor.pos() - 1))
2446                                 ; //cursor.par()->insertChar(cursor.pos(), ' ');
2447                         // strangely enough it seems that commenting out the line above removes
2448                         // most or all of the segfaults. I will however also try to move the
2449                         // two Remove... lines in front of the PasteParagraph too.
2450                         else
2451                                 if (cursor.pos())
2452                                         cursor.pos(cursor.pos() - 1);
2453
2454                         postPaint();
2455
2456                         // remove the lost paragraph
2457                         // This one is not safe, since the paragraph that the tmprow and the
2458                         // following rows belong to has been deleted by the PasteParagraph
2459                         // above. The question is... could this be moved in front of the
2460                         // PasteParagraph?
2461                         //RemoveParagraph(tmprow);
2462                         //RemoveRow(tmprow);
2463
2464                         // This rebuilds the rows.
2465                         appendParagraph(cursorRow());
2466                         updateCounters();
2467
2468                         // the row may have changed, block, hfills etc.
2469                         setCursor(cursor.par(), cursor.pos(), false);
2470                 }
2471         } else {
2472                 // this is the code for a normal backspace, not pasting
2473                 // any paragraphs
2474                 recordUndo(bv(), Undo::DELETE, cursor.par());
2475                 // We used to do cursorLeftIntern() here, but it is
2476                 // not a good idea since it triggers the auto-delete
2477                 // mechanism. So we do a cursorLeftIntern()-lite,
2478                 // without the dreaded mechanism. (JMarc)
2479                 setCursorIntern(cursor.par(), cursor.pos()- 1,
2480                                 false, cursor.boundary());
2481
2482                 if (cursor.par()->isInset(cursor.pos())) {
2483                         // force complete redo when erasing display insets
2484                         // this is a cruel method but safe..... Matthias
2485                         if (cursor.par()->getInset(cursor.pos())->display() ||
2486                             cursor.par()->getInset(cursor.pos())->needFullRow()) {
2487                                 cursor.par()->erase(cursor.pos());
2488                                 redoParagraph();
2489                                 return;
2490                         }
2491                 }
2492
2493                 RowList::iterator row = cursorRow();
2494                 int y = cursor.y() - row->baseline();
2495                 pos_type z;
2496                 // remember that a space at the end of a row doesnt count
2497                 // when calculating the fill
2498                 if (cursor.pos() < lastPos(*this, row) ||
2499                     !cursor.par()->isLineSeparator(cursor.pos())) {
2500                         row->fill(row->fill() + singleWidth(
2501                                                             cursor.par(),
2502                                                             cursor.pos()));
2503                 }
2504
2505                 // some special code when deleting a newline. This is similar
2506                 // to the behavior when pasting paragraphs
2507                 if (cursor.pos() && cursor.par()->isNewline(cursor.pos())) {
2508                         cursor.par()->erase(cursor.pos());
2509                         // refresh the positions
2510                         RowList::iterator tmprow = row;
2511                         while (boost::next(tmprow) != rows().end() &&
2512                                boost::next(tmprow)->par() == row->par()) {
2513                                 ++tmprow;
2514                                 tmprow->pos(tmprow->pos() - 1);
2515                         }
2516                         if (cursor.par()->isLineSeparator(cursor.pos() - 1))
2517                                 cursor.pos(cursor.pos() - 1);
2518
2519                         if (cursor.pos() < cursor.par()->size()
2520                             && !cursor.par()->isSeparator(cursor.pos())) {
2521                                 cursor.par()->insertChar(cursor.pos(), ' ');
2522                                 setCharFont(cursor.par(), cursor.pos(), current_font);
2523                                 // refresh the positions
2524                                 tmprow = row;
2525                                 while (boost::next(tmprow) != rows().end() &&
2526                                        boost::next(tmprow)->par() == row->par()) {
2527                                         ++tmprow;
2528                                         tmprow->pos(tmprow->pos() + 1);
2529                                 }
2530                         }
2531                 } else {
2532                         cursor.par()->erase(cursor.pos());
2533
2534                         // refresh the positions
2535                         RowList::iterator tmprow = row;
2536                         while (boost::next(tmprow) != rows().end() &&
2537                                boost::next(tmprow)->par() == row->par()) {
2538                                 ++tmprow;
2539                                 tmprow->pos(tmprow->pos() - 1);
2540                         }
2541
2542                         // delete newlines at the beginning of paragraphs
2543                         while (!cursor.par()->empty() &&
2544                                cursor.pos() < cursor.par()->size() &&
2545                                cursor.par()->isNewline(cursor.pos()) &&
2546                                cursor.pos() == cursor.par()->beginningOfBody()) {
2547                                 cursor.par()->erase(cursor.pos());
2548                                 // refresh the positions
2549                                 tmprow = row;
2550                                 while (boost::next(tmprow) != rows().end() &&
2551                                        boost::next(tmprow)->par() == row->par()) {
2552                                         ++tmprow;
2553                                         tmprow->pos(tmprow->pos() - 1);
2554                                 }
2555                         }
2556                 }
2557
2558                 // is there a break one row above
2559                 if (row != rows().begin() && boost::prior(row)->par() == row->par()) {
2560                         z = rowBreakPoint(*boost::prior(row));
2561                         if (z >= row->pos()) {
2562                                 row->pos(z + 1);
2563
2564                                 RowList::iterator tmprow = boost::prior(row);
2565
2566                                 // maybe the current row is now empty
2567                                 if (row->pos() >= row->par()->size()) {
2568                                         // remove it
2569                                         removeRow(row);
2570                                 } else {
2571                                         breakAgainOneRow(row);
2572                                 }
2573
2574                                 // set the dimensions of the row above
2575                                 y -= tmprow->height();
2576                                 tmprow->fill(fill(tmprow, workWidth()));
2577                                 setHeightOfRow(tmprow);
2578                                 postPaint();
2579
2580                                 setCursor(cursor.par(), cursor.pos(),
2581                                           false, cursor.boundary());
2582                                 //current_font = rawtmpfont;
2583                                 //real_current_font = realtmpfont;
2584                                 // check, whether the last character's font has changed.
2585                                 if (rawparfont !=
2586                                     cursor.par()->getFontSettings(bv()->buffer()->params,
2587                                                                   cursor.par()->size() - 1))
2588                                         redoHeightOfParagraph();
2589                                 return;
2590                         }
2591                 }
2592
2593                 // break the cursor row again
2594                 if (boost::next(row) != rows().end() &&
2595                     boost::next(row)->par() == row->par() &&
2596                     (lastPos(*this, row) == row->par()->size() - 1 ||
2597                      rowBreakPoint(*row) != lastPos(*this, row))) {
2598
2599                         // it can happen that a paragraph loses one row
2600                         // without a real breakup. This is when a word
2601                         // is to long to be broken. Well, I don t care this
2602                         // hack ;-)
2603                         if (lastPos(*this, row) == row->par()->size() - 1)
2604                                 removeRow(boost::next(row));
2605
2606                         postPaint();
2607
2608                         breakAgainOneRow(row);
2609                         // will the cursor be in another row now?
2610                         if (boost::next(row) != rows().end() &&
2611                             boost::next(row)->par() == row->par() &&
2612                             lastPos(*this, row) <= cursor.pos()) {
2613                                 ++row;
2614                                 breakAgainOneRow(row);
2615                         }
2616
2617                         setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
2618                 } else  {
2619                         // set the dimensions of the row
2620                         row->fill(fill(row, workWidth()));
2621                         setHeightOfRow(row);
2622                         postPaint();
2623                         setCursor(cursor.par(), cursor.pos(), false, cursor.boundary());
2624                 }
2625         }
2626
2627         // current_font = rawtmpfont;
2628         // real_current_font = realtmpfont;
2629
2630         if (isBoundary(bv()->buffer(), *cursor.par(), cursor.pos())
2631             != cursor.boundary())
2632                 setCursor(cursor.par(), cursor.pos(), false, !cursor.boundary());
2633
2634         lastpos = cursor.par()->size();
2635         if (cursor.pos() == lastpos)
2636                 setCurrentFont();
2637
2638         // check, whether the last characters font has changed.
2639         if (rawparfont !=
2640             cursor.par()->getFontSettings(bv()->buffer()->params, lastpos - 1)) {
2641                 redoHeightOfParagraph();
2642         }
2643 }
2644
2645
2646 RowList::iterator LyXText::cursorRow() const
2647 {
2648         return getRow(cursor.par(), cursor.pos());
2649 }
2650
2651
2652 RowList::iterator LyXText::getRow(LyXCursor const & cur) const
2653 {
2654         return getRow(cur.par(), cur.pos());
2655 }
2656
2657
2658 RowList::iterator
2659 LyXText::getRow(ParagraphList::iterator pit, pos_type pos) const
2660 {
2661         if (rows().empty())
2662                 return rowlist_.end();
2663
2664         // find the first row of the specified paragraph
2665         RowList::iterator rit = rowlist_.begin();
2666         RowList::iterator end = rowlist_.end();
2667         while (boost::next(rit) != end && rit->par() != pit) {
2668                 ++rit;
2669         }
2670
2671         // now find the wanted row
2672         while (rit->pos() < pos
2673                && boost::next(rit) != end
2674                && boost::next(rit)->par() == pit
2675                && boost::next(rit)->pos() <= pos) {
2676                 ++rit;
2677         }
2678
2679         return rit;
2680 }
2681
2682
2683 // returns pointer to a specified row
2684 RowList::iterator
2685 LyXText::getRow(ParagraphList::iterator pit, pos_type pos, int & y) const
2686 {
2687         y = 0;
2688
2689         if (rows().empty())
2690                 return rowlist_.end();
2691
2692         // find the first row of the specified paragraph
2693         RowList::iterator rit = rowlist_.begin();
2694         RowList::iterator end = rowlist_.end();
2695         while (boost::next(rit) != end && rit->par() != pit) {
2696                 y += rit->height();
2697                 ++rit;
2698         }
2699
2700         // now find the wanted row
2701         while (rit->pos() < pos
2702                && boost::next(rit) != end
2703                && boost::next(rit)->par() == pit
2704                && boost::next(rit)->pos() <= pos) {
2705                 y += rit->height();
2706                 ++rit;
2707         }
2708
2709         return rit;
2710 }
2711
2712
2713 // returns pointer to some fancy row 'below' specified row
2714 RowList::iterator LyXText::cursorIRow() const
2715 {
2716         int y = 0;
2717         return getRow(cursor.par(), cursor.pos(), y);
2718 }
2719
2720
2721 RowList::iterator LyXText::getRowNearY(int & y) const
2722 {
2723         RowList::iterator rit = anchor_row_;
2724         RowList::iterator const beg = rows().begin();
2725         RowList::iterator const end = rows().end();
2726
2727         if (rows().empty()) {
2728                 y = 0;
2729                 return end;
2730         }
2731         if (rit == end)
2732                 rit = beg;
2733
2734         int tmpy = rit->y();
2735
2736         if (tmpy <= y) {
2737                 while (rit != end && tmpy <= y) {
2738                         tmpy += rit->height();
2739                         ++rit;
2740                 }
2741                 if (rit != beg) {
2742                         --rit;
2743                         tmpy -= rit->height();
2744                 }
2745         } else {
2746                 while (rit != beg && tmpy > y) {
2747                         --rit;
2748                         tmpy -= rit->height();
2749                 }
2750         }
2751         if (tmpy < 0 || rit == end) {
2752                 tmpy = 0;
2753                 rit = beg;
2754         }
2755
2756         // return the rel y
2757         y = tmpy;
2758
2759         return rit;
2760 }
2761
2762
2763 int LyXText::getDepth() const
2764 {
2765         return cursor.par()->getDepth();
2766 }