]> git.lyx.org Git - lyx.git/blob - src/paragraph.C
00de5af8dd59f28e93b03e7729613740cf2fba4f
[lyx.git] / src / paragraph.C
1 /**
2  * \file paragraph.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 Angus Leeming
10  * \author John Levon
11  * \author André Pönitz
12  * \author Dekel Tsur
13  * \author Jürgen Vigna
14  *
15  * Full author contact details are available in file CREDITS.
16  */
17
18 #include <config.h>
19
20 #include "paragraph.h"
21 #include "paragraph_pimpl.h"
22
23 #include "buffer.h"
24 #include "bufferparams.h"
25 #include "encoding.h"
26 #include "debug.h"
27 #include "gettext.h"
28 #include "language.h"
29 #include "latexrunparams.h"
30 #include "lyxfont.h"
31 #include "lyxrc.h"
32 #include "lyxrow.h"
33 #include "texrow.h"
34 #include "vspace.h"
35
36 #include "insets/insetbibitem.h"
37 #include "insets/insetoptarg.h"
38
39 #include "support/lstrings.h"
40 #include "support/textutils.h"
41 #include "support/std_sstream.h"
42
43 using lyx::pos_type;
44
45 using lyx::support::contains;
46 using lyx::support::subst;
47
48 using std::endl;
49 using std::string;
50 using std::ostream;
51 using std::ostringstream;
52
53
54 Paragraph::Paragraph()
55         : y(0), height(0), pimpl_(new Paragraph::Pimpl(this))
56 {
57         itemdepth = 0;
58         params().clear();
59 }
60
61
62 Paragraph::Paragraph(Paragraph const & lp)
63         : y(0), height(0), text_(lp.text_), pimpl_(new Paragraph::Pimpl(*lp.pimpl_, this))
64 {
65         itemdepth = 0;
66         // this is because of the dummy layout of the paragraphs that
67         // follow footnotes
68         layout_ = lp.layout();
69
70         // copy everything behind the break-position to the new paragraph
71         insetlist = lp.insetlist;
72         InsetList::iterator it = insetlist.begin();
73         InsetList::iterator end = insetlist.end();
74         for (; it != end; ++it) {
75                 // currently we hold Inset*, not InsetBase*
76                 it->inset = static_cast<InsetOld*>(it->inset->clone().release());
77         }
78 }
79
80
81 void Paragraph::operator=(Paragraph const & lp)
82 {
83         // needed as we will destroy the pimpl_ before copying it
84         if (&lp != this)
85                 return;
86
87         lyxerr << "Paragraph::operator=()" << endl;
88
89         text_ = lp.text_;
90
91         delete pimpl_;
92         pimpl_ = new Pimpl(*lp.pimpl_, this);
93
94         itemdepth = lp.itemdepth;
95         // this is because of the dummy layout of the paragraphs that
96         // follow footnotes
97         layout_ = lp.layout();
98
99         // copy everything behind the break-position to the new paragraph
100         insetlist = lp.insetlist;
101         InsetList::iterator it = insetlist.begin();
102         InsetList::iterator end = insetlist.end();
103         for (; it != end; ++it) {
104                 it->inset = static_cast<InsetOld*>(it->inset->clone().release());
105         }
106 }
107
108 // the destructor removes the new paragraph from the list
109 Paragraph::~Paragraph()
110 {
111         delete pimpl_;
112         //
113         //lyxerr << "Paragraph::paragraph_id = "
114         //       << Paragraph::paragraph_id << endl;
115 }
116
117
118 void Paragraph::write(Buffer const & buf, ostream & os,
119                           BufferParams const & bparams,
120                           depth_type & dth) const
121 {
122         // The beginning or end of a deeper (i.e. nested) area?
123         if (dth != params().depth()) {
124                 if (params().depth() > dth) {
125                         while (params().depth() > dth) {
126                                 os << "\n\\begin_deeper ";
127                                 ++dth;
128                         }
129                 } else {
130                         while (params().depth() < dth) {
131                                 os << "\n\\end_deeper ";
132                                 --dth;
133                         }
134                 }
135         }
136
137         // First write the layout
138         os << "\n\\begin_layout " << layout()->name() << '\n';
139
140         params().write(os);
141
142         LyXFont font1(LyXFont::ALL_INHERIT, bparams.language);
143
144         Change running_change = Change(Change::UNCHANGED);
145         lyx::time_type const curtime(lyx::current_time());
146
147         int column = 0;
148         for (pos_type i = 0; i < size(); ++i) {
149                 if (!i) {
150                         os << '\n';
151                         column = 0;
152                 }
153
154                 Change change = pimpl_->lookupChangeFull(i);
155                 Changes::lyxMarkChange(os, column, curtime, running_change, change);
156                 running_change = change;
157
158                 // Write font changes
159                 LyXFont font2 = getFontSettings(bparams, i);
160                 if (font2 != font1) {
161                         font2.lyxWriteChanges(font1, os);
162                         column = 0;
163                         font1 = font2;
164                 }
165
166                 value_type const c = getChar(i);
167                 switch (c) {
168                 case META_INSET:
169                 {
170                         InsetOld const * inset = getInset(i);
171                         if (inset)
172                                 if (inset->directWrite()) {
173                                         // international char, let it write
174                                         // code directly so it's shorter in
175                                         // the file
176                                         inset->write(buf, os);
177                                 } else {
178                                         os << "\n\\begin_inset ";
179                                         inset->write(buf, os);
180                                         os << "\n\\end_inset \n\n";
181                                         column = 0;
182                                 }
183                 }
184                 break;
185                 case '\\':
186                         os << "\n\\backslash \n";
187                         column = 0;
188                         break;
189                 case '.':
190                         if (i + 1 < size() && getChar(i + 1) == ' ') {
191                                 os << ".\n";
192                                 column = 0;
193                         } else
194                                 os << '.';
195                         break;
196                 default:
197                         if ((column > 70 && c == ' ')
198                             || column > 79) {
199                                 os << '\n';
200                                 column = 0;
201                         }
202                         // this check is to amend a bug. LyX sometimes
203                         // inserts '\0' this could cause problems.
204                         if (c != '\0')
205                                 os << c;
206                         else
207                                 lyxerr << "ERROR (Paragraph::writeFile):"
208                                         " NULL char in structure." << endl;
209                         ++column;
210                         break;
211                 }
212         }
213
214         // to make reading work properly
215         if (!size()) {
216                 running_change = pimpl_->lookupChange(0);
217                 Changes::lyxMarkChange(os, column, curtime,
218                         Change(Change::UNCHANGED), running_change);
219         }
220         Changes::lyxMarkChange(os, column, curtime,
221                 running_change, Change(Change::UNCHANGED));
222
223         os << "\n\\end_layout\n";
224 }
225
226
227 void Paragraph::validate(LaTeXFeatures & features) const
228 {
229         pimpl_->validate(features, *layout());
230 }
231
232
233 void Paragraph::eraseIntern(lyx::pos_type pos)
234 {
235         pimpl_->eraseIntern(pos);
236 }
237
238
239 bool Paragraph::erase(pos_type pos)
240 {
241         return pimpl_->erase(pos);
242 }
243
244
245 int Paragraph::erase(pos_type start, pos_type end)
246 {
247         return pimpl_->erase(start, end);
248 }
249
250
251 bool Paragraph::checkInsertChar(LyXFont & font)
252 {
253         if (pimpl_->inset_owner)
254                 return pimpl_->inset_owner->checkInsertChar(font);
255         return true;
256 }
257
258
259 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c)
260 {
261         insertChar(pos, c, LyXFont(LyXFont::ALL_INHERIT));
262 }
263
264
265 void Paragraph::insertChar(pos_type pos, Paragraph::value_type c,
266                            LyXFont const & font, Change change)
267 {
268         pimpl_->insertChar(pos, c, font, change);
269 }
270
271
272 void Paragraph::insertInset(pos_type pos, InsetOld * inset)
273 {
274         insertInset(pos, inset, LyXFont(LyXFont::ALL_INHERIT));
275 }
276
277
278 void Paragraph::insertInset(pos_type pos, InsetOld * inset,
279         LyXFont const & font, Change change)
280 {
281         pimpl_->insertInset(pos, inset, font, change);
282 }
283
284
285 bool Paragraph::insetAllowed(InsetOld_code code)
286 {
287         //lyxerr << "Paragraph::InsertInsetAllowed" << endl;
288         if (pimpl_->inset_owner)
289                 return pimpl_->inset_owner->insetAllowed(code);
290         return true;
291 }
292
293
294 InsetOld * Paragraph::getInset(pos_type pos)
295 {
296         BOOST_ASSERT(pos < size());
297         return insetlist.get(pos);
298 }
299
300
301 InsetOld const * Paragraph::getInset(pos_type pos) const
302 {
303         BOOST_ASSERT(pos < size());
304         return insetlist.get(pos);
305 }
306
307
308 // Gets uninstantiated font setting at position.
309 LyXFont const Paragraph::getFontSettings(BufferParams const & bparams,
310                                          pos_type pos) const
311 {
312         BOOST_ASSERT(pos <= size());
313
314         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
315         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
316         for (; cit != end; ++cit)
317                 if (cit->pos() >= pos)
318                         break;
319
320         if (cit != end)
321                 return cit->font();
322
323         if (pos == size() && !empty())
324                 return getFontSettings(bparams, pos - 1);
325
326         return LyXFont(LyXFont::ALL_INHERIT, getParLanguage(bparams));
327 }
328
329
330 lyx::pos_type Paragraph::getEndPosOfFontSpan(lyx::pos_type pos) const
331 {
332         BOOST_ASSERT(pos <= size());
333
334         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
335         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
336         for (; cit != end; ++cit)
337                 if (cit->pos() >= pos)
338                         return cit->pos();
339
340         // This should not happen, but if so, we take no chances.
341         //lyxerr << "Paragraph::getEndPosOfFontSpan: This should not happen!"
342         //      << endl;
343         return pos;
344 }
345
346
347 // Gets uninstantiated font setting at position 0
348 LyXFont const Paragraph::getFirstFontSettings() const
349 {
350         if (!empty() && !pimpl_->fontlist.empty())
351                 return pimpl_->fontlist[0].font();
352
353         return LyXFont(LyXFont::ALL_INHERIT);
354 }
355
356
357 // Gets the fully instantiated font at a given position in a paragraph
358 // This is basically the same function as LyXText::GetFont() in text2.C.
359 // The difference is that this one is used for generating the LaTeX file,
360 // and thus cosmetic "improvements" are disallowed: This has to deliver
361 // the true picture of the buffer. (Asger)
362 LyXFont const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
363                                  LyXFont const & outerfont) const
364 {
365         BOOST_ASSERT(pos >= 0);
366
367         LyXLayout_ptr const & lout = layout();
368
369         pos_type const body_pos = beginningOfBody();
370
371         LyXFont layoutfont;
372         if (pos < body_pos)
373                 layoutfont = lout->labelfont;
374         else
375                 layoutfont = lout->font;
376
377         LyXFont tmpfont = getFontSettings(bparams, pos);
378         tmpfont.realize(layoutfont);
379         tmpfont.realize(outerfont);
380         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
381
382         return tmpfont;
383 }
384
385
386 LyXFont const Paragraph::getLabelFont(BufferParams const & bparams,
387                                       LyXFont const & outerfont) const
388 {
389         LyXFont tmpfont = layout()->labelfont;
390         tmpfont.setLanguage(getParLanguage(bparams));
391         tmpfont.realize(outerfont);
392         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
393         return tmpfont;
394 }
395
396
397 LyXFont const Paragraph::getLayoutFont(BufferParams const & bparams,
398                                        LyXFont const & outerfont) const
399 {
400         LyXFont tmpfont = layout()->font;
401         tmpfont.setLanguage(getParLanguage(bparams));
402         tmpfont.realize(outerfont);
403         tmpfont.realize(bparams.getLyXTextClass().defaultfont());
404         return tmpfont;
405 }
406
407
408 /// Returns the height of the highest font in range
409 LyXFont_size
410 Paragraph::highestFontInRange(pos_type startpos, pos_type endpos,
411                               LyXFont_size def_size) const
412 {
413         if (pimpl_->fontlist.empty())
414                 return def_size;
415
416         Pimpl::FontList::const_iterator end_it = pimpl_->fontlist.begin();
417         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
418         for (; end_it != end; ++end_it) {
419                 if (end_it->pos() >= endpos)
420                         break;
421         }
422
423         if (end_it != end)
424                 ++end_it;
425
426         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
427         for (; cit != end; ++cit) {
428                 if (cit->pos() >= startpos)
429                         break;
430         }
431
432         LyXFont::FONT_SIZE maxsize = LyXFont::SIZE_TINY;
433         for (; cit != end_it; ++cit) {
434                 LyXFont::FONT_SIZE size = cit->font().size();
435                 if (size == LyXFont::INHERIT_SIZE)
436                         size = def_size;
437                 if (size > maxsize && size <= LyXFont::SIZE_HUGER)
438                         maxsize = size;
439         }
440         return maxsize;
441 }
442
443
444 Paragraph::value_type
445 Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const
446 {
447         value_type c = getChar(pos);
448         if (!lyxrc.rtl_support)
449                 return c;
450
451         value_type uc = c;
452         switch (c) {
453         case '(':
454                 uc = ')';
455                 break;
456         case ')':
457                 uc = '(';
458                 break;
459         case '[':
460                 uc = ']';
461                 break;
462         case ']':
463                 uc = '[';
464                 break;
465         case '{':
466                 uc = '}';
467                 break;
468         case '}':
469                 uc = '{';
470                 break;
471         case '<':
472                 uc = '>';
473                 break;
474         case '>':
475                 uc = '<';
476                 break;
477         }
478         if (uc != c && getFontSettings(bparams, pos).isRightToLeft())
479                 return uc;
480         else
481                 return c;
482 }
483
484
485 void Paragraph::setFont(pos_type pos, LyXFont const & font)
486 {
487         BOOST_ASSERT(pos <= size());
488
489         // First, reduce font against layout/label font
490         // Update: The setCharFont() routine in text2.C already
491         // reduces font, so we don't need to do that here. (Asger)
492         // No need to simplify this because it will disappear
493         // in a new kernel. (Asger)
494         // Next search font table
495
496         Pimpl::FontList::iterator beg = pimpl_->fontlist.begin();
497         Pimpl::FontList::iterator it = beg;
498         Pimpl::FontList::iterator endit = pimpl_->fontlist.end();
499         for (; it != endit; ++it) {
500                 if (it->pos() >= pos)
501                         break;
502         }
503         unsigned int i = std::distance(beg, it);
504         bool notfound = (it == endit);
505
506         if (!notfound && pimpl_->fontlist[i].font() == font)
507                 return;
508
509         bool begin = pos == 0 || notfound ||
510                 (i > 0 && pimpl_->fontlist[i - 1].pos() == pos - 1);
511         // Is position pos is a beginning of a font block?
512         bool end = !notfound && pimpl_->fontlist[i].pos() == pos;
513         // Is position pos is the end of a font block?
514         if (begin && end) { // A single char block
515                 if (i + 1 < pimpl_->fontlist.size() &&
516                     pimpl_->fontlist[i + 1].font() == font) {
517                         // Merge the singleton block with the next block
518                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
519                         if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
520                                 pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i - 1);
521                 } else if (i > 0 && pimpl_->fontlist[i - 1].font() == font) {
522                         // Merge the singleton block with the previous block
523                         pimpl_->fontlist[i - 1].pos(pos);
524                         pimpl_->fontlist.erase(pimpl_->fontlist.begin() + i);
525                 } else
526                         pimpl_->fontlist[i].font(font);
527         } else if (begin) {
528                 if (i > 0 && pimpl_->fontlist[i - 1].font() == font)
529                         pimpl_->fontlist[i - 1].pos(pos);
530                 else
531                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
532                                         Pimpl::FontTable(pos, font));
533         } else if (end) {
534                 pimpl_->fontlist[i].pos(pos - 1);
535                 if (!(i + 1 < pimpl_->fontlist.size() &&
536                       pimpl_->fontlist[i + 1].font() == font))
537                         pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
538                                         Pimpl::FontTable(pos, font));
539         } else { // The general case. The block is splitted into 3 blocks
540                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i,
541                                 Pimpl::FontTable(pos - 1, pimpl_->fontlist[i].font()));
542                 pimpl_->fontlist.insert(pimpl_->fontlist.begin() + i + 1,
543                                 Pimpl::FontTable(pos, font));
544         }
545 }
546
547
548 void Paragraph::makeSameLayout(Paragraph const & par)
549 {
550         layout(par.layout());
551         // move to pimpl?
552         params() = par.params();
553 }
554
555
556 int Paragraph::stripLeadingSpaces()
557 {
558         if (isFreeSpacing())
559                 return 0;
560
561         int i = 0;
562         while (!empty() && (isNewline(0) || isLineSeparator(0))) {
563                 pimpl_->eraseIntern(0);
564                 ++i;
565         }
566
567         return i;
568 }
569
570
571 bool Paragraph::hasSameLayout(Paragraph const & par) const
572 {
573         return
574                 par.layout() == layout() &&
575                 params().sameLayout(par.params());
576 }
577
578
579 Paragraph::depth_type Paragraph::getDepth() const
580 {
581         return params().depth();
582 }
583
584
585 Paragraph::depth_type Paragraph::getMaxDepthAfter() const
586 {
587         if (layout()->isEnvironment())
588                 return params().depth() + 1;
589         else
590                 return params().depth();
591 }
592
593
594 char Paragraph::getAlign() const
595 {
596         return params().align();
597 }
598
599
600 string const & Paragraph::getLabelstring() const
601 {
602         return params().labelString();
603 }
604
605
606 // the next two functions are for the manual labels
607 string const Paragraph::getLabelWidthString() const
608 {
609         if (!params().labelWidthString().empty())
610                 return params().labelWidthString();
611         else
612                 return _("Senseless with this layout!");
613 }
614
615
616 void Paragraph::setLabelWidthString(string const & s)
617 {
618         params().labelWidthString(s);
619 }
620
621
622 void Paragraph::applyLayout(LyXLayout_ptr const & new_layout)
623 {
624         layout(new_layout);
625         params().labelWidthString(string());
626         params().align(LYX_ALIGN_LAYOUT);
627         params().spaceTop(VSpace(VSpace::NONE));
628         params().spaceBottom(VSpace(VSpace::NONE));
629         params().spacing(Spacing(Spacing::Default));
630 }
631
632
633 int Paragraph::beginningOfBody() const
634 {
635         if (layout()->labeltype != LABEL_MANUAL)
636                 return 0;
637
638         // Unroll the first two cycles of the loop
639         // and remember the previous character to
640         // remove unnecessary getChar() calls
641         pos_type i = 0;
642         if (i < size() && !isNewline(i)) {
643                 ++i;
644                 char previous_char = 0;
645                 char temp = 0;
646                 if (i < size()) {
647                         previous_char = getChar(i);
648                         if (!isNewline(i)) {
649                                 ++i;
650                                 while (i < size() && previous_char != ' ') {
651                                         temp = getChar(i);
652                                         if (isNewline(i))
653                                                 break;
654
655                                         ++i;
656                                         previous_char = temp;
657                                 }
658                         }
659                 }
660         }
661
662         return i;
663 }
664
665
666 // returns -1 if inset not found
667 int Paragraph::getPositionOfInset(InsetOld const * inset) const
668 {
669         // Find the entry.
670         InsetList::const_iterator it = insetlist.begin();
671         InsetList::const_iterator end = insetlist.end();
672         for (; it != end; ++it)
673                 if (it->inset == inset)
674                         return it->pos;
675         return -1;
676 }
677
678
679 InsetBibitem * Paragraph::bibitem() const
680 {
681         InsetList::const_iterator it = insetlist.begin();
682         if (it != insetlist.end() && it->inset->lyxCode() == InsetOld::BIBTEX_CODE)
683                 return static_cast<InsetBibitem *>(it->inset);
684         return 0;
685 }
686
687
688 namespace {
689
690 /* paragraphs inside floats need different alignment tags to avoid
691 unwanted space */
692
693 bool noTrivlistCentering(UpdatableInset const * inset)
694 {
695         if (inset && inset->owner()) {
696                 InsetOld::Code const code = inset->owner()->lyxCode();
697                 return code == InsetOld::FLOAT_CODE ||
698                         code == InsetOld::WRAP_CODE;
699         }
700         return false;
701 }
702
703
704 string correction(string const & orig)
705 {
706         if (orig == "flushleft")
707                 return "raggedright";
708         if (orig == "flushright")
709                 return "raggedleft";
710         if (orig == "center")
711                 return "centering";
712         return orig;
713 }
714
715
716 string const corrected_env(string const & suffix, string const & env,
717                            UpdatableInset const * inset)
718 {
719         string output = suffix + "{";
720         if (noTrivlistCentering(inset))
721                 output += correction(env);
722         else
723                 output += env;
724         return output + "}";
725 }
726
727 } // namespace anon
728
729
730 // This could go to ParagraphParameters if we want to
731 int Paragraph::startTeXParParams(BufferParams const & bparams,
732                                  ostream & os, bool moving_arg) const
733 {
734         int column = 0;
735
736         if (params().noindent()) {
737                 os << "\\noindent ";
738                 column += 10;
739         }
740
741         switch (params().align()) {
742         case LYX_ALIGN_NONE:
743         case LYX_ALIGN_BLOCK:
744         case LYX_ALIGN_LAYOUT:
745         case LYX_ALIGN_SPECIAL:
746                 break;
747         case LYX_ALIGN_LEFT:
748         case LYX_ALIGN_RIGHT:
749         case LYX_ALIGN_CENTER:
750                 if (moving_arg) {
751                         os << "\\protect";
752                         column = 8;
753                 }
754                 break;
755         }
756
757         switch (params().align()) {
758         case LYX_ALIGN_NONE:
759         case LYX_ALIGN_BLOCK:
760         case LYX_ALIGN_LAYOUT:
761         case LYX_ALIGN_SPECIAL:
762                 break;
763         case LYX_ALIGN_LEFT: {
764                 string output;
765                 UpdatableInset const * const inset = pimpl_->inset_owner;
766                 if (getParLanguage(bparams)->babel() != "hebrew")
767                         output = corrected_env("\\begin", "flushleft", inset);
768                 else
769                         output = corrected_env("\\begin", "flushright", inset);
770                 os << output;
771                 column += output.size();
772                 break;
773         } case LYX_ALIGN_RIGHT: {
774                 string output;
775                 UpdatableInset const * const inset = pimpl_->inset_owner;
776                 if (getParLanguage(bparams)->babel() != "hebrew")
777                         output = corrected_env("\\begin", "flushright", inset);
778                 else
779                         output = corrected_env("\\begin", "flushleft", inset);
780                 os << output;
781                 column += output.size();
782                 break;
783         } case LYX_ALIGN_CENTER: {
784                 string output;
785                 output = corrected_env("\\begin", "center", pimpl_->inset_owner);
786                 os << output;
787                 column += output.size();
788                 break;
789         }
790         }
791
792         return column;
793 }
794
795
796 // This could go to ParagraphParameters if we want to
797 int Paragraph::endTeXParParams(BufferParams const & bparams,
798                                ostream & os, bool moving_arg) const
799 {
800         int column = 0;
801
802         switch (params().align()) {
803         case LYX_ALIGN_NONE:
804         case LYX_ALIGN_BLOCK:
805         case LYX_ALIGN_LAYOUT:
806         case LYX_ALIGN_SPECIAL:
807                 break;
808         case LYX_ALIGN_LEFT:
809         case LYX_ALIGN_RIGHT:
810         case LYX_ALIGN_CENTER:
811                 if (moving_arg) {
812                         os << "\\protect";
813                         column = 8;
814                 }
815                 break;
816         }
817
818         switch (params().align()) {
819         case LYX_ALIGN_NONE:
820         case LYX_ALIGN_BLOCK:
821         case LYX_ALIGN_LAYOUT:
822         case LYX_ALIGN_SPECIAL:
823                 break;
824         case LYX_ALIGN_LEFT: {
825                 string output;
826                 UpdatableInset const * const inset = pimpl_->inset_owner;
827                 if (getParLanguage(bparams)->babel() != "hebrew")
828                         output = corrected_env("\\par\\end", "flushleft", inset);
829                 else
830                         output = corrected_env("\\par\\end", "flushright", inset);
831                 os << output;
832                 column += output.size();
833                 break;
834         } case LYX_ALIGN_RIGHT: {
835                 string output;
836                 UpdatableInset const * const inset = pimpl_->inset_owner;
837                 if (getParLanguage(bparams)->babel() != "hebrew")
838                         output = corrected_env("\\par\\end", "flushright", inset);
839                 else
840                         output = corrected_env("\\par\\end", "flushleft", inset);
841                 os << output;
842                 column += output.size();
843                 break;
844         } case LYX_ALIGN_CENTER: {
845                 string output;
846                 output = corrected_env("\\par\\end", "center", pimpl_->inset_owner);
847                 os << output;
848                 column += output.size();
849                 break;
850         }
851         }
852
853         return column;
854 }
855
856
857 // This one spits out the text of the paragraph
858 bool Paragraph::simpleTeXOnePar(Buffer const & buf,
859                                 BufferParams const & bparams,
860                                 LyXFont const & outerfont,
861                                 ostream & os, TexRow & texrow,
862                                 LatexRunParams const & runparams)
863 {
864         lyxerr[Debug::LATEX] << "SimpleTeXOnePar...     " << this << endl;
865
866         bool return_value = false;
867
868         LyXLayout_ptr style;
869
870         // well we have to check if we are in an inset with unlimited
871         // length (all in one row) if that is true then we don't allow
872         // any special options in the paragraph and also we don't allow
873         // any environment other then "Standard" to be valid!
874         bool asdefault =
875                 (inInset() && inInset()->forceDefaultParagraphs(inInset()));
876
877         if (asdefault) {
878                 style = bparams.getLyXTextClass().defaultLayout();
879         } else {
880                 style = layout();
881         }
882
883         LyXFont basefont;
884
885         // Maybe we have to create a optional argument.
886         pos_type body_pos;
887
888         // FIXME: can we actually skip this check and just call
889         // beginningOfBody() ??
890         if (style->labeltype != LABEL_MANUAL) {
891                 body_pos = 0;
892         } else {
893                 body_pos = beginningOfBody();
894         }
895
896         unsigned int column = 0;
897
898         if (body_pos > 0) {
899                 os << '[';
900                 ++column;
901                 basefont = getLabelFont(bparams, outerfont);
902         } else {
903                 basefont = getLayoutFont(bparams, outerfont);
904         }
905
906         bool moving_arg = runparams.moving_arg;
907         moving_arg |= style->needprotect;
908
909         // Which font is currently active?
910         LyXFont running_font(basefont);
911         // Do we have an open font change?
912         bool open_font = false;
913
914         Change::Type running_change = Change::UNCHANGED;
915
916         texrow.start(id(), 0);
917
918         // if the paragraph is empty, the loop will not be entered at all
919         if (empty()) {
920                 if (style->isCommand()) {
921                         os << '{';
922                         ++column;
923                 }
924                 if (!asdefault)
925                         column += startTeXParParams(bparams, os, moving_arg);
926
927         }
928
929         for (pos_type i = 0; i < size(); ++i) {
930                 ++column;
931                 // First char in paragraph or after label?
932                 if (i == body_pos) {
933                         if (body_pos > 0) {
934                                 if (open_font) {
935                                         column += running_font.latexWriteEndChanges(os, basefont, basefont);
936                                         open_font = false;
937                                 }
938                                 basefont = getLayoutFont(bparams, outerfont);
939                                 running_font = basefont;
940                                 os << ']';
941                                 ++column;
942                         }
943                         if (style->isCommand()) {
944                                 os << '{';
945                                 ++column;
946                         }
947
948                         if (!asdefault)
949                                 column += startTeXParParams(bparams, os,
950                                                             moving_arg);
951                 }
952
953                 value_type c = getChar(i);
954
955                 // Fully instantiated font
956                 LyXFont font = getFont(bparams, i, outerfont);
957
958                 LyXFont const last_font = running_font;
959
960                 // Spaces at end of font change are simulated to be
961                 // outside font change, i.e. we write "\textXX{text} "
962                 // rather than "\textXX{text }". (Asger)
963                 if (open_font && c == ' ' && i <= size() - 2) {
964                         LyXFont const & next_font = getFont(bparams, i + 1, outerfont);
965                         if (next_font != running_font
966                             && next_font != font) {
967                                 font = next_font;
968                         }
969                 }
970
971                 // We end font definition before blanks
972                 if (open_font &&
973                     (font != running_font ||
974                      font.language() != running_font.language()))
975                 {
976                         column += running_font.latexWriteEndChanges(os,
977                                                                     basefont,
978                                                                     (i == body_pos-1) ? basefont : font);
979                         running_font = basefont;
980                         open_font = false;
981                 }
982
983                 // Blanks are printed before start of fontswitch
984                 if (c == ' ') {
985                         // Do not print the separation of the optional argument
986                         if (i != body_pos - 1) {
987                                 pimpl_->simpleTeXBlanks(os, texrow, i,
988                                                        column, font, *style);
989                         }
990                 }
991
992                 // Do we need to change font?
993                 if ((font != running_font ||
994                      font.language() != running_font.language()) &&
995                         i != body_pos - 1)
996                 {
997                         column += font.latexWriteStartChanges(os, basefont,
998                                                               last_font);
999                         running_font = font;
1000                         open_font = true;
1001                 }
1002
1003                 Change::Type change = pimpl_->lookupChange(i);
1004
1005                 column += Changes::latexMarkChange(os, running_change, change);
1006                 running_change = change;
1007
1008                 LatexRunParams rp = runparams;
1009                 rp.moving_arg = moving_arg;
1010                 rp.free_spacing = style->free_spacing;
1011                 pimpl_->simpleTeXSpecialChars(buf, bparams,
1012                                               os, texrow, runparams,
1013                                               font, running_font,
1014                                               basefont, outerfont, open_font,
1015                                               running_change,
1016                                               *style, i, column, c);
1017         }
1018
1019         column += Changes::latexMarkChange(os,
1020                         running_change, Change::UNCHANGED);
1021
1022         // If we have an open font definition, we have to close it
1023         if (open_font) {
1024 #ifdef FIXED_LANGUAGE_END_DETECTION
1025                 if (next_) {
1026                         running_font
1027                                 .latexWriteEndChanges(os, basefont,
1028                                                       next_->getFont(bparams,
1029                                                       0, outerfont));
1030                 } else {
1031                         running_font.latexWriteEndChanges(os, basefont,
1032                                                           basefont);
1033                 }
1034 #else
1035 #ifdef WITH_WARNINGS
1036 //#warning For now we ALWAYS have to close the foreign font settings if they are
1037 //#warning there as we start another \selectlanguage with the next paragraph if
1038 //#warning we are in need of this. This should be fixed sometime (Jug)
1039 #endif
1040                 running_font.latexWriteEndChanges(os, basefont,  basefont);
1041 #endif
1042         }
1043
1044         // Needed if there is an optional argument but no contents.
1045         if (body_pos > 0 && body_pos == size()) {
1046                 os << "]~";
1047                 return_value = false;
1048         }
1049
1050         if (!asdefault) {
1051                 column += endTeXParParams(bparams, os, moving_arg);
1052         }
1053
1054         lyxerr[Debug::LATEX] << "SimpleTeXOnePar...done " << this << endl;
1055         return return_value;
1056 }
1057
1058
1059 namespace {
1060
1061 /// return true if the char is a meta-character for an inset
1062 inline
1063 bool IsInsetChar(char c)
1064 {
1065         return (c == Paragraph::META_INSET);
1066 }
1067
1068 } // namespace anon
1069
1070
1071
1072 bool Paragraph::isHfill(pos_type pos) const
1073 {
1074         return IsInsetChar(getChar(pos))
1075                && getInset(pos)->lyxCode() == InsetOld::HFILL_CODE;
1076 }
1077
1078
1079 bool Paragraph::isInset(pos_type pos) const
1080 {
1081         return IsInsetChar(getChar(pos));
1082 }
1083
1084
1085 bool Paragraph::isNewline(pos_type pos) const
1086 {
1087         return IsInsetChar(getChar(pos))
1088                && getInset(pos)->lyxCode() == InsetOld::NEWLINE_CODE;
1089 }
1090
1091
1092 bool Paragraph::isSeparator(pos_type pos) const
1093 {
1094         return IsSeparatorChar(getChar(pos));
1095 }
1096
1097
1098 bool Paragraph::isLineSeparator(pos_type pos) const
1099 {
1100         value_type const c = getChar(pos);
1101         return IsLineSeparatorChar(c)
1102                 || (IsInsetChar(c) && getInset(pos) &&
1103                 getInset(pos)->isLineSeparator());
1104 }
1105
1106
1107 bool Paragraph::isKomma(pos_type pos) const
1108 {
1109         return IsKommaChar(getChar(pos));
1110 }
1111
1112
1113 /// Used by the spellchecker
1114 bool Paragraph::isLetter(pos_type pos) const
1115 {
1116         value_type const c = getChar(pos);
1117         if (IsLetterChar(c))
1118                 return true;
1119         if (isInset(pos))
1120                 return getInset(pos)->isLetter();
1121         // We want to pass the ' and escape chars to ispell
1122         string const extra = lyxrc.isp_esc_chars + '\'';
1123         return contains(extra, c);
1124 }
1125
1126
1127 bool Paragraph::isWord(pos_type pos) const
1128 {
1129         unsigned char const c = getChar(pos);
1130         return !(IsSeparatorChar(c)
1131                   || IsKommaChar(c)
1132                   || IsInsetChar(c));
1133 }
1134
1135
1136 Language const *
1137 Paragraph::getParLanguage(BufferParams const & bparams) const
1138 {
1139         if (!empty())
1140                 return getFirstFontSettings().language();
1141 #warning FIXME we should check the prev par as well (Lgb)
1142         return bparams.language;
1143 }
1144
1145
1146 bool Paragraph::isRightToLeftPar(BufferParams const & bparams) const
1147 {
1148         return lyxrc.rtl_support
1149                 && getParLanguage(bparams)->RightToLeft()
1150                 && !(inInset() && inInset()->owner() &&
1151                      inInset()->owner()->lyxCode() == InsetOld::ERT_CODE);
1152 }
1153
1154
1155 void Paragraph::changeLanguage(BufferParams const & bparams,
1156                                Language const * from, Language const * to)
1157 {
1158         for (pos_type i = 0; i < size(); ++i) {
1159                 LyXFont font = getFontSettings(bparams, i);
1160                 if (font.language() == from) {
1161                         font.setLanguage(to);
1162                         setFont(i, font);
1163                 }
1164         }
1165 }
1166
1167
1168 bool Paragraph::isMultiLingual(BufferParams const & bparams)
1169 {
1170         Language const * doc_language = bparams.language;
1171         Pimpl::FontList::const_iterator cit = pimpl_->fontlist.begin();
1172         Pimpl::FontList::const_iterator end = pimpl_->fontlist.end();
1173
1174         for (; cit != end; ++cit)
1175                 if (cit->font().language() != ignore_language &&
1176                     cit->font().language() != latex_language &&
1177                     cit->font().language() != doc_language)
1178                         return true;
1179         return false;
1180 }
1181
1182
1183 // Convert the paragraph to a string.
1184 // Used for building the table of contents
1185 string const Paragraph::asString(Buffer const & buffer, bool label) const
1186 {
1187 #if 0
1188         string s;
1189         if (label && !params().labelString().empty())
1190                 s += params().labelString() + ' ';
1191
1192         for (pos_type i = 0; i < size(); ++i) {
1193                 value_type c = getChar(i);
1194                 if (IsPrintable(c))
1195                         s += c;
1196                 else if (c == META_INSET &&
1197                          getInset(i)->lyxCode() == InsetOld::MATH_CODE) {
1198                         ostringstream ost;
1199                         getInset(i)->ascii(buffer, ost);
1200                         s += subst(STRCONV(ost.str()),'\n',' ');
1201                 }
1202         }
1203
1204         return s;
1205 #else
1206         // This should really be done by the caller and not here.
1207         string ret(asString(buffer, 0, size(), label));
1208         return subst(ret, '\n', ' ');
1209 #endif
1210 }
1211
1212
1213 string const Paragraph::asString(Buffer const & buffer,
1214                                  pos_type beg, pos_type end, bool label) const
1215 {
1216         ostringstream os;
1217
1218         if (beg == 0 && label && !params().labelString().empty())
1219                 os << params().labelString() << ' ';
1220
1221         for (pos_type i = beg; i < end; ++i) {
1222                 value_type const c = getUChar(buffer.params(), i);
1223                 if (IsPrintable(c))
1224                         os << c;
1225                 else if (c == META_INSET)
1226                         getInset(i)->ascii(buffer, os);
1227         }
1228
1229         return os.str();
1230 }
1231
1232
1233 void Paragraph::setInsetOwner(UpdatableInset * inset)
1234 {
1235         pimpl_->inset_owner = inset;
1236         InsetList::iterator it = insetlist.begin();
1237         InsetList::iterator end = insetlist.end();
1238         for (; it != end; ++it)
1239                 if (it->inset)
1240                         it->inset->setOwner(inset);
1241 }
1242
1243
1244 void Paragraph::deleteInsetsLyXText(BufferView * bv)
1245 {
1246         insetlist.deleteInsetsLyXText(bv);
1247 }
1248
1249
1250 void Paragraph::setContentsFromPar(Paragraph const & par)
1251 {
1252         pimpl_->setContentsFromPar(par);
1253 }
1254
1255
1256 void Paragraph::trackChanges(Change::Type type)
1257 {
1258         pimpl_->trackChanges(type);
1259 }
1260
1261
1262 void Paragraph::untrackChanges()
1263 {
1264         pimpl_->untrackChanges();
1265 }
1266
1267
1268 void Paragraph::cleanChanges()
1269 {
1270         pimpl_->cleanChanges();
1271 }
1272
1273
1274 Change::Type Paragraph::lookupChange(lyx::pos_type pos) const
1275 {
1276         BOOST_ASSERT(!size() || pos < size());
1277         return pimpl_->lookupChange(pos);
1278 }
1279
1280
1281 Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const
1282 {
1283         BOOST_ASSERT(!size() || pos < size());
1284         return pimpl_->lookupChangeFull(pos);
1285 }
1286
1287
1288 bool Paragraph::isChanged(pos_type start, pos_type end) const
1289 {
1290         return pimpl_->isChanged(start, end);
1291 }
1292
1293
1294 bool Paragraph::isChangeEdited(pos_type start, pos_type end) const
1295 {
1296         return pimpl_->isChangeEdited(start, end);
1297 }
1298
1299
1300 void Paragraph::setChange(lyx::pos_type pos, Change::Type type)
1301 {
1302         pimpl_->setChange(pos, type);
1303
1304 }
1305
1306
1307 void Paragraph::markErased()
1308 {
1309         pimpl_->markErased();
1310 }
1311
1312
1313 void Paragraph::acceptChange(pos_type start, pos_type end)
1314 {
1315         return pimpl_->acceptChange(start, end);
1316 }
1317
1318
1319 void Paragraph::rejectChange(pos_type start, pos_type end)
1320 {
1321         return pimpl_->rejectChange(start, end);
1322 }
1323
1324
1325 Paragraph::value_type Paragraph::getChar(pos_type pos) const
1326 {
1327         // This is in the critical path!
1328         pos_type const siz = text_.size();
1329
1330         BOOST_ASSERT(pos <= siz);
1331
1332         if (pos == siz) {
1333                 lyxerr << "getChar() on pos " << pos << " in par id "
1334                        << id() << " of size " << siz
1335                        << "  is a bit silly !" << endl;
1336                 return '\0';
1337         }
1338
1339         return text_[pos];
1340 }
1341
1342
1343 int Paragraph::id() const
1344 {
1345         return pimpl_->id_;
1346 }
1347
1348
1349 void Paragraph::id(int i)
1350 {
1351         pimpl_->id_ = i;
1352 }
1353
1354
1355 LyXLayout_ptr const & Paragraph::layout() const
1356 {
1357 /*
1358         InsetOld * inset = inInset();
1359         if (inset && inset->lyxCode() == InsetOld::ENVIRONMENT_CODE)
1360                 return static_cast<InsetEnvironment*>(inset)->layout();
1361 */
1362         return layout_;
1363 }
1364
1365
1366 void Paragraph::layout(LyXLayout_ptr const & new_layout)
1367 {
1368         layout_ = new_layout;
1369 }
1370
1371
1372 UpdatableInset * Paragraph::inInset() const
1373 {
1374         return pimpl_->inset_owner;
1375 }
1376
1377
1378 void Paragraph::clearContents()
1379 {
1380         text_.clear();
1381 }
1382
1383
1384 void Paragraph::setChar(pos_type pos, value_type c)
1385 {
1386         text_[pos] = c;
1387 }
1388
1389
1390 ParagraphParameters & Paragraph::params()
1391 {
1392         return pimpl_->params;
1393 }
1394
1395
1396 ParagraphParameters const & Paragraph::params() const
1397 {
1398         return pimpl_->params;
1399 }
1400
1401
1402 bool Paragraph::isFreeSpacing() const
1403 {
1404         if (layout()->free_spacing)
1405                 return true;
1406
1407         // for now we just need this, later should we need this in some
1408         // other way we can always add a function to InsetOld too.
1409         if (pimpl_->inset_owner && pimpl_->inset_owner->owner())
1410                 return pimpl_->inset_owner->owner()->lyxCode() == InsetOld::ERT_CODE;
1411         return false;
1412 }
1413
1414
1415 bool Paragraph::allowEmpty() const
1416 {
1417         if (layout()->keepempty)
1418                 return true;
1419         if (pimpl_->inset_owner && pimpl_->inset_owner->owner())
1420                 return pimpl_->inset_owner->owner()->lyxCode() == InsetOld::ERT_CODE;
1421         return false;
1422 }