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