]> git.lyx.org Git - lyx.git/blob - src/paragraph_funcs.C
a7b5c4546b7fab1326517057326eded97333d029
[lyx.git] / src / paragraph_funcs.C
1 /**
2  * \file paragraph_funcs.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS
9  */
10
11 #include <config.h>
12
13 #include "paragraph_funcs.h"
14 #include "paragraph_pimpl.h"
15 #include "buffer.h"
16 #include "ParagraphParameters.h"
17 #include "lyxtextclasslist.h"
18 #include "debug.h"
19 #include "language.h"
20 #include "encoding.h"
21 #include "lyxrc.h"
22 #include "support/lstrings.h"
23 #include "insets/insetoptarg.h"
24
25 extern string bibitemWidest(Buffer const *);
26
27 using lyx::pos_type;
28 //using lyx::layout_type;
29 using std::endl;
30 using std::ostream;
31
32 void breakParagraph(BufferParams const & bparams,
33                     ParagraphList & paragraphs,
34                     ParagraphList::iterator par,
35                     pos_type pos,
36                     int flag)
37 {
38         // create a new paragraph, and insert into the list
39         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
40                                                         new Paragraph);
41
42         // without doing that we get a crash when typing <Return> at the
43         // end of a paragraph
44         tmp->layout(bparams.getLyXTextClass().defaultLayout());
45         // remember to set the inset_owner
46         tmp->setInsetOwner(par->inInset());
47
48         if (bparams.tracking_changes)
49                 tmp->trackChanges();
50
51         // this is an idea for a more userfriendly layout handling, I will
52         // see what the users say
53
54         // layout stays the same with latex-environments
55         if (flag) {
56                 tmp->layout(par->layout());
57                 tmp->setLabelWidthString(par->params().labelWidthString());
58         }
59
60         bool const isempty = (par->layout()->keepempty && par->empty());
61
62         if (!isempty && (par->size() > pos || par->empty() || flag == 2)) {
63                 tmp->layout(par->layout());
64                 tmp->params().align(par->params().align());
65                 tmp->setLabelWidthString(par->params().labelWidthString());
66
67                 tmp->params().lineBottom(par->params().lineBottom());
68                 par->params().lineBottom(false);
69                 tmp->params().pagebreakBottom(par->params().pagebreakBottom());
70                 par->params().pagebreakBottom(false);
71                 tmp->params().spaceBottom(par->params().spaceBottom());
72                 par->params().spaceBottom(VSpace(VSpace::NONE));
73
74                 tmp->params().depth(par->params().depth());
75                 tmp->params().noindent(par->params().noindent());
76
77                 // copy everything behind the break-position
78                 // to the new paragraph
79
80 #ifdef WITH_WARNINGS
81 #warning this seems wrong
82 #endif
83                 /* FIXME: if !keepempty, empty() == true, then we reach
84                  * here with size() == 0. So pos_end becomes - 1. Why
85                  * doesn't this cause problems ???
86                  */
87                 pos_type pos_end = par->size() - 1;
88                 pos_type i = pos;
89                 pos_type j = pos;
90
91                 for (; i <= pos_end; ++i) {
92                         Change::Type change(par->lookupChange(i));
93                         par->cutIntoMinibuffer(bparams, i);
94                         if (tmp->insertFromMinibuffer(j - pos)) {
95                                 tmp->setChange(j - pos, change);
96                                 ++j;
97                         }
98                 }
99                 for (i = pos_end; i >= pos; --i) {
100                         par->eraseIntern(i);
101                 }
102         }
103
104         if (pos)
105                 return;
106
107         tmp->params().lineTop(par->params().lineTop());
108         tmp->params().pagebreakTop(par->params().pagebreakTop());
109         tmp->params().spaceTop(par->params().spaceTop());
110         par->params().clear();
111
112         par->layout(bparams.getLyXTextClass().defaultLayout());
113
114         // layout stays the same with latex-environments
115         if (flag) {
116                 par->layout(tmp->layout());
117                 par->setLabelWidthString(tmp->params().labelWidthString());
118                 par->params().depth(tmp->params().depth());
119         }
120
121         // subtle, but needed to get empty pars working right
122         if (bparams.tracking_changes) {
123                 if (!par->size()) {
124                         par->cleanChanges();
125                 } else if (!tmp->size()) {
126                         tmp->cleanChanges();
127                 }
128         }
129 }
130
131
132 void breakParagraphConservative(BufferParams const & bparams,
133                                 ParagraphList & paragraphs,
134                                 ParagraphList::iterator par,
135                                 pos_type pos)
136 {
137         // create a new paragraph
138         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
139                                                         new Paragraph);
140         tmp->makeSameLayout(&*par);
141
142         // When can pos > Last()?
143         // I guess pos == Last() is possible.
144         if (par->size() > pos) {
145                 // copy everything behind the break-position to the new
146                 // paragraph
147                 pos_type pos_end = par->size() - 1;
148
149                 for (pos_type i = pos, j = pos; i <= pos_end; ++i) {
150                         par->cutIntoMinibuffer(bparams, i);
151                         if (tmp->insertFromMinibuffer(j - pos))
152                                 ++j;
153                 }
154
155                 for (pos_type k = pos_end; k >= pos; --k) {
156                         par->erase(k);
157                 }
158         }
159 }
160
161
162 void mergeParagraph(BufferParams const & bparams,
163                     ParagraphList & paragraphs,
164                     ParagraphList::iterator par)
165 {
166         ParagraphList::iterator the_next = boost::next(par);
167
168         // first the DTP-stuff
169         par->params().lineBottom(the_next->params().lineBottom());
170         par->params().spaceBottom(the_next->params().spaceBottom());
171         par->params().pagebreakBottom(the_next->params().pagebreakBottom());
172
173         pos_type pos_end = the_next->size() - 1;
174         pos_type pos_insert = par->size();
175
176         // ok, now copy the paragraph
177         for (pos_type i = 0, j = 0; i <= pos_end; ++i) {
178                 the_next->cutIntoMinibuffer(bparams, i);
179                 if (par->insertFromMinibuffer(pos_insert + j))
180                         ++j;
181         }
182
183         paragraphs.erase(the_next);
184 }
185
186
187 #if 0
188 Paragraph * depthHook(Paragraph * par, Paragraph::depth_type depth)
189 {
190         Paragraph * newpar = par;
191
192         do {
193                 newpar = newpar->previous();
194         } while (newpar && newpar->getDepth() > depth);
195
196         if (!newpar) {
197                 if (par->previous() || par->getDepth())
198                         lyxerr << "Error (depthHook): "
199                                << "no hook." << endl;
200                 newpar = par;
201         }
202         return newpar;
203 }
204
205
206 Paragraph * outerHook(Paragraph * par)
207 {
208         if (!par->getDepth())
209                 return 0;
210         return depthHook(par, Paragraph::depth_type(par->getDepth() - 1));
211 }
212
213
214 bool isFirstInSequence(Paragraph * par)
215 {
216         Paragraph const * dhook = depthHook(par, par->getDepth());
217         return (dhook == par
218                 || dhook->getLayout() != par->getLayout()
219                 || dhook->getDepth() != par->getDepth());
220 }
221
222
223 int getEndLabel(Paragraph * para, BufferParams const & bparams)
224 {
225         Paragraph * par = para;
226         while (par) {
227                 Paragraph::depth_type par_depth = par->getDepth();
228                 layout_type layout = par->getLayout();
229                 int const endlabeltype =
230                         textclasslist.Style(bparams.textclass,
231                                             layout).endlabeltype;
232                 if (endlabeltype != END_LABEL_NO_LABEL) {
233                         if (!para->next())
234                                 return endlabeltype;
235
236                         Paragraph::depth_type const next_depth =
237                                 para->next()->getDepth();
238                         if (par_depth > next_depth ||
239                             (par_depth == next_depth
240                              && layout != para->next()->getLayout()))
241                                 return endlabeltype;
242                         break;
243                 }
244                 if (par_depth == 0)
245                         break;
246                 par = outerHook(par);
247         }
248         return END_LABEL_NO_LABEL;
249 }
250 #endif
251
252
253 ParagraphList::iterator
254 TeXDeeper(Buffer const * buf,
255           BufferParams const & bparams,
256           ParagraphList const & paragraphs,
257           ParagraphList::iterator pit,
258           ostream & os, TexRow & texrow)
259 {
260         lyxerr[Debug::LATEX] << "TeXDeeper...     " << &*pit << endl;
261         ParagraphList::iterator par = pit;
262
263         while (par != paragraphs.end()&& par->params().depth() == pit->params().depth()) {
264                 if (par->layout()->isEnvironment()) {
265                         par = TeXEnvironment(buf, bparams, paragraphs, par,
266                                                   os, texrow);
267                 } else {
268                         par = TeXOnePar(buf, bparams, paragraphs, par,
269                                              os, texrow, false);
270                 }
271         }
272         lyxerr[Debug::LATEX] << "TeXDeeper...done " << &*par << endl;
273
274         return par;
275 }
276
277
278 ParagraphList::iterator
279 TeXEnvironment(Buffer const * buf,
280                BufferParams const & bparams,
281                ParagraphList const & paragraphs,
282                ParagraphList::iterator pit,
283                ostream & os, TexRow & texrow)
284 {
285         lyxerr[Debug::LATEX] << "TeXEnvironment...     " << &*pit << endl;
286
287         LyXLayout_ptr const & style = pit->layout();
288
289         Language const * language = pit->getParLanguage(bparams);
290         Language const * doc_language = bparams.language;
291         Language const * previous_language =
292                 (pit != paragraphs.begin())
293                 ? boost::prior(pit)->getParLanguage(bparams)
294                 : doc_language;
295         if (language->babel() != previous_language->babel()) {
296
297                 if (!lyxrc.language_command_end.empty() &&
298                     previous_language->babel() != doc_language->babel()) {
299                         os << subst(lyxrc.language_command_end, "$$lang",
300                                     previous_language->babel())
301                            << endl;
302                         texrow.newline();
303                 }
304
305                 if (lyxrc.language_command_end.empty() ||
306                     language->babel() != doc_language->babel()) {
307                         os << subst(lyxrc.language_command_begin, "$$lang",
308                                     language->babel())
309                            << endl;
310                         texrow.newline();
311                 }
312         }
313
314         bool leftindent_open = false;
315         if (!pit->params().leftIndent().zero()) {
316                 os << "\\begin{LyXParagraphLeftIndent}{" <<
317                         pit->params().leftIndent().asLatexString() << "}\n";
318                 texrow.newline();
319                 leftindent_open = true;
320         }
321
322         if (style->isEnvironment()) {
323                 if (style->latextype == LATEX_LIST_ENVIRONMENT) {
324                         os << "\\begin{" << style->latexname() << "}{"
325                            << pit->params().labelWidthString() << "}\n";
326                 } else if (style->labeltype == LABEL_BIBLIO) {
327                         // ale970405
328                         os << "\\begin{" << style->latexname() << "}{"
329                            <<  bibitemWidest(buf)
330                            << "}\n";
331                 } else if (style->latextype == LATEX_ITEM_ENVIRONMENT) {
332                         os << "\\begin{" << style->latexname() << '}'
333                            << style->latexparam() << '\n';
334                 } else
335                         os << "\\begin{" << style->latexname() << '}'
336                            << style->latexparam() << '\n';
337                 texrow.newline();
338         }
339         ParagraphList::iterator par = pit;
340         do {
341                 par = TeXOnePar(buf, bparams, paragraphs, par, os, texrow, false);
342
343                 if (par != paragraphs.end()&& par->params().depth() > pit->params().depth()) {
344                             if (par->layout()->isParagraph()) {
345
346                             // Thinko!
347                             // How to handle this? (Lgb)
348                             //&& !suffixIs(os, "\n\n")
349                                     //) {
350                                 // There should be at least one '\n' already
351                                 // but we need there to be two for Standard
352                                 // paragraphs that are depth-increment'ed to be
353                                 // output correctly.  However, tables can
354                                 // also be paragraphs so don't adjust them.
355                                 // ARRae
356                                 // Thinkee:
357                                 // Will it ever harm to have one '\n' too
358                                 // many? i.e. that we sometimes will have
359                                 // three in a row. (Lgb)
360                                 os << '\n';
361                                 texrow.newline();
362                         }
363                         par = TeXDeeper(buf, bparams, paragraphs, par, os, texrow);
364                 }
365         } while (par != paragraphs.end()
366                  && par->layout() == pit->layout()
367                  && par->params().depth() == pit->params().depth()
368                  && par->params().leftIndent() == pit->params().leftIndent());
369
370         if (style->isEnvironment()) {
371                 os << "\\end{" << style->latexname() << "}\n";
372                 texrow.newline();
373         }
374
375         if (leftindent_open) {
376                 os << "\\end{LyXParagraphLeftIndent}\n";
377                 texrow.newline();
378         }
379
380         lyxerr[Debug::LATEX] << "TeXEnvironment...done " << &*par << endl;
381         return par;  // ale970302
382 }
383
384
385 namespace {
386
387 InsetOptArg * optArgInset(Paragraph const & par)
388 {
389         // Find the entry.
390         InsetList::iterator it = par.insetlist.begin();
391         InsetList::iterator end = par.insetlist.end();
392         for (; it != end; ++it) {
393                 Inset * ins = it.getInset();
394                 if (ins->lyxCode() == Inset::OPTARG_CODE) {
395                         return static_cast<InsetOptArg *>(ins);
396                 }
397         }
398         return 0;
399 }
400
401 } // end namespace
402
403
404 ParagraphList::iterator
405 TeXOnePar(Buffer const * buf,
406           BufferParams const & bparams,
407           ParagraphList const & paragraphs,
408           ParagraphList::iterator pit,
409           ostream & os, TexRow & texrow,
410           bool moving_arg)
411 {
412         lyxerr[Debug::LATEX] << "TeXOnePar...     " << &*pit << endl;
413         Inset const * in = pit->inInset();
414         bool further_blank_line = false;
415         LyXLayout_ptr style;
416
417         // well we have to check if we are in an inset with unlimited
418         // lenght (all in one row) if that is true then we don't allow
419         // any special options in the paragraph and also we don't allow
420         // any environment other then "Standard" to be valid!
421         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
422                 style = pit->layout();
423
424                 if (pit->params().startOfAppendix()) {
425                         os << "\\appendix\n";
426                         texrow.newline();
427                 }
428
429                 if (!pit->params().spacing().isDefault()
430                         && (pit == paragraphs.begin() || !boost::prior(pit)->hasSameLayout(&*pit))) {
431                         os << pit->params().spacing().writeEnvirBegin() << '\n';
432                         texrow.newline();
433                 }
434
435                 if (style->isCommand()) {
436                         os << '\n';
437                         texrow.newline();
438                 }
439
440                 if (pit->params().pagebreakTop()) {
441                         os << "\\newpage";
442                         further_blank_line = true;
443                 }
444                 if (pit->params().spaceTop().kind() != VSpace::NONE) {
445                         os << pit->params().spaceTop().asLatexCommand(bparams);
446                         further_blank_line = true;
447                 }
448
449                 if (pit->params().lineTop()) {
450                         os << "\\lyxline{\\" << pit->getFont(bparams, 0).latexSize() << '}'
451                            << "\\vspace{-1\\parskip}";
452                         further_blank_line = true;
453                 }
454
455                 if (further_blank_line) {
456                         os << '\n';
457                         texrow.newline();
458                 }
459         } else {
460                 style = bparams.getLyXTextClass().defaultLayout();
461         }
462
463         Language const * language = pit->getParLanguage(bparams);
464         Language const * doc_language = bparams.language;
465         Language const * previous_language =
466                 (pit != paragraphs.begin())
467                 ? boost::prior(pit)->getParLanguage(bparams)
468                 : doc_language;
469
470         if (language->babel() != previous_language->babel()
471             // check if we already put language command in TeXEnvironment()
472             && !(style->isEnvironment()
473                  && (pit == paragraphs.begin() ||
474                      (boost::prior(pit)->layout() != pit->layout() &&
475                       boost::prior(pit)->getDepth() <= pit->getDepth())
476                      || boost::prior(pit)->getDepth() < pit->getDepth())))
477         {
478                 if (!lyxrc.language_command_end.empty() &&
479                     previous_language->babel() != doc_language->babel())
480                 {
481                         os << subst(lyxrc.language_command_end, "$$lang",
482                                     previous_language->babel())
483                            << endl;
484                         texrow.newline();
485                 }
486
487                 if (lyxrc.language_command_end.empty() ||
488                     language->babel() != doc_language->babel())
489                 {
490                         os << subst(lyxrc.language_command_begin, "$$lang",
491                                     language->babel())
492                            << endl;
493                         texrow.newline();
494                 }
495         }
496
497         if (bparams.inputenc == "auto" &&
498             language->encoding() != previous_language->encoding()) {
499                 os << "\\inputencoding{"
500                    << language->encoding()->LatexName()
501                    << "}\n";
502                 texrow.newline();
503         }
504
505         switch (style->latextype) {
506         case LATEX_COMMAND:
507                 os << '\\' << style->latexname();
508
509                 // Separate handling of optional argument inset.
510                 if (style->optionalargs == 1) {
511                         InsetOptArg * it = optArgInset(*pit);
512                         if (it)
513                                 it->latexOptional(buf, os, false, false);
514                 }
515                 else
516                         os << style->latexparam();
517                 break;
518         case LATEX_ITEM_ENVIRONMENT:
519         case LATEX_LIST_ENVIRONMENT:
520                 os << "\\item ";
521                 break;
522         case LATEX_BIB_ENVIRONMENT:
523                 // ignore this, the inset will write itself
524                 break;
525         default:
526                 break;
527         }
528
529         bool need_par = pit->simpleTeXOnePar(buf, bparams, os, texrow, moving_arg);
530
531         // Make sure that \\par is done with the font of the last
532         // character if this has another size as the default.
533         // This is necessary because LaTeX (and LyX on the screen)
534         // calculates the space between the baselines according
535         // to this font. (Matthias)
536         //
537         // Is this really needed ? (Dekel)
538         // We do not need to use to change the font for the last paragraph
539         // or for a command.
540         LyXFont const font =
541                 (pit->empty()
542                  ? pit->getLayoutFont(bparams) : pit->getFont(bparams, pit->size() - 1));
543
544         bool is_command = style->isCommand();
545
546         if (style->resfont.size() != font.size()
547             && boost::next(pit) != paragraphs.end()
548             && !is_command) {
549                 if (!need_par)
550                         os << '{';
551                 os << "\\" << font.latexSize() << " \\par}";
552         } else if (need_par) {
553                 os << "\\par}";
554         } else if (is_command)
555                 os << '}';
556
557         switch (style->latextype) {
558         case LATEX_ITEM_ENVIRONMENT:
559         case LATEX_LIST_ENVIRONMENT:
560                 if (boost::next(pit) != paragraphs.end()
561                     && (pit->params().depth() < boost::next(pit)->params().depth())) {
562                         os << '\n';
563                         texrow.newline();
564                 }
565                 break;
566         case LATEX_ENVIRONMENT:
567                 // if its the last paragraph of the current environment
568                 // skip it otherwise fall through
569                 if (boost::next(pit) != paragraphs.end()
570                     && (boost::next(pit)->layout() != pit->layout()
571                         || boost::next(pit)->params().depth() != pit->params().depth()))
572                         break;
573                 // fall through possible
574         default:
575                 // we don't need it for the last paragraph!!!
576                 if (boost::next(pit) != paragraphs.end()) {
577                         os << '\n';
578                         texrow.newline();
579                 }
580         }
581
582         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
583                 further_blank_line = false;
584                 if (pit->params().lineBottom()) {
585                         os << "\\lyxline{\\" << font.latexSize() << '}';
586                         further_blank_line = true;
587                 }
588
589                 if (pit->params().spaceBottom().kind() != VSpace::NONE) {
590                         os << pit->params().spaceBottom().asLatexCommand(bparams);
591                         further_blank_line = true;
592                 }
593
594                 if (pit->params().pagebreakBottom()) {
595                         os << "\\newpage";
596                         further_blank_line = true;
597                 }
598
599                 if (further_blank_line) {
600                         os << '\n';
601                         texrow.newline();
602                 }
603
604                 if (!pit->params().spacing().isDefault()
605                         && (boost::next(pit) == paragraphs.end()|| !boost::next(pit)->hasSameLayout(&*pit))) {
606                         os << pit->params().spacing().writeEnvirEnd() << '\n';
607                         texrow.newline();
608                 }
609         }
610
611         // we don't need it for the last paragraph!!!
612         if (boost::next(pit) != paragraphs.end()) {
613                 os << '\n';
614                 texrow.newline();
615         } else {
616                 // Since \selectlanguage write the language to the aux file,
617                 // we need to reset the language at the end of footnote or
618                 // float.
619
620                 if (language->babel() != doc_language->babel()) {
621                         if (lyxrc.language_command_end.empty())
622                                 os << subst(lyxrc.language_command_begin,
623                                             "$$lang",
624                                             doc_language->babel())
625                                    << endl;
626                         else
627                                 os << subst(lyxrc.language_command_end,
628                                             "$$lang",
629                                             language->babel())
630                                    << endl;
631                         texrow.newline();
632                 }
633         }
634
635         lyxerr[Debug::LATEX] << "TeXOnePar...done " << &*boost::next(pit) << endl;
636         return ++pit;
637 }
638
639
640 //
641 // LaTeX all paragraphs from par to endpar, if endpar == 0 then to the end
642 //
643 void latexParagraphs(Buffer const * buf,
644                      ParagraphList const & paragraphs,
645                      ParagraphList::iterator par,
646                      ParagraphList::iterator endpar,
647                      ostream & ofs,
648                      TexRow & texrow,
649                      bool moving_arg)
650 {
651         bool was_title = false;
652         bool already_title = false;
653         LyXTextClass const & tclass = buf->params.getLyXTextClass();
654
655         // if only_body
656         while (par != endpar) {
657                 Inset * in = par->inInset();
658                 // well we have to check if we are in an inset with unlimited
659                 // length (all in one row) if that is true then we don't allow
660                 // any special options in the paragraph and also we don't allow
661                 // any environment other then "Standard" to be valid!
662                 if ((in == 0) || !in->forceDefaultParagraphs(in)) {
663                         LyXLayout_ptr const & layout = par->layout();
664
665                         if (layout->intitle) {
666                                 if (already_title) {
667                                         lyxerr <<"Error in latexParagraphs: You"
668                                                 " should not mix title layouts"
669                                                 " with normal ones." << endl;
670                                 } else if (!was_title) {
671                                         was_title = true;
672                                         if (tclass.titletype() == TITLE_ENVIRONMENT) {
673                                                 ofs << "\\begin{"
674                                                     << tclass.titlename()
675                                                     << "}\n";
676                                                 texrow.newline();
677                                         }
678                                 }
679                         } else if (was_title && !already_title) {
680                                 if (tclass.titletype() == TITLE_ENVIRONMENT) {
681                                         ofs << "\\end{" << tclass.titlename()
682                                             << "}\n";
683                                 }
684                                 else {
685                                         ofs << "\\" << tclass.titlename()
686                                             << "\n";
687                                 }
688                                 texrow.newline();
689                                 already_title = true;
690                                 was_title = false;
691                         }
692
693                         if (layout->isEnvironment() ||
694                                 !par->params().leftIndent().zero())
695                         {
696                                 par = TeXEnvironment(buf, buf->params, paragraphs, par, ofs, texrow);
697                         } else {
698                                 par = TeXOnePar(buf, buf->params, paragraphs, par, ofs, texrow, moving_arg);
699                         }
700                 } else {
701                         par = TeXOnePar(buf, buf->params, paragraphs, par, ofs, texrow, moving_arg);
702                 }
703         }
704         // It might be that we only have a title in this document
705         if (was_title && !already_title) {
706                 if (tclass.titletype() == TITLE_ENVIRONMENT) {
707                         ofs << "\\end{" << tclass.titlename()
708                             << "}\n";
709                 }
710                 else {
711                         ofs << "\\" << tclass.titlename()
712                             << "\n";
713                                 }
714                 texrow.newline();
715         }
716 }