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