]> git.lyx.org Git - lyx.git/blob - src/paragraph_funcs.C
Oops...
[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(Buffer * buf,
33                     ParagraphList::iterator par,
34                     pos_type pos,
35                     int flag)
36 {
37         BufferParams const & bparams = buf->params;
38
39         // create a new paragraph, and insert into the list
40         ParagraphList::iterator tmp = buf->paragraphs.insert(boost::next(par),
41                                                              new Paragraph);
42
43         // without doing that we get a crash when typing <Return> at the
44         // end of a paragraph
45         tmp->layout(bparams.getLyXTextClass().defaultLayout());
46         // remember to set the inset_owner
47         tmp->setInsetOwner(par->inInset());
48
49         if (bparams.tracking_changes)
50                 tmp->trackChanges();
51
52         // this is an idea for a more userfriendly layout handling, I will
53         // see what the users say
54
55         // layout stays the same with latex-environments
56         if (flag) {
57                 tmp->layout(par->layout());
58                 tmp->setLabelWidthString(par->params().labelWidthString());
59         }
60
61         bool const isempty = (par->layout()->keepempty && par->empty());
62
63         if (!isempty && (par->size() > pos || par->empty() || flag == 2)) {
64                 tmp->layout(par->layout());
65                 tmp->params().align(par->params().align());
66                 tmp->setLabelWidthString(par->params().labelWidthString());
67
68                 tmp->params().lineBottom(par->params().lineBottom());
69                 par->params().lineBottom(false);
70                 tmp->params().pagebreakBottom(par->params().pagebreakBottom());
71                 par->params().pagebreakBottom(false);
72                 tmp->params().spaceBottom(par->params().spaceBottom());
73                 par->params().spaceBottom(VSpace(VSpace::NONE));
74
75                 tmp->params().depth(par->params().depth());
76                 tmp->params().noindent(par->params().noindent());
77
78                 // copy everything behind the break-position
79                 // to the new paragraph
80
81 #ifdef WITH_WARNINGS
82 #warning this seems wrong
83 #endif
84                 /* FIXME: if !keepempty, empty() == true, then we reach
85                  * here with size() == 0. So pos_end becomes - 1. Why
86                  * doesn't this cause problems ???
87                  */
88                 pos_type pos_end = par->size() - 1;
89                 pos_type i = pos;
90                 pos_type j = pos;
91
92                 for (; i <= pos_end; ++i) {
93                         Change::Type change(par->lookupChange(i));
94                         par->cutIntoMinibuffer(bparams, i);
95                         if (tmp->insertFromMinibuffer(j - pos)) {
96                                 tmp->setChange(j - pos, change);
97                                 ++j;
98                         }
99                 }
100                 for (i = pos_end; i >= pos; --i) {
101                         par->eraseIntern(i);
102                 }
103         }
104
105         if (pos)
106                 return;
107
108         tmp->params().lineTop(par->params().lineTop());
109         tmp->params().pagebreakTop(par->params().pagebreakTop());
110         tmp->params().spaceTop(par->params().spaceTop());
111         par->params().clear();
112
113         par->layout(bparams.getLyXTextClass().defaultLayout());
114
115         // layout stays the same with latex-environments
116         if (flag) {
117                 par->layout(tmp->layout());
118                 par->setLabelWidthString(tmp->params().labelWidthString());
119                 par->params().depth(tmp->params().depth());
120         }
121
122         // subtle, but needed to get empty pars working right
123         if (bparams.tracking_changes) {
124                 if (!par->size()) {
125                         par->cleanChanges();
126                 } else if (!tmp->size()) {
127                         tmp->cleanChanges();
128                 }
129         }
130 }
131
132
133 void breakParagraphConservative(Buffer * buf,
134                                 ParagraphList::iterator par,
135                                 pos_type pos)
136 {
137         BufferParams const & bparams = buf->params;
138
139         // create a new paragraph
140         ParagraphList::iterator tmp = buf->paragraphs.insert(boost::next(par),
141                                                              new Paragraph);
142         tmp->makeSameLayout(&*par);
143
144         // When can pos > Last()?
145         // I guess pos == Last() is possible.
146         if (par->size() > pos) {
147                 // copy everything behind the break-position to the new
148                 // paragraph
149                 pos_type pos_end = par->size() - 1;
150
151                 for (pos_type i = pos, j = pos; i <= pos_end; ++i) {
152                         par->cutIntoMinibuffer(bparams, i);
153                         if (tmp->insertFromMinibuffer(j - pos))
154                                 ++j;
155                 }
156
157                 for (pos_type k = pos_end; k >= pos; --k) {
158                         par->erase(k);
159                 }
160         }
161 }
162
163
164 void mergeParagraph(Buffer * buf, ParagraphList::iterator par)
165 {
166         BufferParams const & bparams = buf->params;
167
168         ParagraphList::iterator the_next = boost::next(par);
169
170         // first the DTP-stuff
171         par->params().lineBottom(the_next->params().lineBottom());
172         par->params().spaceBottom(the_next->params().spaceBottom());
173         par->params().pagebreakBottom(the_next->params().pagebreakBottom());
174
175         pos_type pos_end = the_next->size() - 1;
176         pos_type pos_insert = par->size();
177
178         // ok, now copy the paragraph
179         for (pos_type i = 0, j = 0; i <= pos_end; ++i) {
180                 the_next->cutIntoMinibuffer(bparams, i);
181                 if (par->insertFromMinibuffer(pos_insert + j))
182                         ++j;
183         }
184
185         buf->paragraphs.erase(the_next);
186 }
187
188
189 #if 0
190 Paragraph * depthHook(Paragraph * par, Paragraph::depth_type depth)
191 {
192         Paragraph * newpar = par;
193
194         do {
195                 newpar = newpar->previous();
196         } while (newpar && newpar->getDepth() > depth);
197
198         if (!newpar) {
199                 if (par->previous() || par->getDepth())
200                         lyxerr << "Error (depthHook): "
201                                << "no hook." << endl;
202                 newpar = par;
203         }
204         return newpar;
205 }
206
207
208 Paragraph * outerHook(Paragraph * par)
209 {
210         if (!par->getDepth())
211                 return 0;
212         return depthHook(par, Paragraph::depth_type(par->getDepth() - 1));
213 }
214
215
216 bool isFirstInSequence(Paragraph * par)
217 {
218         Paragraph const * dhook = depthHook(par, par->getDepth());
219         return (dhook == par
220                 || dhook->getLayout() != par->getLayout()
221                 || dhook->getDepth() != par->getDepth());
222 }
223
224
225 int getEndLabel(Paragraph * para, BufferParams const & bparams)
226 {
227         Paragraph * par = para;
228         while (par) {
229                 Paragraph::depth_type par_depth = par->getDepth();
230                 layout_type layout = par->getLayout();
231                 int const endlabeltype =
232                         textclasslist.Style(bparams.textclass,
233                                             layout).endlabeltype;
234                 if (endlabeltype != END_LABEL_NO_LABEL) {
235                         if (!para->next())
236                                 return endlabeltype;
237
238                         Paragraph::depth_type const next_depth =
239                                 para->next()->getDepth();
240                         if (par_depth > next_depth ||
241                             (par_depth == next_depth
242                              && layout != para->next()->getLayout()))
243                                 return endlabeltype;
244                         break;
245                 }
246                 if (par_depth == 0)
247                         break;
248                 par = outerHook(par);
249         }
250         return END_LABEL_NO_LABEL;
251 }
252 #endif
253
254
255 ParagraphList::iterator
256 TeXDeeper(Buffer const * buf,
257           BufferParams const & bparams,
258           ParagraphList::iterator pit,
259           ostream & os, TexRow & texrow)
260 {
261         lyxerr[Debug::LATEX] << "TeXDeeper...     " << &*pit << endl;
262         ParagraphList::iterator par = pit;
263
264         while (par != buf->paragraphs.end()&& par->params().depth() == pit->params().depth()) {
265                 if (par->layout()->isEnvironment()) {
266                         par = TeXEnvironment(buf, bparams, par,
267                                                   os, texrow);
268                 } else {
269                         par = TeXOnePar(buf, bparams, par,
270                                              os, texrow, false);
271                 }
272         }
273         lyxerr[Debug::LATEX] << "TeXDeeper...done " << &*par << endl;
274
275         return par;
276 }
277
278
279 ParagraphList::iterator
280 TeXEnvironment(Buffer const * buf,
281                BufferParams const & bparams,
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 != buf->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, par, os, texrow, false);
342
343                 if (par != buf->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, par, os, texrow);
364                 }
365         } while (par != buf->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::iterator pit,
408           ostream & os, TexRow & texrow,
409           bool moving_arg)
410 {
411         lyxerr[Debug::LATEX] << "TeXOnePar...     " << &*pit << endl;
412         Inset const * in = pit->inInset();
413         bool further_blank_line = false;
414         LyXLayout_ptr style;
415
416         // well we have to check if we are in an inset with unlimited
417         // lenght (all in one row) if that is true then we don't allow
418         // any special options in the paragraph and also we don't allow
419         // any environment other then "Standard" to be valid!
420         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
421                 style = pit->layout();
422
423                 if (pit->params().startOfAppendix()) {
424                         os << "\\appendix\n";
425                         texrow.newline();
426                 }
427
428                 if (!pit->params().spacing().isDefault()
429                         && (pit == buf->paragraphs.begin() || !boost::prior(pit)->hasSameLayout(&*pit))) {
430                         os << pit->params().spacing().writeEnvirBegin() << '\n';
431                         texrow.newline();
432                 }
433
434                 if (style->isCommand()) {
435                         os << '\n';
436                         texrow.newline();
437                 }
438
439                 if (pit->params().pagebreakTop()) {
440                         os << "\\newpage";
441                         further_blank_line = true;
442                 }
443                 if (pit->params().spaceTop().kind() != VSpace::NONE) {
444                         os << pit->params().spaceTop().asLatexCommand(bparams);
445                         further_blank_line = true;
446                 }
447
448                 if (pit->params().lineTop()) {
449                         os << "\\lyxline{\\" << pit->getFont(bparams, 0).latexSize() << '}'
450                            << "\\vspace{-1\\parskip}";
451                         further_blank_line = true;
452                 }
453
454                 if (further_blank_line) {
455                         os << '\n';
456                         texrow.newline();
457                 }
458         } else {
459                 style = bparams.getLyXTextClass().defaultLayout();
460         }
461
462         Language const * language = pit->getParLanguage(bparams);
463         Language const * doc_language = bparams.language;
464         Language const * previous_language =
465                 (pit != buf->paragraphs.begin())
466                 ? boost::prior(pit)->getParLanguage(bparams)
467                 : doc_language;
468
469         if (language->babel() != previous_language->babel()
470             // check if we already put language command in TeXEnvironment()
471             && !(style->isEnvironment()
472                  && (pit == buf->paragraphs.begin() ||
473                      (boost::prior(pit)->layout() != pit->layout() &&
474                       boost::prior(pit)->getDepth() <= pit->getDepth())
475                      || boost::prior(pit)->getDepth() < pit->getDepth())))
476         {
477                 if (!lyxrc.language_command_end.empty() &&
478                     previous_language->babel() != doc_language->babel())
479                 {
480                         os << subst(lyxrc.language_command_end, "$$lang",
481                                     previous_language->babel())
482                            << endl;
483                         texrow.newline();
484                 }
485
486                 if (lyxrc.language_command_end.empty() ||
487                     language->babel() != doc_language->babel())
488                 {
489                         os << subst(lyxrc.language_command_begin, "$$lang",
490                                     language->babel())
491                            << endl;
492                         texrow.newline();
493                 }
494         }
495
496         if (bparams.inputenc == "auto" &&
497             language->encoding() != previous_language->encoding()) {
498                 os << "\\inputencoding{"
499                    << language->encoding()->LatexName()
500                    << "}\n";
501                 texrow.newline();
502         }
503
504         switch (style->latextype) {
505         case LATEX_COMMAND:
506                 os << '\\' << style->latexname();
507
508                 // Separate handling of optional argument inset.
509                 if (style->optionalargs == 1) {
510                         InsetOptArg * it = optArgInset(*pit);
511                         if (it)
512                                 it->latexOptional(buf, os, false, false);
513                 }
514                 else
515                         os << style->latexparam();
516                 break;
517         case LATEX_ITEM_ENVIRONMENT:
518         case LATEX_LIST_ENVIRONMENT:
519                 os << "\\item ";
520                 break;
521         case LATEX_BIB_ENVIRONMENT:
522                 // ignore this, the inset will write itself
523                 break;
524         default:
525                 break;
526         }
527
528         bool need_par = pit->simpleTeXOnePar(buf, bparams, os, texrow, moving_arg);
529
530         // Make sure that \\par is done with the font of the last
531         // character if this has another size as the default.
532         // This is necessary because LaTeX (and LyX on the screen)
533         // calculates the space between the baselines according
534         // to this font. (Matthias)
535         //
536         // Is this really needed ? (Dekel)
537         // We do not need to use to change the font for the last paragraph
538         // or for a command.
539         LyXFont const font =
540                 (pit->empty()
541                  ? pit->getLayoutFont(bparams) : pit->getFont(bparams, pit->size() - 1));
542
543         bool is_command = style->isCommand();
544
545         if (style->resfont.size() != font.size()
546             && boost::next(pit) != buf->paragraphs.end()
547             && !is_command) {
548                 if (!need_par)
549                         os << '{';
550                 os << "\\" << font.latexSize() << " \\par}";
551         } else if (need_par) {
552                 os << "\\par}";
553         } else if (is_command)
554                 os << '}';
555
556         switch (style->latextype) {
557         case LATEX_ITEM_ENVIRONMENT:
558         case LATEX_LIST_ENVIRONMENT:
559                 if (boost::next(pit) != buf->paragraphs.end()
560                     && (pit->params().depth() < boost::next(pit)->params().depth())) {
561                         os << '\n';
562                         texrow.newline();
563                 }
564                 break;
565         case LATEX_ENVIRONMENT:
566                 // if its the last paragraph of the current environment
567                 // skip it otherwise fall through
568                 if (boost::next(pit) != buf->paragraphs.end()
569                     && (boost::next(pit)->layout() != pit->layout()
570                         || boost::next(pit)->params().depth() != pit->params().depth()))
571                         break;
572                 // fall through possible
573         default:
574                 // we don't need it for the last paragraph!!!
575                 if (boost::next(pit) != buf->paragraphs.end()) {
576                         os << '\n';
577                         texrow.newline();
578                 }
579         }
580
581         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
582                 further_blank_line = false;
583                 if (pit->params().lineBottom()) {
584                         os << "\\lyxline{\\" << font.latexSize() << '}';
585                         further_blank_line = true;
586                 }
587
588                 if (pit->params().spaceBottom().kind() != VSpace::NONE) {
589                         os << pit->params().spaceBottom().asLatexCommand(bparams);
590                         further_blank_line = true;
591                 }
592
593                 if (pit->params().pagebreakBottom()) {
594                         os << "\\newpage";
595                         further_blank_line = true;
596                 }
597
598                 if (further_blank_line) {
599                         os << '\n';
600                         texrow.newline();
601                 }
602
603                 if (!pit->params().spacing().isDefault()
604                         && (boost::next(pit) == buf->paragraphs.end()|| !boost::next(pit)->hasSameLayout(&*pit))) {
605                         os << pit->params().spacing().writeEnvirEnd() << '\n';
606                         texrow.newline();
607                 }
608         }
609
610         // we don't need it for the last paragraph!!!
611         if (boost::next(pit) != buf->paragraphs.end()) {
612                 os << '\n';
613                 texrow.newline();
614         } else {
615                 // Since \selectlanguage write the language to the aux file,
616                 // we need to reset the language at the end of footnote or
617                 // float.
618
619                 if (language->babel() != doc_language->babel()) {
620                         if (lyxrc.language_command_end.empty())
621                                 os << subst(lyxrc.language_command_begin,
622                                             "$$lang",
623                                             doc_language->babel())
624                                    << endl;
625                         else
626                                 os << subst(lyxrc.language_command_end,
627                                             "$$lang",
628                                             language->babel())
629                                    << endl;
630                         texrow.newline();
631                 }
632         }
633
634         lyxerr[Debug::LATEX] << "TeXOnePar...done " << &*boost::next(pit) << endl;
635         return ++pit;
636 }