]> git.lyx.org Git - lyx.git/blob - src/text.C
Remove a couple of #includes from buffer.h
[lyx.git] / src / text.C
1 /**
2  * \file text.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author John Levon
10  * \author André Pönitz
11  * \author Dekel Tsur
12  * \author Jürgen Vigna
13  *
14  * Full author contact details are available in file CREDITS.
15  */
16
17 #include <config.h>
18
19 #include "lyxtext.h"
20 #include "gettext.h"
21 #include "buffer.h"
22 #include "debug.h"
23 #include "encoding.h"
24 #include "funcrequest.h"
25 #include "frontends/LyXView.h"
26 #include "frontends/font_metrics.h"
27 #include "BufferView.h"
28 #include "language.h"
29 #include "ParagraphParameters.h"
30 #include "undo_funcs.h"
31 #include "text_funcs.h"
32 #include "WordLangTuple.h"
33 #include "paragraph_funcs.h"
34 #include "rowpainter.h"
35 #include "lyxrow.h"
36 #include "lyxrow_funcs.h"
37
38 #include "insets/insettext.h"
39
40 #include "support/textutils.h"
41 #include "support/LAssert.h"
42 #include "support/lstrings.h"
43
44
45 using namespace lyx::support;
46
47 using std::max;
48 using std::min;
49 using std::endl;
50 using std::pair;
51
52 using lyx::pos_type;
53 using lyx::word_location;
54
55 using namespace bv_funcs;
56
57 /// top, right, bottom pixel margin
58 extern int const PAPER_MARGIN = 20;
59 /// margin for changebar
60 extern int const CHANGEBAR_MARGIN = 10;
61 /// left margin
62 extern int const LEFT_MARGIN = PAPER_MARGIN + CHANGEBAR_MARGIN;
63
64
65
66 int bibitemMaxWidth(BufferView *, LyXFont const &);
67
68
69 BufferView * LyXText::bv()
70 {
71         Assert(bv_owner != 0);
72         return bv_owner;
73 }
74
75
76 BufferView * LyXText::bv() const
77 {
78         Assert(bv_owner != 0);
79         return bv_owner;
80 }
81
82
83 void LyXText::updateRowPositions()
84 {
85         ParagraphList::iterator pit = ownerParagraphs().begin();
86         ParagraphList::iterator end = ownerParagraphs().end();
87         for (height = 0; pit != end; ++pit) {
88                 RowList::iterator rit = pit->rows.begin();
89                 RowList::iterator rend = pit->rows.end();
90                 for ( ; rit != rend ; rit = ++rit) {
91                         rit->y(height);
92                         height += rit->height();
93                 }
94         }
95 }
96
97
98 int LyXText::workWidth() const
99 {
100         return inset_owner ? inset_owner->textWidth() : bv()->workWidth();
101 }
102
103
104 int LyXText::getRealCursorX() const
105 {
106         int x = cursor.x();
107         if (the_locking_inset && (the_locking_inset->getLyXText(bv())!= this))
108                 x = the_locking_inset->getLyXText(bv())->getRealCursorX();
109         return x;
110 }
111
112
113 #warning FIXME  This function seems to belong outside of LyxText.
114 unsigned char LyXText::transformChar(unsigned char c, Paragraph const & par,
115                                      pos_type pos) const
116 {
117         if (!Encodings::is_arabic(c))
118                 if (lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 && IsDigit(c))
119                         return c + (0xb0 - '0');
120                 else
121                         return c;
122
123         unsigned char const prev_char = pos > 0 ? par.getChar(pos - 1) : ' ';
124         unsigned char next_char = ' ';
125
126         pos_type const par_size = par.size();
127
128         for (pos_type i = pos + 1; i < par_size; ++i) {
129                 unsigned char const par_char = par.getChar(i);
130                 if (!Encodings::IsComposeChar_arabic(par_char)) {
131                         next_char = par_char;
132                         break;
133                 }
134         }
135
136         if (Encodings::is_arabic(next_char)) {
137                 if (Encodings::is_arabic(prev_char) &&
138                         !Encodings::is_arabic_special(prev_char))
139                         return Encodings::TransformChar(c, Encodings::FORM_MEDIAL);
140                 else
141                         return Encodings::TransformChar(c, Encodings::FORM_INITIAL);
142         } else {
143                 if (Encodings::is_arabic(prev_char) &&
144                         !Encodings::is_arabic_special(prev_char))
145                         return Encodings::TransformChar(c, Encodings::FORM_FINAL);
146                 else
147                         return Encodings::TransformChar(c, Encodings::FORM_ISOLATED);
148         }
149 }
150
151 // This is the comments that some of the warnings below refers to.
152 // There are some issues in this file and I don't think they are
153 // really related to the FIX_DOUBLE_SPACE patch. I'd rather think that
154 // this is a problem that has been here almost from day one and that a
155 // larger userbase with differenct access patters triggers the bad
156 // behaviour. (segfaults.) What I think happen is: In several places
157 // we store the paragraph in the current cursor and then moves the
158 // cursor. This movement of the cursor will delete paragraph at the
159 // old position if it is now empty. This will make the temporary
160 // pointer to the old cursor paragraph invalid and dangerous to use.
161 // And is some cases this will trigger a segfault. I have marked some
162 // of the cases where this happens with a warning, but I am sure there
163 // are others in this file and in text2.C. There is also a note in
164 // Delete() that you should read. In Delete I store the paragraph->id
165 // instead of a pointer to the paragraph. I am pretty sure this faulty
166 // use of temporary pointers to paragraphs that might have gotten
167 // invalidated (through a cursor movement) before they are used, are
168 // the cause of the strange crashes we get reported often.
169 //
170 // It is very tiresom to change this code, especially when it is as
171 // hard to read as it is. Help to fix all the cases where this is done
172 // would be greately appreciated.
173 //
174 // Lgb
175
176 int LyXText::singleWidth(ParagraphList::iterator pit, pos_type pos) const
177 {
178         if (pos >= pit->size())
179                 return 0;
180
181         char const c = pit->getChar(pos);
182         LyXFont const & font = getFont(pit, pos);
183         return singleWidth(pit, pos, c, font);
184 }
185
186
187 int LyXText::singleWidth(ParagraphList::iterator pit,
188                          pos_type pos, char c, LyXFont const & font) const
189 {
190         if (pos >= pit->size()) {
191                 lyxerr << "in singleWidth(), pos: " << pos << endl;
192                 Assert(false);
193                 return 0;
194         }
195
196
197         // The most common case is handled first (Asger)
198         if (IsPrintable(c)) {
199                 if (font.language()->RightToLeft()) {
200                         if ((lyxrc.font_norm_type == LyXRC::ISO_8859_6_8 ||
201                              lyxrc.font_norm_type == LyXRC::ISO_10646_1)
202                             && font.language()->lang() == "arabic") {
203                                 if (Encodings::IsComposeChar_arabic(c))
204                                         return 0;
205                                 else
206                                         c = transformChar(c, *pit, pos);
207                         } else if (font.language()->lang() == "hebrew" &&
208                                  Encodings::IsComposeChar_hebrew(c))
209                                 return 0;
210                 }
211                 return font_metrics::width(c, font);
212         }
213
214         if (c == Paragraph::META_INSET) {
215                 InsetOld * tmpinset = pit->getInset(pos);
216                 Assert(tmpinset);
217                 if (tmpinset->lyxCode() == InsetOld::HFILL_CODE) {
218                         // Because of the representation as vertical lines
219                         return 3;
220                 }
221                 return tmpinset->width();
222         }
223
224         if (IsSeparatorChar(c))
225                 c = ' ';
226         return font_metrics::width(c, font);
227 }
228
229
230 lyx::pos_type LyXText::log2vis(lyx::pos_type pos) const
231 {
232         if (bidi_start == -1)
233                 return pos;
234         else
235                 return log2vis_list[pos - bidi_start];
236 }
237
238
239 lyx::pos_type LyXText::vis2log(lyx::pos_type pos) const
240 {
241         if (bidi_start == -1)
242                 return pos;
243         else
244                 return vis2log_list[pos - bidi_start];
245 }
246
247
248 lyx::pos_type LyXText::bidi_level(lyx::pos_type pos) const
249 {
250         if (bidi_start == -1)
251                 return 0;
252         else
253                 return bidi_levels[pos - bidi_start];
254 }
255
256
257 bool LyXText::bidi_InRange(lyx::pos_type pos) const
258 {
259         return bidi_start == -1 ||
260                 (bidi_start <= pos && pos <= bidi_end);
261 }
262
263
264 void LyXText::computeBidiTables(ParagraphList::iterator pit,
265    Buffer const & buf, RowList::iterator row) const
266 {
267         bidi_same_direction = true;
268         if (!lyxrc.rtl_support) {
269                 bidi_start = -1;
270                 return;
271         }
272
273         InsetOld * inset = pit->inInset();
274         if (inset && inset->owner() &&
275             inset->owner()->lyxCode() == InsetOld::ERT_CODE) {
276                 bidi_start = -1;
277                 return;
278         }
279
280         bidi_start = row->pos();
281         bidi_end = lastPos(*pit, row);
282
283         if (bidi_start > bidi_end) {
284                 bidi_start = -1;
285                 return;
286         }
287
288         if (bidi_end + 2 - bidi_start >
289             static_cast<pos_type>(log2vis_list.size())) {
290                 pos_type new_size =
291                         (bidi_end + 2 - bidi_start < 500) ?
292                         500 : 2 * (bidi_end + 2 - bidi_start);
293                 log2vis_list.resize(new_size);
294                 vis2log_list.resize(new_size);
295                 bidi_levels.resize(new_size);
296         }
297
298         vis2log_list[bidi_end + 1 - bidi_start] = -1;
299         log2vis_list[bidi_end + 1 - bidi_start] = -1;
300
301         pos_type stack[2];
302         bool const rtl_par =
303                 pit->isRightToLeftPar(buf.params);
304         int level = 0;
305         bool rtl = false;
306         bool rtl0 = false;
307         pos_type const body_pos = pit->beginningOfBody();
308
309         for (pos_type lpos = bidi_start; lpos <= bidi_end; ++lpos) {
310                 bool is_space = pit->isLineSeparator(lpos);
311                 pos_type const pos =
312                         (is_space && lpos + 1 <= bidi_end &&
313                          !pit->isLineSeparator(lpos + 1) &&
314                          !pit->isNewline(lpos + 1))
315                         ? lpos + 1 : lpos;
316                 LyXFont font = pit->getFontSettings(buf.params, pos);
317                 if (pos != lpos && 0 < lpos && rtl0 && font.isRightToLeft() &&
318                     font.number() == LyXFont::ON &&
319                     pit->getFontSettings(buf.params, lpos - 1).number()
320                     == LyXFont::ON) {
321                         font = pit->getFontSettings(buf.params, lpos);
322                         is_space = false;
323                 }
324
325
326                 bool new_rtl = font.isVisibleRightToLeft();
327                 bool new_rtl0 = font.isRightToLeft();
328                 int new_level;
329
330                 if (lpos == body_pos - 1
331                     && row->pos() < body_pos - 1
332                     && is_space) {
333                         new_level = (rtl_par) ? 1 : 0;
334                         new_rtl = new_rtl0 = rtl_par;
335                 } else if (new_rtl0)
336                         new_level = (new_rtl) ? 1 : 2;
337                 else
338                         new_level = (rtl_par) ? 2 : 0;
339
340                 if (is_space && new_level >= level) {
341                         new_level = level;
342                         new_rtl = rtl;
343                         new_rtl0 = rtl0;
344                 }
345
346                 int new_level2 = new_level;
347
348                 if (level == new_level && rtl0 != new_rtl0) {
349                         --new_level2;
350                         log2vis_list[lpos - bidi_start] = (rtl) ? 1 : -1;
351                 } else if (level < new_level) {
352                         log2vis_list[lpos - bidi_start] =  (rtl) ? -1 : 1;
353                         if (new_level > rtl_par)
354                                 bidi_same_direction = false;
355                 } else
356                         log2vis_list[lpos - bidi_start] = (new_rtl) ? -1 : 1;
357                 rtl = new_rtl;
358                 rtl0 = new_rtl0;
359                 bidi_levels[lpos - bidi_start] = new_level;
360
361                 while (level > new_level2) {
362                         pos_type old_lpos = stack[--level];
363                         int delta = lpos - old_lpos - 1;
364                         if (level % 2)
365                                 delta = -delta;
366                         log2vis_list[lpos - bidi_start] += delta;
367                         log2vis_list[old_lpos - bidi_start] += delta;
368                 }
369                 while (level < new_level)
370                         stack[level++] = lpos;
371         }
372
373         while (level > 0) {
374                 pos_type const old_lpos = stack[--level];
375                 int delta = bidi_end - old_lpos;
376                 if (level % 2)
377                         delta = -delta;
378                 log2vis_list[old_lpos - bidi_start] += delta;
379         }
380
381         pos_type vpos = bidi_start - 1;
382         for (pos_type lpos = bidi_start;
383              lpos <= bidi_end; ++lpos) {
384                 vpos += log2vis_list[lpos - bidi_start];
385                 vis2log_list[vpos - bidi_start] = lpos;
386                 log2vis_list[lpos - bidi_start] = vpos;
387         }
388 }
389
390
391 // This method requires a previous call to ComputeBidiTables()
392 bool LyXText::isBoundary(Buffer const & buf, Paragraph const & par,
393                          pos_type pos) const
394 {
395         if (!lyxrc.rtl_support || pos == 0)
396                 return false;
397
398         if (!bidi_InRange(pos - 1)) {
399                 /// This can happen if pos is the first char of a row.
400                 /// Returning false in this case is incorrect!
401                 return false;
402         }
403
404         bool const rtl = bidi_level(pos - 1) % 2;
405         bool const rtl2 = bidi_InRange(pos)
406                 ? bidi_level(pos) % 2
407                 : par.isRightToLeftPar(buf.params);
408         return rtl != rtl2;
409 }
410
411
412 bool LyXText::isBoundary(Buffer const & buf, Paragraph const & par,
413                          pos_type pos, LyXFont const & font) const
414 {
415         if (!lyxrc.rtl_support)
416                 return false;    // This is just for speedup
417
418         bool const rtl = font.isVisibleRightToLeft();
419         bool const rtl2 = bidi_InRange(pos)
420                 ? bidi_level(pos) % 2
421                 : par.isRightToLeftPar(buf.params);
422         return rtl != rtl2;
423 }
424
425
426 int LyXText::leftMargin(ParagraphList::iterator pit, Row const & row) const
427 {
428         LyXTextClass const & tclass =
429                 bv()->buffer()->params.getLyXTextClass();
430         LyXLayout_ptr const & layout = pit->layout();
431
432         string parindent = layout->parindent;
433
434         int x = LEFT_MARGIN;
435
436         x += font_metrics::signedWidth(tclass.leftmargin(), tclass.defaultfont());
437
438         // this is the way, LyX handles the LaTeX-Environments.
439         // I have had this idea very late, so it seems to be a
440         // later added hack and this is true
441         if (!pit->getDepth()) {
442                 if (pit->layout() == tclass.defaultLayout()) {
443                         // find the previous same level paragraph
444                         if (pit != ownerParagraphs().begin()) {
445                                 ParagraphList::iterator newpit =
446                                         depthHook(pit, ownerParagraphs(),
447                                                   pit->getDepth());
448                                 if (newpit == pit &&
449                                     newpit->layout()->nextnoindent)
450                                         parindent.erase();
451                         }
452                 }
453         } else {
454                 // find the next level paragraph
455
456                 ParagraphList::iterator newpar = outerHook(pit,
457                                                            ownerParagraphs());
458
459                 // make a corresponding row. Needed to call leftMargin()
460
461                 // check wether it is a sufficent paragraph
462                 if (newpar != ownerParagraphs().end() &&
463                     newpar->layout()->isEnvironment()) {
464                         x = leftMargin(newpar, Row(newpar->size()));
465                 }
466
467                 if (newpar != ownerParagraphs().end() &&
468                     pit->layout() == tclass.defaultLayout()) {
469                         if (newpar->params().noindent())
470                                 parindent.erase();
471                         else {
472                                 parindent = newpar->layout()->parindent;
473                         }
474
475                 }
476         }
477
478         LyXFont const labelfont = getLabelFont(pit);
479         switch (layout->margintype) {
480         case MARGIN_DYNAMIC:
481                 if (!layout->leftmargin.empty()) {
482                         x += font_metrics::signedWidth(layout->leftmargin,
483                                                   tclass.defaultfont());
484                 }
485                 if (!pit->getLabelstring().empty()) {
486                         x += font_metrics::signedWidth(layout->labelindent,
487                                                   labelfont);
488                         x += font_metrics::width(pit->getLabelstring(),
489                                             labelfont);
490                         x += font_metrics::width(layout->labelsep, labelfont);
491                 }
492                 break;
493         case MARGIN_MANUAL:
494                 x += font_metrics::signedWidth(layout->labelindent, labelfont);
495                 // The width of an empty par, even with manual label, should be 0
496                 if (!pit->empty() && row.pos() >= pit->beginningOfBody()) {
497                         if (!pit->getLabelWidthString().empty()) {
498                                 x += font_metrics::width(pit->getLabelWidthString(),
499                                                labelfont);
500                                 x += font_metrics::width(layout->labelsep, labelfont);
501                         }
502                 }
503                 break;
504         case MARGIN_STATIC:
505                 x += font_metrics::signedWidth(layout->leftmargin, tclass.defaultfont()) * 4
506                         / (pit->getDepth() + 4);
507                 break;
508         case MARGIN_FIRST_DYNAMIC:
509                 if (layout->labeltype == LABEL_MANUAL) {
510                         if (row.pos() >= pit->beginningOfBody()) {
511                                 x += font_metrics::signedWidth(layout->leftmargin,
512                                                           labelfont);
513                         } else {
514                                 x += font_metrics::signedWidth(layout->labelindent,
515                                                           labelfont);
516                         }
517                 } else if (row.pos()
518                            // Special case to fix problems with
519                            // theorems (JMarc)
520                            || (layout->labeltype == LABEL_STATIC
521                                && layout->latextype == LATEX_ENVIRONMENT
522                                && !isFirstInSequence(pit, ownerParagraphs()))) {
523                         x += font_metrics::signedWidth(layout->leftmargin,
524                                                   labelfont);
525                 } else if (layout->labeltype != LABEL_TOP_ENVIRONMENT
526                            && layout->labeltype != LABEL_BIBLIO
527                            && layout->labeltype !=
528                            LABEL_CENTERED_TOP_ENVIRONMENT) {
529                         x += font_metrics::signedWidth(layout->labelindent,
530                                                   labelfont);
531                         x += font_metrics::width(layout->labelsep, labelfont);
532                         x += font_metrics::width(pit->getLabelstring(),
533                                             labelfont);
534                 }
535                 break;
536
537         case MARGIN_RIGHT_ADDRESS_BOX:
538         {
539                 // ok, a terrible hack. The left margin depends on the widest
540                 // row in this paragraph. Do not care about footnotes, they
541                 // are *NOT* allowed in the LaTeX realisation of this layout.
542
543                 // find the first row of this paragraph
544                 RowList::iterator rit = pit->rows.begin();
545                 RowList::iterator end = pit->rows.end();
546                 int minfill = rit->fill();
547                 for ( ; rit != end; ++rit)
548                         if (rit->fill() < minfill)
549                                 minfill = rit->fill();
550
551                 x += font_metrics::signedWidth(layout->leftmargin,
552                         tclass.defaultfont());
553                 x += minfill;
554         }
555         break;
556         }
557
558         if (workWidth() > 0 && !pit->params().leftIndent().zero()) {
559                 LyXLength const len = pit->params().leftIndent();
560                 int const tw = inset_owner ?
561                         inset_owner->latexTextWidth(bv()) : workWidth();
562                 x += len.inPixels(tw);
563         }
564
565         LyXAlignment align;
566
567         if (pit->params().align() == LYX_ALIGN_LAYOUT)
568                 align = layout->align;
569         else
570                 align = pit->params().align();
571
572         // set the correct parindent
573         if (row.pos() == 0) {
574                 if ((layout->labeltype == LABEL_NO_LABEL
575                      || layout->labeltype == LABEL_TOP_ENVIRONMENT
576                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT
577                      || (layout->labeltype == LABEL_STATIC
578                          && layout->latextype == LATEX_ENVIRONMENT
579                          && !isFirstInSequence(pit, ownerParagraphs())))
580                     && align == LYX_ALIGN_BLOCK
581                     && !pit->params().noindent()
582                         // in tabulars and ert paragraphs are never indented!
583                         && (!pit->inInset() || !pit->inInset()->owner() ||
584                                 (pit->inInset()->owner()->lyxCode() != InsetOld::TABULAR_CODE &&
585                                  pit->inInset()->owner()->lyxCode() != InsetOld::ERT_CODE))
586                     && (pit->layout() != tclass.defaultLayout() ||
587                         bv()->buffer()->params.paragraph_separation ==
588                         BufferParams::PARSEP_INDENT)) {
589                         x += font_metrics::signedWidth(parindent,
590                                                   tclass.defaultfont());
591                 } else if (layout->labeltype == LABEL_BIBLIO) {
592                         // ale970405 Right width for bibitems
593                         x += bibitemMaxWidth(bv(), tclass.defaultfont());
594                 }
595         }
596
597         return x;
598 }
599
600
601 int LyXText::rightMargin(ParagraphList::iterator pit,
602         Buffer const & buf, Row const &) const
603 {
604         LyXTextClass const & tclass = buf.params.getLyXTextClass();
605         LyXLayout_ptr const & layout = pit->layout();
606
607         return PAPER_MARGIN
608                 + font_metrics::signedWidth(tclass.rightmargin(),
609                                        tclass.defaultfont());
610                 + font_metrics::signedWidth(layout->rightmargin,
611                                        tclass.defaultfont())
612                 * 4 / (pit->getDepth() + 4);
613 }
614
615
616 int LyXText::labelEnd(ParagraphList::iterator pit, Row const & row) const
617 {
618         if (pit->layout()->margintype == MARGIN_MANUAL) {
619                 Row tmprow = row;
620                 tmprow.pos(pit->size());
621                 // return the beginning of the body
622                 return leftMargin(pit, tmprow);
623         }
624
625         // LabelEnd is only needed if the layout
626         // fills a flushleft label.
627         return 0;
628 }
629
630
631 namespace {
632
633 // this needs special handling - only newlines count as a break point
634 pos_type addressBreakPoint(pos_type i, Paragraph const & par)
635 {
636         for (; i < par.size(); ++i) {
637                 if (par.isNewline(i))
638                         return i;
639         }
640
641         return par.size();
642 }
643
644 };
645
646
647 pos_type LyXText::rowBreakPoint(ParagraphList::iterator pit,
648         Row const & row) const
649 {
650         // maximum pixel width of a row.
651         int width = workWidth()
652                 - rightMargin(pit, *bv()->buffer(), row);
653
654         // inset->textWidth() returns -1 via workWidth(),
655         // but why ?
656         if (width < 0)
657                 return pit->size();
658
659         LyXLayout_ptr const & layout = pit->layout();
660
661         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX)
662                 return addressBreakPoint(row.pos(), *pit);
663
664         pos_type const pos = row.pos();
665         pos_type const body_pos = pit->beginningOfBody();
666         pos_type const last = pit->size();
667         pos_type point = last;
668
669         if (pos == last)
670                 return last;
671
672         // Now we iterate through until we reach the right margin
673         // or the end of the par, then choose the possible break
674         // nearest that.
675
676         int const left = leftMargin(pit, row);
677         int x = left;
678
679         // pixel width since last breakpoint
680         int chunkwidth = 0;
681
682         pos_type i = pos;
683
684         // We re-use the font resolution for the entire font span when possible
685         LyXFont font = getFont(pit, i);
686         lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
687
688         for (; i < last; ++i) {
689                 if (pit->isNewline(i)) {
690                         point = i;
691                         break;
692                 }
693
694                 char const c = pit->getChar(i);
695                 if (i > endPosOfFontSpan) {
696                         font = getFont(pit, i);
697                         endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
698                 }
699
700                 int thiswidth;
701
702                 // add the auto-hfill from label end to the body
703                 if (body_pos && i == body_pos) {
704                         thiswidth = font_metrics::width(layout->labelsep, getLabelFont(pit));
705                         if (pit->isLineSeparator(i - 1))
706                                 thiswidth -= singleWidth(pit, i - 1);
707                         int left_margin = labelEnd(pit, row);
708                         if (thiswidth + x < left_margin)
709                                 thiswidth = left_margin - x;
710                         thiswidth += singleWidth(pit, i, c, font);
711                 } else {
712                         thiswidth = singleWidth(pit, i, c, font);
713                 }
714
715                 x += thiswidth;
716                 chunkwidth += thiswidth;
717
718                 InsetOld * in = pit->isInset(i) ? pit->getInset(i) : 0;
719
720                 // break before a character that will fall off
721                 // the right of the row
722                 if (x >= width) {
723                         // if no break before, break here
724                         if (point == last || chunkwidth >= (width - left)) {
725                                 if (pos < i)
726                                         point = i - 1;
727                                 else
728                                         point = i;
729                         }
730                         break;
731                 }
732
733                 if (!in || in->isChar()) {
734                         // some insets are line separators too
735                         if (pit->isLineSeparator(i)) {
736                                 point = i;
737                                 chunkwidth = 0;
738                         }
739                         continue;
740                 }
741
742                 continue;
743         }
744
745         if (point == last && x >= width) {
746                 // didn't find one, break at the point we reached the edge
747                 point = i;
748         } else if (i == last && x < width) {
749                 // found one, but we fell off the end of the par, so prefer
750                 // that.
751                 point = last;
752         }
753
754         // manual labels cannot be broken in LaTeX. But we
755         // want to make our on-screen rendering of footnotes
756         // etc. still break
757         if (body_pos && point < body_pos)
758                 point = body_pos - 1;
759
760         return point;
761 }
762
763
764 // returns the minimum space a row needs on the screen in pixel
765 int LyXText::fill(ParagraphList::iterator pit,
766         RowList::iterator row, int paper_width) const
767 {
768         if (paper_width < 0)
769                 return 0;
770
771         int w;
772         // get the pure distance
773         pos_type const last = lastPos(*pit, row);
774
775         LyXLayout_ptr const & layout = pit->layout();
776
777         // special handling of the right address boxes
778         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX) {
779                 int const tmpfill = row->fill();
780                 row->fill(0); // the minfill in MarginLeft()
781                 w = leftMargin(pit, *row);
782                 row->fill(tmpfill);
783         } else
784                 w = leftMargin(pit, *row);
785
786         pos_type const body_pos = pit->beginningOfBody();
787         pos_type i = row->pos();
788
789         if (! pit->empty() && i <= last) {
790                 // We re-use the font resolution for the entire span when possible
791                 LyXFont font = getFont(pit, i);
792                 lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
793                 while (i <= last) {
794                         if (body_pos > 0 && i == body_pos) {
795                                 w += font_metrics::width(layout->labelsep, getLabelFont(pit));
796                                 if (pit->isLineSeparator(i - 1))
797                                         w -= singleWidth(pit, i - 1);
798                                 int left_margin = labelEnd(pit, *row);
799                                 if (w < left_margin)
800                                         w = left_margin;
801                         }
802                         char const c = pit->getChar(i);
803                         if (IsPrintable(c) && i > endPosOfFontSpan) {
804                                 // We need to get the next font
805                                 font = getFont(pit, i);
806                                 endPosOfFontSpan = pit->getEndPosOfFontSpan(i);
807                         }
808                         w += singleWidth(pit, i, c, font);
809                         ++i;
810                 }
811         }
812         if (body_pos > 0 && body_pos > last) {
813                 w += font_metrics::width(layout->labelsep, getLabelFont(pit));
814                 if (last >= 0 && pit->isLineSeparator(last))
815                         w -= singleWidth(pit, last);
816                 int const left_margin = labelEnd(pit, *row);
817                 if (w < left_margin)
818                         w = left_margin;
819         }
820
821         int const fill = paper_width - w - rightMargin(pit, *bv()->buffer(), *row);
822
823         // If this case happens, it means that our calculation
824         // of the widths of the chars when we do rowBreakPoint()
825         // went wrong for some reason. Typically in list bodies.
826         // Things just about hobble on anyway, though you'll end
827         // up with a "fill_separator" less than zero, which corresponds
828         // to inter-word spacing being too small. Hopefully this problem
829         // will die when the label hacks die.
830         if (lyxerr.debugging() && fill < 0) {
831                 lyxerr[Debug::GUI] << "Eek, fill() was < 0: " << fill
832                         << " w " << w << " paper_width " << paper_width
833                         << " right margin " << rightMargin(pit, *bv()->buffer(), *row) << endl;
834         }
835         return fill;
836 }
837
838
839 // returns the minimum space a manual label needs on the screen in pixel
840 int LyXText::labelFill(ParagraphList::iterator pit, Row const & row) const
841 {
842         pos_type last = pit->beginningOfBody();
843
844         Assert(last > 0);
845
846         // -1 because a label ends either with a space that is in the label,
847         // or with the beginning of a footnote that is outside the label.
848         --last;
849
850         // a separator at this end does not count
851         if (pit->isLineSeparator(last))
852                 --last;
853
854         int w = 0;
855         pos_type i = row.pos();
856         while (i <= last) {
857                 w += singleWidth(pit, i);
858                 ++i;
859         }
860
861         int fill = 0;
862         string const & labwidstr = pit->params().labelWidthString();
863         if (!labwidstr.empty()) {
864                 LyXFont const labfont = getLabelFont(pit);
865                 int const labwidth = font_metrics::width(labwidstr, labfont);
866                 fill = max(labwidth - w, 0);
867         }
868
869         return fill;
870 }
871
872
873 LColor::color LyXText::backgroundColor() const
874 {
875         if (inset_owner)
876                 return inset_owner->backgroundColor();
877         else
878                 return LColor::background;
879 }
880
881
882 void LyXText::setHeightOfRow(ParagraphList::iterator pit, RowList::iterator rit)
883 {
884         // get the maximum ascent and the maximum descent
885         double layoutasc = 0;
886         double layoutdesc = 0;
887         double tmptop = 0;
888
889         // ok, let us initialize the maxasc and maxdesc value.
890         // Only the fontsize count. The other properties
891         // are taken from the layoutfont. Nicer on the screen :)
892         LyXLayout_ptr const & layout = pit->layout();
893
894         // as max get the first character of this row then it can increase but not
895         // decrease the height. Just some point to start with so we don't have to
896         // do the assignment below too often.
897         LyXFont font = getFont(pit, rit->pos());
898         LyXFont::FONT_SIZE const tmpsize = font.size();
899         font = getLayoutFont(pit);
900         LyXFont::FONT_SIZE const size = font.size();
901         font.setSize(tmpsize);
902
903         LyXFont labelfont = getLabelFont(pit);
904
905         double spacing_val = 1.0;
906         if (!pit->params().spacing().isDefault())
907                 spacing_val = pit->params().spacing().getValue();
908         else
909                 spacing_val = bv()->buffer()->params.spacing.getValue();
910         //lyxerr << "spacing_val = " << spacing_val << endl;
911
912         int maxasc  = int(font_metrics::maxAscent(font) *
913                           layout->spacing.getValue() * spacing_val);
914         int maxdesc = int(font_metrics::maxDescent(font) *
915                           layout->spacing.getValue() * spacing_val);
916
917         pos_type const pos_end = lastPos(*pit, rit);
918         int labeladdon = 0;
919         int maxwidth = 0;
920
921         if (!pit->empty()) {
922                 // We re-use the font resolution for the entire font span when possible
923                 LyXFont font = getFont(pit, rit->pos());
924                 lyx::pos_type endPosOfFontSpan = pit->getEndPosOfFontSpan(rit->pos());
925
926                 // Optimisation
927                 Paragraph const & par = *pit;
928
929                 // Check if any insets are larger
930                 for (pos_type pos = rit->pos(); pos <= pos_end; ++pos) {
931                         // Manual inlined optimised version of common case of
932                         // "maxwidth += singleWidth(pit, pos);"
933                         char const c = par.getChar(pos);
934
935                         if (IsPrintable(c)) {
936                                 if (pos > endPosOfFontSpan) {
937                                         // We need to get the next font
938                                         font = getFont(pit, pos);
939                                         endPosOfFontSpan = par.getEndPosOfFontSpan(pos);
940                                 }
941                                 if (! font.language()->RightToLeft()) {
942                                         maxwidth += font_metrics::width(c, font);
943                                 } else {
944                                         // Fall-back to normal case
945                                         maxwidth += singleWidth(pit, pos, c, font);
946                                         // And flush font cache
947                                         endPosOfFontSpan = 0;
948                                 }
949                         } else {
950                                 // Special handling of insets - are any larger?
951                                 if (par.isInset(pos)) {
952                                         InsetOld const * tmpinset = par.getInset(pos);
953                                         if (tmpinset) {
954                                                 maxwidth += tmpinset->width();
955                                                 maxasc = max(maxasc, tmpinset->ascent());
956                                                 maxdesc = max(maxdesc, tmpinset->descent());
957                                         }
958                                 } else {
959                                         // Fall-back to normal case
960                                         maxwidth += singleWidth(pit, pos, c, font);
961                                         // And flush font cache
962                                         endPosOfFontSpan = 0;
963                                 }
964                         }
965                 }
966         }
967
968         // Check if any custom fonts are larger (Asger)
969         // This is not completely correct, but we can live with the small,
970         // cosmetic error for now.
971         LyXFont::FONT_SIZE maxsize =
972                 pit->highestFontInRange(rit->pos(), pos_end, size);
973         if (maxsize > font.size()) {
974                 font.setSize(maxsize);
975                 maxasc = max(maxasc, font_metrics::maxAscent(font));
976                 maxdesc = max(maxdesc, font_metrics::maxDescent(font));
977         }
978
979         // This is nicer with box insets:
980         ++maxasc;
981         ++maxdesc;
982
983         rit->ascent_of_text(maxasc);
984
985         // is it a top line?
986         if (!rit->pos()) {
987
988                 // some parksips VERY EASY IMPLEMENTATION
989                 if (bv()->buffer()->params.paragraph_separation ==
990                         BufferParams::PARSEP_SKIP)
991                 {
992                         if (layout->isParagraph()
993                                 && pit->getDepth() == 0
994                                 && pit != ownerParagraphs().begin())
995                         {
996                                 maxasc += bv()->buffer()->params.getDefSkip().inPixels(*bv());
997                         } else if (pit != ownerParagraphs().begin() &&
998                                    boost::prior(pit)->layout()->isParagraph() &&
999                                    boost::prior(pit)->getDepth() == 0)
1000                         {
1001                                 // is it right to use defskip here too? (AS)
1002                                 maxasc += bv()->buffer()->params.getDefSkip().inPixels(*bv());
1003                         }
1004                 }
1005
1006                 // the top margin
1007                 if (pit == ownerParagraphs().begin() && !isInInset())
1008                         maxasc += PAPER_MARGIN;
1009
1010                 // add the vertical spaces, that the user added
1011                 maxasc += getLengthMarkerHeight(*bv(), pit->params().spaceTop());
1012
1013                 // do not forget the DTP-lines!
1014                 // there height depends on the font of the nearest character
1015                 if (pit->params().lineTop())
1016
1017                         maxasc += 2 * font_metrics::ascent('x', getFont(pit, 0));
1018                 // and now the pagebreaks
1019                 if (pit->params().pagebreakTop())
1020                         maxasc += 3 * defaultRowHeight();
1021
1022                 if (pit->params().startOfAppendix())
1023                         maxasc += 3 * defaultRowHeight();
1024
1025                 // This is special code for the chapter, since the label of this
1026                 // layout is printed in an extra row
1027                 if (layout->labeltype == LABEL_COUNTER_CHAPTER
1028                         && bv()->buffer()->params.secnumdepth >= 0)
1029                 {
1030                         float spacing_val = 1.0;
1031                         if (!pit->params().spacing().isDefault()) {
1032                                 spacing_val = pit->params().spacing().getValue();
1033                         } else {
1034                                 spacing_val = bv()->buffer()->params.spacing.getValue();
1035                         }
1036
1037                         labeladdon = int(font_metrics::maxDescent(labelfont) *
1038                                          layout->spacing.getValue() *
1039                                          spacing_val)
1040                                 + int(font_metrics::maxAscent(labelfont) *
1041                                       layout->spacing.getValue() *
1042                                       spacing_val);
1043                 }
1044
1045                 // special code for the top label
1046                 if ((layout->labeltype == LABEL_TOP_ENVIRONMENT
1047                      || layout->labeltype == LABEL_BIBLIO
1048                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)
1049                     && isFirstInSequence(pit, ownerParagraphs())
1050                     && !pit->getLabelstring().empty())
1051                 {
1052                         float spacing_val = 1.0;
1053                         if (!pit->params().spacing().isDefault()) {
1054                                 spacing_val = pit->params().spacing().getValue();
1055                         } else {
1056                                 spacing_val = bv()->buffer()->params.spacing.getValue();
1057                         }
1058
1059                         labeladdon = int(
1060                                 (font_metrics::maxAscent(labelfont) +
1061                                  font_metrics::maxDescent(labelfont)) *
1062                                   layout->spacing.getValue() *
1063                                   spacing_val
1064                                 + layout->topsep * defaultRowHeight()
1065                                 + layout->labelbottomsep * defaultRowHeight());
1066                 }
1067
1068                 // And now the layout spaces, for example before and after
1069                 // a section, or between the items of a itemize or enumerate
1070                 // environment.
1071
1072                 if (!pit->params().pagebreakTop()) {
1073                         ParagraphList::iterator prev =
1074                                 depthHook(pit, ownerParagraphs(),
1075                                           pit->getDepth());
1076                         if (prev != pit && prev->layout() == layout &&
1077                                 prev->getDepth() == pit->getDepth() &&
1078                                 prev->getLabelWidthString() == pit->getLabelWidthString())
1079                         {
1080                                 layoutasc = (layout->itemsep * defaultRowHeight());
1081                         } else if (rit != firstRow()) {
1082                                 tmptop = layout->topsep;
1083
1084                                 //if (boost::prior(pit)->getDepth() >= pit->getDepth())
1085                                 //      tmptop -= getPar(previousRow(rit))->layout()->bottomsep;
1086
1087                                 if (tmptop > 0)
1088                                         layoutasc = (tmptop * defaultRowHeight());
1089                         } else if (pit->params().lineTop()) {
1090                                 tmptop = layout->topsep;
1091
1092                                 if (tmptop > 0)
1093                                         layoutasc = (tmptop * defaultRowHeight());
1094                         }
1095
1096                         prev = outerHook(pit, ownerParagraphs());
1097                         if (prev != ownerParagraphs().end())  {
1098                                 maxasc += int(prev->layout()->parsep * defaultRowHeight());
1099                         } else if (pit != ownerParagraphs().begin()) {
1100                                 ParagraphList::iterator prior_pit = boost::prior(pit);
1101                                 if (prior_pit->getDepth() != 0 ||
1102                                     prior_pit->layout() == layout) {
1103                                         maxasc += int(layout->parsep * defaultRowHeight());
1104                                 }
1105                         }
1106                 }
1107         }
1108
1109         // is it a bottom line?
1110         if (boost::next(rit) == pit->rows.end()) {
1111                 // the bottom margin
1112                 ParagraphList::iterator nextpit = boost::next(pit);
1113                 if (nextpit == ownerParagraphs().end() && !isInInset())
1114                         maxdesc += PAPER_MARGIN;
1115
1116                 // add the vertical spaces, that the user added
1117                 maxdesc += getLengthMarkerHeight(*bv(), pit->params().spaceBottom());
1118
1119                 // do not forget the DTP-lines!
1120                 // there height depends on the font of the nearest character
1121                 if (pit->params().lineBottom())
1122                         maxdesc += 2 * font_metrics::ascent('x',
1123                                         getFont(pit, max(pos_type(0), pit->size() - 1)));
1124
1125                 // and now the pagebreaks
1126                 if (pit->params().pagebreakBottom())
1127                         maxdesc += 3 * defaultRowHeight();
1128
1129                 // and now the layout spaces, for example before and after
1130                 // a section, or between the items of a itemize or enumerate
1131                 // environment
1132                 if (!pit->params().pagebreakBottom()
1133                     && nextpit != ownerParagraphs().end()) {
1134                         ParagraphList::iterator comparepit = pit;
1135                         float usual = 0;
1136                         float unusual = 0;
1137
1138                         if (comparepit->getDepth() > nextpit->getDepth()) {
1139                                 usual = (comparepit->layout()->bottomsep * defaultRowHeight());
1140                                 comparepit = depthHook(comparepit, ownerParagraphs(), nextpit->getDepth());
1141                                 if (comparepit->layout()!= nextpit->layout()
1142                                         || nextpit->getLabelWidthString() !=
1143                                         comparepit->getLabelWidthString())
1144                                 {
1145                                         unusual = (comparepit->layout()->bottomsep * defaultRowHeight());
1146                                 }
1147                                 if (unusual > usual)
1148                                         layoutdesc = unusual;
1149                                 else
1150                                         layoutdesc = usual;
1151                         } else if (comparepit->getDepth() ==  nextpit->getDepth()) {
1152
1153                                 if (comparepit->layout() != nextpit->layout()
1154                                         || nextpit->getLabelWidthString() !=
1155                                         comparepit->getLabelWidthString())
1156                                         layoutdesc = int(comparepit->layout()->bottomsep * defaultRowHeight());
1157                         }
1158                 }
1159         }
1160
1161         // incalculate the layout spaces
1162         maxasc += int(layoutasc * 2 / (2 + pit->getDepth()));
1163         maxdesc += int(layoutdesc * 2 / (2 + pit->getDepth()));
1164
1165         rit->height(maxasc + maxdesc + labeladdon);
1166         rit->baseline(maxasc + labeladdon);
1167         rit->top_of_text(rit->baseline() - font_metrics::maxAscent(font));
1168
1169         double x = 0;
1170         rit->width(int(maxwidth + x));
1171         if (inset_owner) {
1172                 width = max(0, workWidth());
1173                 RowList::iterator rit = firstRow();
1174                 RowList::iterator end = endRow();
1175                 ParagraphList::iterator it = ownerParagraphs().begin();
1176                 while (rit != end) {
1177                         if (rit->width() > width)
1178                                 width = rit->width();
1179                         nextRow(it, rit);
1180                 }
1181         }
1182 }
1183
1184
1185 void LyXText::breakParagraph(ParagraphList & paragraphs, char keep_layout)
1186 {
1187         // allow only if at start or end, or all previous is new text
1188         if (cursor.pos() && cursor.pos() != cursor.par()->size()
1189                 && cursor.par()->isChangeEdited(0, cursor.pos()))
1190                 return;
1191
1192         LyXTextClass const & tclass =
1193                 bv()->buffer()->params.getLyXTextClass();
1194         LyXLayout_ptr const & layout = cursor.par()->layout();
1195
1196         // this is only allowed, if the current paragraph is not empty or caption
1197         // and if it has not the keepempty flag active
1198         if (cursor.par()->empty() && !cursor.par()->allowEmpty()
1199            && layout->labeltype != LABEL_SENSITIVE)
1200                 return;
1201
1202         recordUndo(bv(), Undo::ATOMIC, cursor.par());
1203
1204         // Always break behind a space
1205         //
1206         // It is better to erase the space (Dekel)
1207         if (cursor.pos() < cursor.par()->size()
1208              && cursor.par()->isLineSeparator(cursor.pos()))
1209            cursor.par()->erase(cursor.pos());
1210
1211         // break the paragraph
1212         if (keep_layout)
1213                 keep_layout = 2;
1214         else
1215                 keep_layout = layout->isEnvironment();
1216
1217         // we need to set this before we insert the paragraph. IMO the
1218         // breakParagraph call should return a bool if it inserts the
1219         // paragraph before or behind and we should react on that one
1220         // but we can fix this in 1.3.0 (Jug 20020509)
1221         bool const isempty = (cursor.par()->allowEmpty() && cursor.par()->empty());
1222         ::breakParagraph(bv()->buffer()->params, paragraphs, cursor.par(),
1223                          cursor.pos(), keep_layout);
1224
1225 #warning Trouble Point! (Lgb)
1226         // When ::breakParagraph is called from within an inset we must
1227         // ensure that the correct ParagraphList is used. Today that is not
1228         // the case and the Buffer::paragraphs is used. Not good. (Lgb)
1229         ParagraphList::iterator next_par = boost::next(cursor.par());
1230
1231         // well this is the caption hack since one caption is really enough
1232         if (layout->labeltype == LABEL_SENSITIVE) {
1233                 if (!cursor.pos())
1234                         // set to standard-layout
1235                         cursor.par()->applyLayout(tclass.defaultLayout());
1236                 else
1237                         // set to standard-layout
1238                         next_par->applyLayout(tclass.defaultLayout());
1239         }
1240
1241         // if the cursor is at the beginning of a row without prior newline,
1242         // move one row up!
1243         // This touches only the screen-update. Otherwise we would may have
1244         // an empty row on the screen
1245         if (cursor.pos() && cursorRow()->pos() == cursor.pos()
1246             && !cursor.par()->isNewline(cursor.pos() - 1))
1247         {
1248                 cursorLeft(bv());
1249         }
1250
1251         while (!next_par->empty() && next_par->isNewline(0))
1252                 next_par->erase(0);
1253
1254         updateCounters();
1255         redoParagraph(cursor.par());
1256         redoParagraph(next_par);
1257
1258         // This check is necessary. Otherwise the new empty paragraph will
1259         // be deleted automatically. And it is more friendly for the user!
1260         if (cursor.pos() || isempty)
1261                 setCursor(next_par, 0);
1262         else
1263                 setCursor(cursor.par(), 0);
1264 }
1265
1266
1267 // convenience function
1268 void LyXText::redoParagraph()
1269 {
1270         clearSelection();
1271         redoParagraph(cursor.par());
1272         setCursorIntern(cursor.par(), cursor.pos());
1273 }
1274
1275
1276 // insert a character, moves all the following breaks in the
1277 // same Paragraph one to the right and make a rebreak
1278 void LyXText::insertChar(char c)
1279 {
1280         recordUndo(bv(), Undo::INSERT, cursor.par());
1281
1282         // When the free-spacing option is set for the current layout,
1283         // disable the double-space checking
1284
1285         bool const freeSpacing = cursor.par()->layout()->free_spacing ||
1286                 cursor.par()->isFreeSpacing();
1287
1288         if (lyxrc.auto_number) {
1289                 static string const number_operators = "+-/*";
1290                 static string const number_unary_operators = "+-";
1291                 static string const number_seperators = ".,:";
1292
1293                 if (current_font.number() == LyXFont::ON) {
1294                         if (!IsDigit(c) && !contains(number_operators, c) &&
1295                             !(contains(number_seperators, c) &&
1296                               cursor.pos() >= 1 &&
1297                               cursor.pos() < cursor.par()->size() &&
1298                               getFont(cursor.par(), cursor.pos()).number() == LyXFont::ON &&
1299                               getFont(cursor.par(), cursor.pos() - 1).number() == LyXFont::ON)
1300                            )
1301                                 number(bv()); // Set current_font.number to OFF
1302                 } else if (IsDigit(c) &&
1303                            real_current_font.isVisibleRightToLeft()) {
1304                         number(bv()); // Set current_font.number to ON
1305
1306                         if (cursor.pos() > 0) {
1307                                 char const c = cursor.par()->getChar(cursor.pos() - 1);
1308                                 if (contains(number_unary_operators, c) &&
1309                                     (cursor.pos() == 1 ||
1310                                      cursor.par()->isSeparator(cursor.pos() - 2) ||
1311                                      cursor.par()->isNewline(cursor.pos() - 2))
1312                                   ) {
1313                                         setCharFont(
1314                                                     cursor.par(),
1315                                                     cursor.pos() - 1,
1316                                                     current_font);
1317                                 } else if (contains(number_seperators, c) &&
1318                                            cursor.pos() >= 2 &&
1319                                            getFont(
1320                                                    cursor.par(),
1321                                                    cursor.pos() - 2).number() == LyXFont::ON) {
1322                                         setCharFont(
1323                                                     cursor.par(),
1324                                                     cursor.pos() - 1,
1325                                                     current_font);
1326                                 }
1327                         }
1328                 }
1329         }
1330
1331
1332         // First check, if there will be two blanks together or a blank at
1333         // the beginning of a paragraph.
1334         // I decided to handle blanks like normal characters, the main
1335         // difference are the special checks when calculating the row.fill
1336         // (blank does not count at the end of a row) and the check here
1337
1338         // The bug is triggered when we type in a description environment:
1339         // The current_font is not changed when we go from label to main text
1340         // and it should (along with realtmpfont) when we type the space.
1341         // CHECK There is a bug here! (Asger)
1342
1343         // store the current font.  This is because of the use of cursor
1344         // movements. The moving cursor would refresh the current font
1345         LyXFont realtmpfont = real_current_font;
1346         LyXFont rawtmpfont = current_font;
1347
1348         if (!freeSpacing && IsLineSeparatorChar(c)) {
1349                 if ((cursor.pos() > 0
1350                      && cursor.par()->isLineSeparator(cursor.pos() - 1))
1351                     || (cursor.pos() > 0
1352                         && cursor.par()->isNewline(cursor.pos() - 1))
1353                     || (cursor.pos() == 0)) {
1354                         static bool sent_space_message = false;
1355                         if (!sent_space_message) {
1356                                 if (cursor.pos() == 0)
1357                                         bv()->owner()->message(_("You cannot insert a space at the beginning of a paragraph. Please read the Tutorial."));
1358                                 else
1359                                         bv()->owner()->message(_("You cannot type two spaces this way. Please read the Tutorial."));
1360                                 sent_space_message = true;
1361                         }
1362                         charInserted();
1363                         return;
1364                 }
1365         }
1366
1367         // Here case LyXText::InsertInset already inserted the character
1368         if (c != Paragraph::META_INSET)
1369                 cursor.par()->insertChar(cursor.pos(), c);
1370
1371         setCharFont(cursor.par(), cursor.pos(), rawtmpfont);
1372
1373         current_font = rawtmpfont;
1374         real_current_font = realtmpfont;
1375         redoParagraph(cursor.par());
1376         setCursor(cursor.par(), cursor.pos() + 1, false, cursor.boundary());
1377
1378         charInserted();
1379 }
1380
1381
1382 void LyXText::charInserted()
1383 {
1384         // Here we could call finishUndo for every 20 characters inserted.
1385         // This is from my experience how emacs does it. (Lgb)
1386         static unsigned int counter;
1387         if (counter < 20) {
1388                 ++counter;
1389         } else {
1390                 finishUndo();
1391                 counter = 0;
1392         }
1393 }
1394
1395
1396 void LyXText::prepareToPrint(ParagraphList::iterator pit,
1397            RowList::iterator const rit) const
1398 {
1399         double w = rit->fill();
1400         double fill_hfill = 0;
1401         double fill_label_hfill = 0;
1402         double fill_separator = 0;
1403         double x = 0;
1404
1405         bool const is_rtl =
1406                 pit->isRightToLeftPar(bv()->buffer()->params);
1407         if (is_rtl)
1408                 x = workWidth() > 0 ? rightMargin(pit, *bv()->buffer(), *rit) : 0;
1409         else
1410                 x = workWidth() > 0 ? leftMargin(pit, *rit) : 0;
1411
1412         // is there a manual margin with a manual label
1413         LyXLayout_ptr const & layout = pit->layout();
1414
1415         if (layout->margintype == MARGIN_MANUAL
1416             && layout->labeltype == LABEL_MANUAL) {
1417                 /// We might have real hfills in the label part
1418                 int nlh = numberOfLabelHfills(*pit, rit);
1419
1420                 // A manual label par (e.g. List) has an auto-hfill
1421                 // between the label text and the body of the
1422                 // paragraph too.
1423                 // But we don't want to do this auto hfill if the par
1424                 // is empty.
1425                 if (!pit->empty())
1426                         ++nlh;
1427
1428                 if (nlh && !pit->getLabelWidthString().empty()) {
1429                         fill_label_hfill = labelFill(pit, *rit) / double(nlh);
1430                 }
1431         }
1432
1433         // are there any hfills in the row?
1434         int const nh = numberOfHfills(*pit, rit);
1435
1436         if (nh) {
1437                 if (w > 0)
1438                         fill_hfill = w / nh;
1439         // we don't have to look at the alignment if it is ALIGN_LEFT and
1440         // if the row is already larger then the permitted width as then
1441         // we force the LEFT_ALIGN'edness!
1442         } else if (int(rit->width()) < workWidth()) {
1443                 // is it block, flushleft or flushright?
1444                 // set x how you need it
1445                 int align;
1446                 if (pit->params().align() == LYX_ALIGN_LAYOUT) {
1447                         align = layout->align;
1448                 } else {
1449                         align = pit->params().align();
1450                 }
1451                 InsetOld * inset = 0;
1452                 // ERT insets should always be LEFT ALIGNED on screen
1453                 inset = pit->inInset();
1454                 if (inset && inset->owner() &&
1455                         inset->owner()->lyxCode() == InsetOld::ERT_CODE)
1456                 {
1457                         align = LYX_ALIGN_LEFT;
1458                 }
1459
1460                 switch (align) {
1461             case LYX_ALIGN_BLOCK:
1462                 {
1463                         int const ns = numberOfSeparators(*pit, rit);
1464                         RowList::iterator next_row = boost::next(rit);
1465                         if (ns
1466                                 && next_row != pit->rows.end()
1467                                 && !pit->isNewline(next_row->pos() - 1)
1468                                 ) {
1469                                         fill_separator = w / ns;
1470                         } else if (is_rtl) {
1471                                 x += w;
1472                         }
1473                         break;
1474             }
1475             case LYX_ALIGN_RIGHT:
1476                         x += w;
1477                         break;
1478             case LYX_ALIGN_CENTER:
1479                         x += w / 2;
1480                         break;
1481                 }
1482         }
1483
1484         computeBidiTables(pit, *bv()->buffer(), rit);
1485         if (is_rtl) {
1486                 pos_type body_pos = pit->beginningOfBody();
1487                 pos_type last = lastPos(*pit, rit);
1488
1489                 if (body_pos > 0 &&
1490                                 (body_pos - 1 > last ||
1491                                  !pit->isLineSeparator(body_pos - 1))) {
1492                         x += font_metrics::width(layout->labelsep, getLabelFont(pit));
1493                         if (body_pos - 1 <= last)
1494                                 x += fill_label_hfill;
1495                 }
1496         }
1497
1498         rit->fill_hfill(fill_hfill);
1499         rit->fill_label_hfill(fill_label_hfill);
1500         rit->fill_separator(fill_separator);
1501         rit->x(x);
1502 }
1503
1504
1505 // important for the screen
1506
1507
1508 // the cursor set functions have a special mechanism. When they
1509 // realize, that you left an empty paragraph, they will delete it.
1510 // They also delete the corresponding row
1511
1512 void LyXText::cursorRightOneWord()
1513 {
1514         ::cursorRightOneWord(cursor, ownerParagraphs());
1515         setCursor(cursor.par(), cursor.pos());
1516 }
1517
1518
1519 // Skip initial whitespace at end of word and move cursor to *start*
1520 // of prior word, not to end of next prior word.
1521 void LyXText::cursorLeftOneWord()
1522 {
1523         LyXCursor tmpcursor = cursor;
1524         ::cursorLeftOneWord(tmpcursor, ownerParagraphs());
1525         setCursor(tmpcursor.par(), tmpcursor.pos());
1526 }
1527
1528
1529 void LyXText::selectWord(word_location loc)
1530 {
1531         LyXCursor from = cursor;
1532         LyXCursor to;
1533         ::getWord(from, to, loc, ownerParagraphs());
1534         if (cursor != from)
1535                 setCursor(from.par(), from.pos());
1536         if (to == from)
1537                 return;
1538         selection.cursor = cursor;
1539         setCursor(to.par(), to.pos());
1540         setSelection();
1541 }
1542
1543
1544 // Select the word currently under the cursor when no
1545 // selection is currently set
1546 bool LyXText::selectWordWhenUnderCursor(word_location loc)
1547 {
1548         if (!selection.set()) {
1549                 selectWord(loc);
1550                 return selection.set();
1551         }
1552         return false;
1553 }
1554
1555
1556 void LyXText::acceptChange()
1557 {
1558         if (!selection.set() && cursor.par()->size())
1559                 return;
1560
1561         if (selection.start.par() == selection.end.par()) {
1562                 LyXCursor & startc = selection.start;
1563                 LyXCursor & endc = selection.end;
1564                 recordUndo(bv(), Undo::INSERT, startc.par());
1565                 startc.par()->acceptChange(startc.pos(), endc.pos());
1566                 finishUndo();
1567                 clearSelection();
1568                 redoParagraph(startc.par());
1569                 setCursorIntern(startc.par(), 0);
1570         }
1571 #warning handle multi par selection
1572 }
1573
1574
1575 void LyXText::rejectChange()
1576 {
1577         if (!selection.set() && cursor.par()->size())
1578                 return;
1579
1580         if (selection.start.par() == selection.end.par()) {
1581                 LyXCursor & startc = selection.start;
1582                 LyXCursor & endc = selection.end;
1583                 recordUndo(bv(), Undo::INSERT, startc.par());
1584                 startc.par()->rejectChange(startc.pos(), endc.pos());
1585                 finishUndo();
1586                 clearSelection();
1587                 redoParagraph(startc.par());
1588                 setCursorIntern(startc.par(), 0);
1589         }
1590 #warning handle multi par selection
1591 }
1592
1593
1594 // This function is only used by the spellchecker for NextWord().
1595 // It doesn't handle LYX_ACCENTs and probably never will.
1596 WordLangTuple const
1597 LyXText::selectNextWordToSpellcheck(float & value)
1598 {
1599         if (the_locking_inset) {
1600                 WordLangTuple word = the_locking_inset->selectNextWordToSpellcheck(bv(), value);
1601                 if (!word.word().empty()) {
1602                         value += float(cursor.y());
1603                         value /= float(height);
1604                         return word;
1605                 }
1606                 // we have to go on checking so move cursor to the next char
1607                 if (cursor.pos() == cursor.par()->size()) {
1608                         if (boost::next(cursor.par()) == ownerParagraphs().end())
1609                                 return word;
1610                         cursor.par(boost::next(cursor.par()));
1611                         cursor.pos(0);
1612                 } else
1613                         cursor.pos(cursor.pos() + 1);
1614         }
1615         ParagraphList::iterator tmppit = cursor.par();
1616
1617         // If this is not the very first word, skip rest of
1618         // current word because we are probably in the middle
1619         // of a word if there is text here.
1620         if (cursor.pos() || cursor.par() != ownerParagraphs().begin()) {
1621                 while (cursor.pos() < cursor.par()->size()
1622                        && cursor.par()->isLetter(cursor.pos()))
1623                         cursor.pos(cursor.pos() + 1);
1624         }
1625
1626         // Now, skip until we have real text (will jump paragraphs)
1627         while (true) {
1628                 ParagraphList::iterator cpit = cursor.par();
1629                 pos_type const cpos(cursor.pos());
1630
1631                 if (cpos == cpit->size()) {
1632                         if (boost::next(cpit) != ownerParagraphs().end()) {
1633                                 cursor.par(boost::next(cpit));
1634                                 cursor.pos(0);
1635                                 continue;
1636                         }
1637                         break;
1638                 }
1639
1640                 bool const is_good_inset = cpit->isInset(cpos)
1641                         && cpit->getInset(cpos)->allowSpellcheck();
1642
1643                 if (!isDeletedText(*cpit, cpos)
1644                     && (is_good_inset || cpit->isLetter(cpos)))
1645                         break;
1646
1647                 cursor.pos(cpos + 1);
1648         }
1649
1650         // now check if we hit an inset so it has to be a inset containing text!
1651         if (cursor.pos() < cursor.par()->size() &&
1652             cursor.par()->isInset(cursor.pos())) {
1653                 // lock the inset!
1654                 FuncRequest cmd(bv(), LFUN_INSET_EDIT, "left");
1655                 cursor.par()->getInset(cursor.pos())->localDispatch(cmd);
1656                 // now call us again to do the above trick
1657                 // but obviously we have to start from down below ;)
1658                 return bv()->text->selectNextWordToSpellcheck(value);
1659         }
1660
1661         // Update the value if we changed paragraphs
1662         if (cursor.par() != tmppit) {
1663                 setCursor(cursor.par(), cursor.pos());
1664                 value = float(cursor.y())/float(height);
1665         }
1666
1667         // Start the selection from here
1668         selection.cursor = cursor;
1669
1670         string lang_code = getFont(cursor.par(), cursor.pos()).language()->code();
1671         // and find the end of the word (insets like optional hyphens
1672         // and ligature break are part of a word)
1673         while (cursor.pos() < cursor.par()->size()
1674                && cursor.par()->isLetter(cursor.pos())
1675                && !isDeletedText(*cursor.par(), cursor.pos()))
1676                 cursor.pos(cursor.pos() + 1);
1677
1678         // Finally, we copy the word to a string and return it
1679         string str;
1680         if (selection.cursor.pos() < cursor.pos()) {
1681                 pos_type i;
1682                 for (i = selection.cursor.pos(); i < cursor.pos(); ++i) {
1683                         if (!cursor.par()->isInset(i))
1684                                 str += cursor.par()->getChar(i);
1685                 }
1686         }
1687         return WordLangTuple(str, lang_code);
1688 }
1689
1690
1691 // This one is also only for the spellchecker
1692 void LyXText::selectSelectedWord()
1693 {
1694         if (the_locking_inset) {
1695                 the_locking_inset->selectSelectedWord(bv());
1696                 return;
1697         }
1698         // move cursor to the beginning
1699         setCursor(selection.cursor.par(), selection.cursor.pos());
1700
1701         // set the sel cursor
1702         selection.cursor = cursor;
1703
1704         // now find the end of the word
1705         while (cursor.pos() < cursor.par()->size()
1706                && cursor.par()->isLetter(cursor.pos()))
1707                 cursor.pos(cursor.pos() + 1);
1708
1709         setCursor(cursor.par(), cursor.pos());
1710
1711         // finally set the selection
1712         setSelection();
1713 }
1714
1715
1716 // Delete from cursor up to the end of the current or next word.
1717 void LyXText::deleteWordForward()
1718 {
1719         if (cursor.par()->empty())
1720                 cursorRight(bv());
1721         else {
1722                 LyXCursor tmpcursor = cursor;
1723                 selection.set(true); // to avoid deletion
1724                 cursorRightOneWord();
1725                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
1726                 selection.cursor = cursor;
1727                 cursor = tmpcursor;
1728                 setSelection();
1729
1730                 // Great, CutSelection() gets rid of multiple spaces.
1731                 cutSelection(true, false);
1732         }
1733 }
1734
1735
1736 // Delete from cursor to start of current or prior word.
1737 void LyXText::deleteWordBackward()
1738 {
1739         if (cursor.par()->empty())
1740                 cursorLeft(bv());
1741         else {
1742                 LyXCursor tmpcursor = cursor;
1743                 selection.set(true); // to avoid deletion
1744                 cursorLeftOneWord();
1745                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
1746                 selection.cursor = cursor;
1747                 cursor = tmpcursor;
1748                 setSelection();
1749                 cutSelection(true, false);
1750         }
1751 }
1752
1753
1754 // Kill to end of line.
1755 void LyXText::deleteLineForward()
1756 {
1757         if (cursor.par()->empty())
1758                 // Paragraph is empty, so we just go to the right
1759                 cursorRight(bv());
1760         else {
1761                 LyXCursor tmpcursor = cursor;
1762                 // We can't store the row over a regular setCursor
1763                 // so we set it to 0 and reset it afterwards.
1764                 selection.set(true); // to avoid deletion
1765                 cursorEnd();
1766                 setCursor(tmpcursor, tmpcursor.par(), tmpcursor.pos());
1767                 selection.cursor = cursor;
1768                 cursor = tmpcursor;
1769                 setSelection();
1770                 // What is this test for ??? (JMarc)
1771                 if (!selection.set()) {
1772                         deleteWordForward();
1773                 } else {
1774                         cutSelection(true, false);
1775                 }
1776         }
1777 }
1778
1779
1780 void LyXText::changeCase(LyXText::TextCase action)
1781 {
1782         LyXCursor from;
1783         LyXCursor to;
1784
1785         if (selection.set()) {
1786                 from = selection.start;
1787                 to = selection.end;
1788         } else {
1789                 from = cursor;
1790                 ::getWord(from, to, lyx::PARTIAL_WORD, ownerParagraphs());
1791                 setCursor(to.par(), to.pos() + 1);
1792         }
1793
1794         recordUndo(bv(), Undo::ATOMIC, from.par(), to.par());
1795
1796         pos_type pos = from.pos();
1797         ParagraphList::iterator pit = from.par();
1798
1799         while (pit != ownerParagraphs().end() &&
1800                (pos != to.pos() || pit != to.par())) {
1801                 if (pos == pit->size()) {
1802                         ++pit;
1803                         pos = 0;
1804                         continue;
1805                 }
1806                 unsigned char c = pit->getChar(pos);
1807                 if (!IsInsetChar(c)) {
1808                         switch (action) {
1809                         case text_lowercase:
1810                                 c = lowercase(c);
1811                                 break;
1812                         case text_capitalization:
1813                                 c = uppercase(c);
1814                                 action = text_lowercase;
1815                                 break;
1816                         case text_uppercase:
1817                                 c = uppercase(c);
1818                                 break;
1819                         }
1820                 }
1821 #warning changes
1822                 pit->setChar(pos, c);
1823                 ++pos;
1824         }
1825 }
1826
1827
1828 void LyXText::Delete()
1829 {
1830         // this is a very easy implementation
1831
1832         LyXCursor old_cursor = cursor;
1833         int const old_cur_par_id = old_cursor.par()->id();
1834         int const old_cur_par_prev_id =
1835                 (old_cursor.par() != ownerParagraphs().begin() ?
1836                  boost::prior(old_cursor.par())->id() : -1);
1837
1838         // just move to the right
1839         cursorRight(bv());
1840
1841         // CHECK Look at the comment here.
1842         // This check is not very good...
1843         // The cursorRightIntern calls DeleteEmptyParagrapgMechanism
1844         // and that can very well delete the par or par->previous in
1845         // old_cursor. Will a solution where we compare paragraph id's
1846         //work better?
1847         if ((cursor.par() != ownerParagraphs().begin() ? boost::prior(cursor.par())->id() : -1)
1848             == old_cur_par_prev_id
1849             && cursor.par()->id() != old_cur_par_id) {
1850                 // delete-empty-paragraph-mechanism has done it
1851                 return;
1852         }
1853
1854         // if you had success make a backspace
1855         if (old_cursor.par() != cursor.par() || old_cursor.pos() != cursor.pos()) {
1856                 LyXCursor tmpcursor = cursor;
1857                 // to make sure undo gets the right cursor position
1858                 cursor = old_cursor;
1859                 recordUndo(bv(), Undo::DELETE, cursor.par());
1860                 cursor = tmpcursor;
1861                 backspace();
1862         }
1863 }
1864
1865
1866 void LyXText::backspace()
1867 {
1868         // Get the font that is used to calculate the baselineskip
1869         pos_type lastpos = cursor.par()->size();
1870
1871         if (cursor.pos() == 0) {
1872                 // The cursor is at the beginning of a paragraph,
1873                 // so the the backspace will collapse two paragraphs into one.
1874
1875                 // but it's not allowed unless it's new
1876                 if (cursor.par()->isChangeEdited(0, cursor.par()->size()))
1877                         return;
1878
1879                 // we may paste some paragraphs
1880
1881                 // is it an empty paragraph?
1882
1883                 if (lastpos == 0
1884                      || (lastpos == 1 && cursor.par()->isSeparator(0))) {
1885                         // This is an empty paragraph and we delete it just
1886                         // by moving the cursor one step
1887                         // left and let the DeleteEmptyParagraphMechanism
1888                         // handle the actual deletion of the paragraph.
1889
1890                         if (cursor.par() != ownerParagraphs().begin()) {
1891                                 ParagraphList::iterator tmppit = boost::prior(cursor.par());
1892                                 if (cursor.par()->layout() == tmppit->layout()
1893                                     && cursor.par()->getAlign() == tmppit->getAlign()) {
1894                                         // Inherit bottom DTD from the paragraph below.
1895                                         // (the one we are deleting)
1896                                         tmppit->params().lineBottom(cursor.par()->params().lineBottom());
1897                                         tmppit->params().spaceBottom(cursor.par()->params().spaceBottom());
1898                                         tmppit->params().pagebreakBottom(cursor.par()->params().pagebreakBottom());
1899                                 }
1900
1901                                 cursorLeft(bv());
1902
1903                                 // the layout things can change the height of a row !
1904                                 redoParagraph();
1905                                 return;
1906                         }
1907                 }
1908
1909                 if (cursor.par() != ownerParagraphs().begin()) {
1910                         recordUndo(bv(), Undo::DELETE,
1911                                 boost::prior(cursor.par()),
1912                                 cursor.par());
1913                 }
1914
1915                 ParagraphList::iterator tmppit = cursor.par();
1916                 // We used to do cursorLeftIntern() here, but it is
1917                 // not a good idea since it triggers the auto-delete
1918                 // mechanism. So we do a cursorLeftIntern()-lite,
1919                 // without the dreaded mechanism. (JMarc)
1920                 if (cursor.par() != ownerParagraphs().begin()) {
1921                         // steps into the above paragraph.
1922                         setCursorIntern(boost::prior(cursor.par()),
1923                                         boost::prior(cursor.par())->size(),
1924                                         false);
1925                 }
1926
1927                 // Pasting is not allowed, if the paragraphs have different
1928                 // layout. I think it is a real bug of all other
1929                 // word processors to allow it. It confuses the user.
1930                 // Correction: Pasting is always allowed with standard-layout
1931                 LyXTextClass const & tclass =
1932                         bv()->buffer()->params.getLyXTextClass();
1933
1934                 if (cursor.par() != tmppit
1935                     && (cursor.par()->layout() == tmppit->layout()
1936                         || tmppit->layout() == tclass.defaultLayout())
1937                     && cursor.par()->getAlign() == tmppit->getAlign()) {
1938                         mergeParagraph(bv()->buffer()->params,
1939                                 bv()->buffer()->paragraphs, cursor.par());
1940
1941                         if (cursor.pos() && cursor.par()->isSeparator(cursor.pos() - 1))
1942                                 cursor.pos(cursor.pos() - 1);
1943
1944                         // the row may have changed, block, hfills etc.
1945                         updateCounters();
1946                         setCursor(cursor.par(), cursor.pos(), false);
1947                 }
1948         } else {
1949                 // this is the code for a normal backspace, not pasting
1950                 // any paragraphs
1951                 recordUndo(bv(), Undo::DELETE, cursor.par());
1952                 // We used to do cursorLeftIntern() here, but it is
1953                 // not a good idea since it triggers the auto-delete
1954                 // mechanism. So we do a cursorLeftIntern()-lite,
1955                 // without the dreaded mechanism. (JMarc)
1956                 setCursorIntern(cursor.par(), cursor.pos() - 1,
1957                                 false, cursor.boundary());
1958                 cursor.par()->erase(cursor.pos());
1959         }
1960
1961         lastpos = cursor.par()->size();
1962         if (cursor.pos() == lastpos)
1963                 setCurrentFont();
1964
1965         redoParagraph();
1966         setCursor(cursor.par(), cursor.pos(), false, !cursor.boundary());
1967 }
1968
1969
1970 RowList::iterator LyXText::cursorRow() const
1971 {
1972         return getRow(cursor.par(), cursor.pos());
1973 }
1974
1975
1976 RowList::iterator LyXText::getRow(LyXCursor const & cur) const
1977 {
1978         return getRow(cur.par(), cur.pos());
1979 }
1980
1981
1982 RowList::iterator
1983 LyXText::getRow(ParagraphList::iterator pit, pos_type pos) const
1984 {
1985         RowList::iterator rit = boost::prior(pit->rows.end());
1986         RowList::iterator const begin = pit->rows.begin();
1987
1988         while (rit != begin && rit->pos() > pos)
1989                 --rit;
1990
1991         return rit;
1992 }
1993
1994
1995 // returns pointer to some fancy row 'below' specified row
1996 RowList::iterator LyXText::cursorIRow() const
1997 {
1998         return getRow(cursor.par(), cursor.pos());
1999 }
2000
2001
2002 RowList::iterator LyXText::getRowNearY(int y,
2003         ParagraphList::iterator & pit) const
2004 {
2005         //lyxerr << "getRowNearY: y " << y << endl;
2006
2007         pit = boost::prior(ownerParagraphs().end());
2008
2009         RowList::iterator rit = lastRow();
2010         RowList::iterator rbegin = firstRow();
2011
2012         while (rit != rbegin && static_cast<int>(rit->y()) > y)
2013                 previousRow(pit, rit);
2014
2015         return rit;
2016 }
2017
2018
2019 int LyXText::getDepth() const
2020 {
2021         return cursor.par()->getDepth();
2022 }
2023
2024
2025 RowList::iterator LyXText::firstRow() const
2026 {
2027         return ownerParagraphs().front().rows.begin();
2028 }
2029
2030
2031 RowList::iterator LyXText::lastRow() const
2032 {
2033         return boost::prior(endRow());
2034 }
2035
2036
2037 RowList::iterator LyXText::endRow() const
2038 {
2039         return ownerParagraphs().back().rows.end();
2040 }
2041
2042
2043 void LyXText::nextRow(ParagraphList::iterator & pit,
2044         RowList::iterator & rit) const
2045 {
2046         ++rit;
2047         if (rit == pit->rows.end()) {
2048                 ++pit;
2049                 if (pit == ownerParagraphs().end())
2050                         --pit;
2051                 else
2052                         rit = pit->rows.begin();
2053         }
2054 }
2055
2056
2057 void LyXText::previousRow(ParagraphList::iterator & pit,
2058         RowList::iterator & rit) const
2059 {
2060         if (rit != pit->rows.begin())
2061                 --rit;
2062         else {
2063                 Assert(pit != ownerParagraphs().begin());
2064                 --pit;
2065                 rit = boost::prior(pit->rows.end());
2066         }
2067 }
2068
2069
2070 bool LyXText::noRows() const
2071 {
2072         return ownerParagraphs().begin()->rows.empty();
2073 }