]> git.lyx.org Git - features.git/blob - src/paragraph_funcs.C
add a ParagraphList::insert, adjust other funcs accordingly
[features.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 = pit->previous()
292                 ? pit->previous()->getParLanguage(bparams) : doc_language;
293         if (language->babel() != previous_language->babel()) {
294
295                 if (!lyxrc.language_command_end.empty() &&
296                     previous_language->babel() != doc_language->babel()) {
297                         os << subst(lyxrc.language_command_end, "$$lang",
298                                     previous_language->babel())
299                            << endl;
300                         texrow.newline();
301                 }
302
303                 if (lyxrc.language_command_end.empty() ||
304                     language->babel() != doc_language->babel()) {
305                         os << subst(lyxrc.language_command_begin, "$$lang",
306                                     language->babel())
307                            << endl;
308                         texrow.newline();
309                 }
310         }
311
312         bool leftindent_open = false;
313         if (!pit->params().leftIndent().zero()) {
314                 os << "\\begin{LyXParagraphLeftIndent}{" <<
315                         pit->params().leftIndent().asLatexString() << "}\n";
316                 texrow.newline();
317                 leftindent_open = true;
318         }
319
320         if (style->isEnvironment()) {
321                 if (style->latextype == LATEX_LIST_ENVIRONMENT) {
322                         os << "\\begin{" << style->latexname() << "}{"
323                            << pit->params().labelWidthString() << "}\n";
324                 } else if (style->labeltype == LABEL_BIBLIO) {
325                         // ale970405
326                         os << "\\begin{" << style->latexname() << "}{"
327                            <<  bibitemWidest(buf)
328                            << "}\n";
329                 } else if (style->latextype == LATEX_ITEM_ENVIRONMENT) {
330                         os << "\\begin{" << style->latexname() << '}'
331                            << style->latexparam() << '\n';
332                 } else
333                         os << "\\begin{" << style->latexname() << '}'
334                            << style->latexparam() << '\n';
335                 texrow.newline();
336         }
337         ParagraphList::iterator par = pit;
338         do {
339                 par = TeXOnePar(buf, bparams, par, os, texrow, false);
340
341                 if (par != buf->paragraphs.end()&& par->params().depth() > pit->params().depth()) {
342                             if (par->layout()->isParagraph()) {
343
344                             // Thinko!
345                             // How to handle this? (Lgb)
346                             //&& !suffixIs(os, "\n\n")
347                                     //) {
348                                 // There should be at least one '\n' already
349                                 // but we need there to be two for Standard
350                                 // paragraphs that are depth-increment'ed to be
351                                 // output correctly.  However, tables can
352                                 // also be paragraphs so don't adjust them.
353                                 // ARRae
354                                 // Thinkee:
355                                 // Will it ever harm to have one '\n' too
356                                 // many? i.e. that we sometimes will have
357                                 // three in a row. (Lgb)
358                                 os << '\n';
359                                 texrow.newline();
360                         }
361                         par = TeXDeeper(buf, bparams, par, os, texrow);
362                 }
363         } while (par != buf->paragraphs.end()
364                  && par->layout() == pit->layout()
365                  && par->params().depth() == pit->params().depth()
366                  && par->params().leftIndent() == pit->params().leftIndent());
367
368         if (style->isEnvironment()) {
369                 os << "\\end{" << style->latexname() << "}\n";
370                 texrow.newline();
371         }
372
373         if (leftindent_open) {
374                 os << "\\end{LyXParagraphLeftIndent}\n";
375                 texrow.newline();
376         }
377
378         lyxerr[Debug::LATEX] << "TeXEnvironment...done " << &*par << endl;
379         return par;  // ale970302
380 }
381
382
383 namespace {
384
385 InsetOptArg * optArgInset(Paragraph const & par)
386 {
387         // Find the entry.
388         InsetList::iterator it = par.insetlist.begin();
389         InsetList::iterator end = par.insetlist.end();
390         for (; it != end; ++it) {
391                 Inset * ins = it.getInset();
392                 if (ins->lyxCode() == Inset::OPTARG_CODE) {
393                         return static_cast<InsetOptArg *>(ins);
394                 }
395         }
396         return 0;
397 }
398
399 } // end namespace
400
401
402 ParagraphList::iterator
403 TeXOnePar(Buffer const * buf,
404           BufferParams const & bparams,
405           ParagraphList::iterator pit,
406           ostream & os, TexRow & texrow,
407           bool moving_arg)
408 {
409         lyxerr[Debug::LATEX] << "TeXOnePar...     " << &*pit << endl;
410         Inset const * in = pit->inInset();
411         bool further_blank_line = false;
412         LyXLayout_ptr style;
413
414         // well we have to check if we are in an inset with unlimited
415         // lenght (all in one row) if that is true then we don't allow
416         // any special options in the paragraph and also we don't allow
417         // any environment other then "Standard" to be valid!
418         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
419                 style = pit->layout();
420
421                 if (pit->params().startOfAppendix()) {
422                         os << "\\appendix\n";
423                         texrow.newline();
424                 }
425
426                 if (!pit->params().spacing().isDefault()
427                         && (!pit->previous() || !pit->previous()->hasSameLayout(&*pit))) {
428                         os << pit->params().spacing().writeEnvirBegin() << '\n';
429                         texrow.newline();
430                 }
431
432                 if (style->isCommand()) {
433                         os << '\n';
434                         texrow.newline();
435                 }
436
437                 if (pit->params().pagebreakTop()) {
438                         os << "\\newpage";
439                         further_blank_line = true;
440                 }
441                 if (pit->params().spaceTop().kind() != VSpace::NONE) {
442                         os << pit->params().spaceTop().asLatexCommand(bparams);
443                         further_blank_line = true;
444                 }
445
446                 if (pit->params().lineTop()) {
447                         os << "\\lyxline{\\" << pit->getFont(bparams, 0).latexSize() << '}'
448                            << "\\vspace{-1\\parskip}";
449                         further_blank_line = true;
450                 }
451
452                 if (further_blank_line) {
453                         os << '\n';
454                         texrow.newline();
455                 }
456         } else {
457                 style = bparams.getLyXTextClass().defaultLayout();
458         }
459
460         Language const * language = pit->getParLanguage(bparams);
461         Language const * doc_language = bparams.language;
462         Language const * previous_language = pit->previous()
463                 ? pit->previous()->getParLanguage(bparams) : doc_language;
464
465         if (language->babel() != previous_language->babel()
466             // check if we already put language command in TeXEnvironment()
467             && !(style->isEnvironment()
468                  && (!pit->previous() ||
469                      (pit->previous()->layout() != pit->layout() &&
470                       pit->previous()->getDepth() <= pit->getDepth())
471                      || pit->previous()->getDepth() < pit->getDepth())))
472         {
473                 if (!lyxrc.language_command_end.empty() &&
474                     previous_language->babel() != doc_language->babel())
475                 {
476                         os << subst(lyxrc.language_command_end, "$$lang",
477                                     previous_language->babel())
478                            << endl;
479                         texrow.newline();
480                 }
481
482                 if (lyxrc.language_command_end.empty() ||
483                     language->babel() != doc_language->babel())
484                 {
485                         os << subst(lyxrc.language_command_begin, "$$lang",
486                                     language->babel())
487                            << endl;
488                         texrow.newline();
489                 }
490         }
491
492         if (bparams.inputenc == "auto" &&
493             language->encoding() != previous_language->encoding()) {
494                 os << "\\inputencoding{"
495                    << language->encoding()->LatexName()
496                    << "}\n";
497                 texrow.newline();
498         }
499
500         switch (style->latextype) {
501         case LATEX_COMMAND:
502                 os << '\\' << style->latexname();
503
504                 // Separate handling of optional argument inset.
505                 if (style->optionalargs == 1) {
506                         InsetOptArg * it = optArgInset(*pit);
507                         if (it)
508                                 it->latexOptional(buf, os, false, false);
509                 }
510                 else
511                         os << style->latexparam();
512                 break;
513         case LATEX_ITEM_ENVIRONMENT:
514         case LATEX_LIST_ENVIRONMENT:
515                 os << "\\item ";
516                 break;
517         case LATEX_BIB_ENVIRONMENT:
518                 // ignore this, the inset will write itself
519                 break;
520         default:
521                 break;
522         }
523
524         bool need_par = pit->simpleTeXOnePar(buf, bparams, os, texrow, moving_arg);
525
526         // Make sure that \\par is done with the font of the last
527         // character if this has another size as the default.
528         // This is necessary because LaTeX (and LyX on the screen)
529         // calculates the space between the baselines according
530         // to this font. (Matthias)
531         //
532         // Is this really needed ? (Dekel)
533         // We do not need to use to change the font for the last paragraph
534         // or for a command.
535         LyXFont const font =
536                 (pit->empty()
537                  ? pit->getLayoutFont(bparams) : pit->getFont(bparams, pit->size() - 1));
538
539         bool is_command = style->isCommand();
540
541         if (style->resfont.size() != font.size() && pit->next() && !is_command) {
542                 if (!need_par)
543                         os << '{';
544                 os << "\\" << font.latexSize() << " \\par}";
545         } else if (need_par) {
546                 os << "\\par}";
547         } else if (is_command)
548                 os << '}';
549
550         switch (style->latextype) {
551         case LATEX_ITEM_ENVIRONMENT:
552         case LATEX_LIST_ENVIRONMENT:
553                 if (pit->next() && (pit->params().depth() < pit->next()->params().depth())) {
554                         os << '\n';
555                         texrow.newline();
556                 }
557                 break;
558         case LATEX_ENVIRONMENT:
559                 // if its the last paragraph of the current environment
560                 // skip it otherwise fall through
561                 if (pit->next()
562                     && (pit->next()->layout() != pit->layout()
563                         || pit->next()->params().depth() != pit->params().depth()))
564                         break;
565                 // fall through possible
566         default:
567                 // we don't need it for the last paragraph!!!
568                 if (pit->next()) {
569                         os << '\n';
570                         texrow.newline();
571                 }
572         }
573
574         if ((in == 0) || !in->forceDefaultParagraphs(in)) {
575                 further_blank_line = false;
576                 if (pit->params().lineBottom()) {
577                         os << "\\lyxline{\\" << font.latexSize() << '}';
578                         further_blank_line = true;
579                 }
580
581                 if (pit->params().spaceBottom().kind() != VSpace::NONE) {
582                         os << pit->params().spaceBottom().asLatexCommand(bparams);
583                         further_blank_line = true;
584                 }
585
586                 if (pit->params().pagebreakBottom()) {
587                         os << "\\newpage";
588                         further_blank_line = true;
589                 }
590
591                 if (further_blank_line) {
592                         os << '\n';
593                         texrow.newline();
594                 }
595
596                 if (!pit->params().spacing().isDefault()
597                         && (!pit->next() || !pit->next()->hasSameLayout(&*pit))) {
598                         os << pit->params().spacing().writeEnvirEnd() << '\n';
599                         texrow.newline();
600                 }
601         }
602
603         // we don't need it for the last paragraph!!!
604         if (pit->next()) {
605                 os << '\n';
606                 texrow.newline();
607         } else {
608                 // Since \selectlanguage write the language to the aux file,
609                 // we need to reset the language at the end of footnote or
610                 // float.
611
612                 if (language->babel() != doc_language->babel()) {
613                         if (lyxrc.language_command_end.empty())
614                                 os << subst(lyxrc.language_command_begin,
615                                             "$$lang",
616                                             doc_language->babel())
617                                    << endl;
618                         else
619                                 os << subst(lyxrc.language_command_end,
620                                             "$$lang",
621                                             language->babel())
622                                    << endl;
623                         texrow.newline();
624                 }
625         }
626
627         lyxerr[Debug::LATEX] << "TeXOnePar...done " << pit->next() << endl;
628         return ++pit;
629 }