]> git.lyx.org Git - lyx.git/blob - src/paragraph_funcs.C
move space above/below from Paragraph into a separate vspace inset.
[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
15 #include "buffer.h"
16 #include "bufferparams.h"
17
18 #include "debug.h"
19 #include "encoding.h"
20 #include "errorlist.h"
21 #include "factory.h"
22 #include "gettext.h"
23 #include "iterators.h"
24 #include "language.h"
25 #include "lyxlex.h"
26 #include "lyxrc.h"
27 #include "outputparams.h"
28 #include "paragraph_pimpl.h"
29 #include "sgml.h"
30 #include "texrow.h"
31 #include "vspace.h"
32
33 #include "insets/insetbibitem.h"
34 #include "insets/insethfill.h"
35 #include "insets/insetlatexaccent.h"
36 #include "insets/insetline.h"
37 #include "insets/insetnewline.h"
38 #include "insets/insetpagebreak.h"
39 #include "insets/insetoptarg.h"
40 #include "insets/insetspace.h"
41 #include "insets/insetspecialchar.h"
42 #include "insets/insettabular.h"
43
44 #include "support/filetools.h"
45 #include "support/lstrings.h"
46 #include "support/lyxlib.h"
47 #include "support/std_sstream.h"
48
49 #include <vector>
50
51 using lyx::pos_type;
52
53 using lyx::support::ascii_lowercase;
54 using lyx::support::atoi;
55 using lyx::support::bformat;
56 using lyx::support::compare_ascii_no_case;
57 using lyx::support::compare_no_case;
58 using lyx::support::contains;
59 using lyx::support::split;
60 using lyx::support::subst;
61
62 using std::auto_ptr;
63 using std::endl;
64 using std::string;
65 using std::vector;
66 using std::istringstream;
67 using std::ostream;
68 using std::pair;
69
70
71 namespace {
72
73 bool moveItem(Paragraph & from, Paragraph & to,
74         BufferParams const & params, pos_type i, pos_type j)
75 {
76         char const tmpchar = from.getChar(i);
77         LyXFont tmpfont = from.getFontSettings(params, i);
78
79         if (tmpchar == Paragraph::META_INSET) {
80                 InsetOld * tmpinset = 0;
81                 if (from.getInset(i)) {
82                         // the inset is not in a paragraph anymore
83                         tmpinset = from.insetlist.release(i);
84                 }
85
86                 if (!to.insetAllowed(tmpinset->lyxCode()))
87                         return false;
88                 to.insertInset(j, tmpinset, tmpfont);
89         } else {
90                 if (!to.checkInsertChar(tmpfont))
91                         return false;
92                 to.insertChar(j, tmpchar, tmpfont);
93         }
94         return true;
95 }
96
97 }
98
99
100 void breakParagraph(BufferParams const & bparams,
101                     ParagraphList & paragraphs,
102                     ParagraphList::iterator par,
103                     pos_type pos,
104                     int flag)
105 {
106         // create a new paragraph, and insert into the list
107         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
108                                                         Paragraph());
109
110         // without doing that we get a crash when typing <Return> at the
111         // end of a paragraph
112         tmp->layout(bparams.getLyXTextClass().defaultLayout());
113         // remember to set the inset_owner
114         tmp->setInsetOwner(par->inInset());
115
116         if (bparams.tracking_changes)
117                 tmp->trackChanges();
118
119         // this is an idea for a more userfriendly layout handling, I will
120         // see what the users say
121
122         // layout stays the same with latex-environments
123         if (flag) {
124                 tmp->layout(par->layout());
125                 tmp->setLabelWidthString(par->params().labelWidthString());
126         }
127
128         bool const isempty = (par->allowEmpty() && par->empty());
129
130         if (!isempty && (par->size() > pos || par->empty() || flag == 2)) {
131                 tmp->layout(par->layout());
132                 tmp->params().align(par->params().align());
133                 tmp->setLabelWidthString(par->params().labelWidthString());
134
135                 tmp->params().depth(par->params().depth());
136                 tmp->params().noindent(par->params().noindent());
137
138                 // copy everything behind the break-position
139                 // to the new paragraph
140
141 #ifdef WITH_WARNINGS
142 #warning this seems wrong
143 #endif
144                 /* FIXME: if !keepempty, empty() == true, then we reach
145                  * here with size() == 0. So pos_end becomes - 1. Why
146                  * doesn't this cause problems ???
147                  */
148                 pos_type pos_end = par->size() - 1;
149                 pos_type i = pos;
150                 pos_type j = pos;
151
152                 for (; i <= pos_end; ++i) {
153                         Change::Type change = par->lookupChange(i);
154                         if (moveItem(*par, *tmp, bparams, i, j - pos)) {
155                                 tmp->setChange(j - pos, change);
156                                 ++j;
157                         }
158                 }
159
160                 for (i = pos_end; i >= pos; --i)
161                         par->eraseIntern(i);
162         }
163
164         if (pos)
165                 return;
166
167         par->params().clear();
168
169         par->layout(bparams.getLyXTextClass().defaultLayout());
170
171         // layout stays the same with latex-environments
172         if (flag) {
173                 par->layout(tmp->layout());
174                 par->setLabelWidthString(tmp->params().labelWidthString());
175                 par->params().depth(tmp->params().depth());
176         }
177
178         // subtle, but needed to get empty pars working right
179         if (bparams.tracking_changes) {
180                 if (!par->size()) {
181                         par->cleanChanges();
182                 } else if (!tmp->size()) {
183                         tmp->cleanChanges();
184                 }
185         }
186 }
187
188
189 void breakParagraphConservative(BufferParams const & bparams,
190                                 ParagraphList & paragraphs,
191                                 ParagraphList::iterator par,
192                                 pos_type pos)
193 {
194         // create a new paragraph
195         ParagraphList::iterator tmp = paragraphs.insert(boost::next(par),
196                                                         Paragraph());
197         tmp->makeSameLayout(*par);
198
199         // When can pos > size()?
200         // I guess pos == size() is possible.
201         if (par->size() > pos) {
202                 // copy everything behind the break-position to the new
203                 // paragraph
204                 pos_type pos_end = par->size() - 1;
205
206                 for (pos_type i = pos, j = pos; i <= pos_end; ++i)
207                         if (moveItem(*par, *tmp, bparams, i, j - pos))
208                                 ++j;
209
210                 for (pos_type k = pos_end; k >= pos; --k)
211                         par->erase(k);
212         }
213 }
214
215
216 void mergeParagraph(BufferParams const & bparams,
217                     ParagraphList & paragraphs,
218                     ParagraphList::iterator par)
219 {
220         ParagraphList::iterator the_next = boost::next(par);
221
222         pos_type pos_end = the_next->size() - 1;
223         pos_type pos_insert = par->size();
224
225         // ok, now copy the paragraph
226         for (pos_type i = 0, j = 0; i <= pos_end; ++i)
227                 if (moveItem(*the_next, *par, bparams, i, pos_insert + j))
228                         ++j;
229
230         paragraphs.erase(the_next);
231 }
232
233
234 ParagraphList::iterator depthHook(ParagraphList::iterator pit,
235                                   ParagraphList const & plist,
236                                   Paragraph::depth_type depth)
237 {
238         ParagraphList::iterator newpit = pit;
239         ParagraphList::iterator beg = const_cast<ParagraphList&>(plist).begin();
240
241         if (newpit != beg)
242                 --newpit;
243
244         while (newpit != beg && newpit->getDepth() > depth) {
245                 --newpit;
246         }
247
248         if (newpit->getDepth() > depth)
249                 return pit;
250
251         return newpit;
252 }
253
254
255 ParagraphList::iterator outerHook(ParagraphList::iterator pit,
256                                   ParagraphList const & plist)
257 {
258         if (!pit->getDepth())
259                 return const_cast<ParagraphList&>(plist).end();
260         return depthHook(pit, plist,
261                          Paragraph::depth_type(pit->getDepth() - 1));
262 }
263
264
265 bool isFirstInSequence(ParagraphList::iterator pit,
266                        ParagraphList const & plist)
267 {
268         ParagraphList::iterator dhook = depthHook(pit, plist, pit->getDepth());
269         return (dhook == pit
270                 || dhook->layout() != pit->layout()
271                 || dhook->getDepth() != pit->getDepth());
272 }
273
274
275 int getEndLabel(ParagraphList::iterator p, ParagraphList const & plist)
276 {
277         ParagraphList::iterator pit = p;
278         Paragraph::depth_type par_depth = p->getDepth();
279         while (pit != const_cast<ParagraphList&>(plist).end()) {
280                 LyXLayout_ptr const & layout = pit->layout();
281                 int const endlabeltype = layout->endlabeltype;
282
283                 if (endlabeltype != END_LABEL_NO_LABEL) {
284                         if (boost::next(p) == const_cast<ParagraphList&>(plist).end())
285                                 return endlabeltype;
286
287                         Paragraph::depth_type const next_depth = boost::next(p)->getDepth();
288                         if (par_depth > next_depth ||
289                             (par_depth == next_depth &&
290                              layout != boost::next(p)->layout()))
291                                 return endlabeltype;
292                         break;
293                 }
294                 if (par_depth == 0)
295                         break;
296                 pit = outerHook(pit, plist);
297                 if (pit != const_cast<ParagraphList&>(plist).end())
298                         par_depth = pit->getDepth();
299         }
300         return END_LABEL_NO_LABEL;
301 }
302
303
304 namespace {
305
306 int readParToken(Buffer & buf, Paragraph & par, LyXLex & lex, string const & token)
307 {
308         static LyXFont font;
309         static Change change;
310
311         BufferParams const & bp = buf.params();
312
313         if (token[0] != '\\') {
314                 string::const_iterator cit = token.begin();
315                 for (; cit != token.end(); ++cit) {
316                         par.insertChar(par.size(), (*cit), font, change);
317                 }
318         } else if (token == "\\begin_layout") {
319                 lex.eatLine();
320                 string layoutname = lex.getString();
321
322                 font = LyXFont(LyXFont::ALL_INHERIT, bp.language);
323                 change = Change();
324
325                 LyXTextClass const & tclass = bp.getLyXTextClass();
326
327                 if (layoutname.empty()) {
328                         layoutname = tclass.defaultLayoutName();
329                 }
330
331                 bool hasLayout = tclass.hasLayout(layoutname);
332
333                 if (!hasLayout) {
334                         lyxerr << "Layout '" << layoutname << "' does not"
335                                << " exist in textclass '" << tclass.name()
336                                << "'." << endl;
337                         lyxerr << "Trying to use default layout instead."
338                                << endl;
339                         layoutname = tclass.defaultLayoutName();
340                 }
341
342                 par.layout(bp.getLyXTextClass()[layoutname]);
343
344                 // Test whether the layout is obsolete.
345                 LyXLayout_ptr const & layout = par.layout();
346                 if (!layout->obsoleted_by().empty())
347                         par.layout(bp.getLyXTextClass()[layout->obsoleted_by()]);
348
349                 par.params().read(lex);
350
351         } else if (token == "\\end_layout") {
352                 lyxerr << "Solitary \\end_layout in line " << lex.getLineNo() << "\n"
353                        << "Missing \\begin_layout?.\n";
354         } else if (token == "\\end_inset") {
355                 lyxerr << "Solitary \\end_inset in line " << lex.getLineNo() << "\n"
356                        << "Missing \\begin_inset?.\n";
357         } else if (token == "\\begin_inset") {
358                 InsetOld * inset = readInset(lex, buf);
359                 if (inset)
360                         par.insertInset(par.size(), inset, font, change);
361                 else {
362                         lex.eatLine();
363                         string line = lex.getString();
364                         buf.error(ErrorItem(_("Unknown Inset"), line,
365                                             par.id(), 0, par.size()));
366                         return 1;
367                 }
368         } else if (token == "\\family") {
369                 lex.next();
370                 font.setLyXFamily(lex.getString());
371         } else if (token == "\\series") {
372                 lex.next();
373                 font.setLyXSeries(lex.getString());
374         } else if (token == "\\shape") {
375                 lex.next();
376                 font.setLyXShape(lex.getString());
377         } else if (token == "\\size") {
378                 lex.next();
379                 font.setLyXSize(lex.getString());
380         } else if (token == "\\lang") {
381                 lex.next();
382                 string const tok = lex.getString();
383                 Language const * lang = languages.getLanguage(tok);
384                 if (lang) {
385                         font.setLanguage(lang);
386                 } else {
387                         font.setLanguage(bp.language);
388                         lex.printError("Unknown language `$$Token'");
389                 }
390         } else if (token == "\\numeric") {
391                 lex.next();
392                 font.setNumber(font.setLyXMisc(lex.getString()));
393         } else if (token == "\\emph") {
394                 lex.next();
395                 font.setEmph(font.setLyXMisc(lex.getString()));
396         } else if (token == "\\bar") {
397                 lex.next();
398                 string const tok = lex.getString();
399
400                 if (tok == "under")
401                         font.setUnderbar(LyXFont::ON);
402                 else if (tok == "no")
403                         font.setUnderbar(LyXFont::OFF);
404                 else if (tok == "default")
405                         font.setUnderbar(LyXFont::INHERIT);
406                 else
407                         lex.printError("Unknown bar font flag "
408                                        "`$$Token'");
409         } else if (token == "\\noun") {
410                 lex.next();
411                 font.setNoun(font.setLyXMisc(lex.getString()));
412         } else if (token == "\\color") {
413                 lex.next();
414                 font.setLyXColor(lex.getString());
415         } else if (token == "\\InsetSpace" || token == "\\SpecialChar") {
416
417                 // Insets don't make sense in a free-spacing context! ---Kayvan
418                 if (par.isFreeSpacing()) {
419                         if (token == "\\InsetSpace")
420                                 par.insertChar(par.size(), ' ', font, change);
421                         else if (lex.isOK()) {
422                                 lex.next();
423                                 string const next_token = lex.getString();
424                                 if (next_token == "\\-")
425                                         par.insertChar(par.size(), '-', font, change);
426                                 else {
427                                         lex.printError("Token `$$Token' "
428                                                        "is in free space "
429                                                        "paragraph layout!");
430                                 }
431                         }
432                 } else {
433                         auto_ptr<InsetOld> inset;
434                         if (token == "\\SpecialChar" )
435                                 inset.reset(new InsetSpecialChar);
436                         else
437                                 inset.reset(new InsetSpace);
438                         inset->read(buf, lex);
439                         par.insertInset(par.size(), inset.release(),
440                                         font, change);
441                 }
442         } else if (token == "\\i") {
443                 auto_ptr<InsetOld> inset(new InsetLatexAccent);
444                 inset->read(buf, lex);
445                 par.insertInset(par.size(), inset.release(), font, change);
446         } else if (token == "\\backslash") {
447                 par.insertChar(par.size(), '\\', font, change);
448         } else if (token == "\\newline") {
449                 auto_ptr<InsetOld> inset(new InsetNewline);
450                 inset->read(buf, lex);
451                 par.insertInset(par.size(), inset.release(), font, change);
452         } else if (token == "\\LyXTable") {
453                 auto_ptr<InsetOld> inset(new InsetTabular(buf));
454                 inset->read(buf, lex);
455                 par.insertInset(par.size(), inset.release(), font, change);
456         } else if (token == "\\bibitem") {
457                 InsetCommandParams p("bibitem", "dummy");
458                 auto_ptr<InsetBibitem> inset(new InsetBibitem(p));
459                 inset->read(buf, lex);
460                 par.insertInset(par.size(), inset.release(), font, change);
461         } else if (token == "\\hfill") {
462                 par.insertInset(par.size(), new InsetHFill, font, change);
463         } else if (token == "\\lyxline") {
464                 par.insertInset(par.size(), new InsetLine, font, change);
465         } else if (token == "\\newpage") {
466                 par.insertInset(par.size(), new InsetPagebreak, font, change);
467         } else if (token == "\\change_unchanged") {
468                 // Hack ! Needed for empty paragraphs :/
469                 // FIXME: is it still ??
470                 if (!par.size())
471                         par.cleanChanges();
472                 change = Change(Change::UNCHANGED);
473         } else if (token == "\\change_inserted") {
474                 lex.nextToken();
475                 istringstream is(lex.getString());
476                 int aid;
477                 lyx::time_type ct;
478                 is >> aid >> ct;
479                 change = Change(Change::INSERTED, bp.author_map[aid], ct);
480         } else if (token == "\\change_deleted") {
481                 lex.nextToken();
482                 istringstream is(lex.getString());
483                 int aid;
484                 lyx::time_type ct;
485                 is >> aid >> ct;
486                 change = Change(Change::DELETED, bp.author_map[aid], ct);
487         } else {
488                 lex.eatLine();
489                 string const s = bformat(_("Unknown token: %1$s %2$s\n"),
490                         token, lex.getString());
491
492                 buf.error(ErrorItem(_("Unknown token"), s,
493                                     par.id(), 0, par.size()));
494                 return 1;
495         }
496         return 0;
497 }
498
499 }
500
501
502 int readParagraph(Buffer & buf, Paragraph & par, LyXLex & lex)
503 {
504         int unknown = 0;
505
506         lex.nextToken();
507         string token = lex.getString();
508
509         while (lex.isOK()) {
510
511                 unknown += readParToken(buf, par, lex, token);
512
513                 lex.nextToken();
514                 token = lex.getString();
515
516                 if (token.empty())
517                         continue;
518
519                 if (token == "\\end_layout") {
520                         //Ok, paragraph finished
521                         break;
522                 }
523
524                 lyxerr[Debug::PARSER] << "Handling paragraph token: `"
525                                       << token << '\'' << endl;
526                 if (token == "\\begin_layout" || token == "\\end_document"
527                     || token == "\\end_inset" || token == "\\begin_deeper"
528                     || token == "\\end_deeper") {
529                         lex.pushToken(token);
530                         lyxerr << "Paragraph ended in line "
531                                << lex.getLineNo() << "\n"
532                                << "Missing \\end_layout.\n";
533                         break;
534                 }
535         }
536
537         return unknown;
538 }
539
540
541 LyXFont const outerFont(ParagraphList::iterator pit,
542                         ParagraphList const & plist)
543 {
544         Paragraph::depth_type par_depth = pit->getDepth();
545         LyXFont tmpfont(LyXFont::ALL_INHERIT);
546
547         // Resolve against environment font information
548         while (pit != const_cast<ParagraphList&>(plist).end() &&
549                par_depth && !tmpfont.resolved()) {
550                 pit = outerHook(pit, plist);
551                 if (pit != const_cast<ParagraphList&>(plist).end()) {
552                         tmpfont.realize(pit->layout()->font);
553                         par_depth = pit->getDepth();
554                 }
555         }
556
557         return tmpfont;
558 }
559
560
561 ParagraphList::iterator outerPar(Buffer const & buf, InsetOld const * inset)
562 {
563         ParIterator pit = const_cast<Buffer &>(buf).par_iterator_begin();
564         ParIterator end = const_cast<Buffer &>(buf).par_iterator_end();
565         for ( ; pit != end; ++pit) {
566
567                 ParagraphList * plist;
568                 // the second '=' below is intentional
569                 for (int i = 0; (plist = inset->getParagraphs(i)); ++i)
570                         if (plist == &pit.plist())
571                                 return pit.outerPar();
572
573                 InsetList::iterator ii = pit->insetlist.begin();
574                 InsetList::iterator iend = pit->insetlist.end();
575                 for ( ; ii != iend; ++ii)
576                         if (ii->inset == inset)
577                                 return pit.outerPar();
578         }
579         lyxerr << "outerPar: should not happen" << endl;
580         BOOST_ASSERT(false);
581         return const_cast<Buffer &>(buf).paragraphs().end(); // shut up compiler
582 }
583
584
585 Paragraph const & ownerPar(Buffer const & buf, InsetOld const * inset)
586 {
587         ParConstIterator pit = buf.par_iterator_begin();
588         ParConstIterator end = buf.par_iterator_end();
589         for ( ; pit != end; ++pit) {
590                 ParagraphList * plist;
591                 // the second '=' below is intentional
592                 for (int i = 0; (plist = inset->getParagraphs(i)); ++i)
593                         if (plist == &pit.plist())
594                                 return *pit.pit();
595
596                 InsetList::const_iterator ii = pit->insetlist.begin();
597                 InsetList::const_iterator iend = pit->insetlist.end();
598                 for ( ; ii != iend; ++ii)
599                         if (ii->inset == inset)
600                                 return *pit.pit();
601         }
602         lyxerr << "ownerPar: should not happen" << endl;
603         BOOST_ASSERT(false);
604         return buf.paragraphs().front(); // shut up compiler
605 }
606
607
608 void getParsInRange(ParagraphList & pl,
609                     int ystart, int yend,
610                     ParagraphList::iterator & beg,
611                     ParagraphList::iterator & end)
612 {
613         ParagraphList::iterator const endpar = pl.end();
614         ParagraphList::iterator const begpar = pl.begin();
615
616         BOOST_ASSERT(begpar != endpar);
617
618         beg = endpar;
619         for (--beg; beg != begpar && beg->y > ystart; --beg)
620                 ;
621
622         for (end = beg ; end != endpar && end->y <= yend; ++end)
623                 ;
624 }