]> git.lyx.org Git - lyx.git/blob - src/insets/InsetBox.cpp
Do not paint change mark for individual table cell insets (#12077)
[lyx.git] / src / insets / InsetBox.cpp
1 /**
2  * \file InsetBox.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Martin Vermeer
8  * \author Jürgen Spitzmüller
9  * \author Uwe Stöhr
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "InsetBox.h"
17
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "ColorSet.h"
22 #include "Cursor.h"
23 #include "DispatchResult.h"
24 #include "FuncStatus.h"
25 #include "FuncRequest.h"
26 #include "LaTeXFeatures.h"
27 #include "Lexer.h"
28 #include "MetricsInfo.h"
29 #include "output_docbook.h"
30 #include "output_xhtml.h"
31 #include "TexRow.h"
32 #include "texstream.h"
33 #include "TextClass.h"
34
35 #include "support/debug.h"
36 #include "support/docstream.h"
37 #include "support/gettext.h"
38 #include "support/lstrings.h"
39 #include "support/Translator.h"
40
41 #include "frontends/Application.h"
42
43 #include <sstream>
44
45 using namespace std;
46 using namespace lyx::support;
47
48 namespace lyx {
49
50 namespace {
51
52 typedef Translator<string, InsetBox::BoxType> BoxTranslator;
53 typedef Translator<docstring, InsetBox::BoxType> BoxTranslatorLoc;
54
55 BoxTranslator initBoxtranslator()
56 {
57         BoxTranslator translator("Boxed", InsetBox::Boxed);
58         translator.addPair("Frameless", InsetBox::Frameless);
59         translator.addPair("Framed", InsetBox::Framed);
60         translator.addPair("ovalbox", InsetBox::ovalbox);
61         translator.addPair("Ovalbox", InsetBox::Ovalbox);
62         translator.addPair("Shadowbox", InsetBox::Shadowbox);
63         translator.addPair("Shaded", InsetBox::Shaded);
64         translator.addPair("Doublebox",InsetBox::Doublebox);
65         return translator;
66 }
67
68
69 BoxTranslatorLoc initBoxtranslatorLoc()
70 {
71         BoxTranslatorLoc translator(_("simple frame"), InsetBox::Boxed);
72         translator.addPair(_("frameless"), InsetBox::Frameless);
73         translator.addPair(_("simple frame, page breaks"), InsetBox::Framed);
74         translator.addPair(_("oval, thin"), InsetBox::ovalbox);
75         translator.addPair(_("oval, thick"), InsetBox::Ovalbox);
76         translator.addPair(_("drop shadow"), InsetBox::Shadowbox);
77         translator.addPair(_("shaded background"), InsetBox::Shaded);
78         translator.addPair(_("double frame"), InsetBox::Doublebox);
79         return translator;
80 }
81
82
83 BoxTranslator const & boxtranslator()
84 {
85         static BoxTranslator const translator = initBoxtranslator();
86         return translator;
87 }
88
89
90 BoxTranslatorLoc const & boxtranslator_loc()
91 {
92         static BoxTranslatorLoc const translator = initBoxtranslatorLoc();
93         return translator;
94 }
95
96 } // namespace
97
98
99 /////////////////////////////////////////////////////////////////////////
100 //
101 // InsetBox
102 //
103 /////////////////////////////////////////////////////////////////////////
104
105 InsetBox::InsetBox(Buffer * buffer, string const & label)
106         : InsetCollapsible(buffer), params_(label)
107 {}
108
109
110 docstring InsetBox::layoutName() const
111 {
112         // FIXME: UNICODE
113         return from_ascii("Box:" + params_.type);
114 }
115
116
117 void InsetBox::write(ostream & os) const
118 {
119         params_.write(os);
120         InsetCollapsible::write(os);
121 }
122
123
124 void InsetBox::read(Lexer & lex)
125 {
126         params_.read(lex);
127         InsetCollapsible::read(lex);
128 }
129
130
131 void InsetBox::setButtonLabel()
132 {
133         BoxType const btype = boxtranslator().find(params_.type);
134
135         docstring const type = _("Box");
136
137         docstring inner;
138         if (params_.inner_box) {
139                 if (params_.use_parbox)
140                         inner = _("Parbox");
141                 else if (params_.use_makebox)
142                         inner = _("Makebox");
143                 else
144                         inner = _("Minipage");
145         }
146
147         docstring frame;
148         if (btype != Frameless)
149                 frame = boxtranslator_loc().find(btype);
150
151         docstring label;
152         if (inner.empty() && frame.empty())
153                 label = type;
154         else if (inner.empty())
155                 label = bformat(_("%1$s (%2$s)"),
156                         type, frame);
157         else if (frame.empty())
158                 label = bformat(_("%1$s (%2$s)"),
159                         type, inner);
160         else
161                 label = bformat(_("%1$s (%2$s, %3$s)"),
162                         type, inner, frame);
163         setLabel(label);
164
165         // set the frame color for the inset if the type is Boxed
166         if (btype == Boxed)
167                 setFrameColor(lcolor.getFromLaTeXName(params_.framecolor));
168         else
169                 setFrameColor(Color_collapsibleframe);
170 }
171
172
173 bool InsetBox::hasFixedWidth() const
174 {
175         return !params_.width.empty() && params_.special == "none";
176 }
177
178
179 bool InsetBox::allowMultiPar() const
180 {
181         return (params_.inner_box && !params_.use_makebox)
182                 || params_.type == "Shaded" || params_.type == "Framed";
183 }
184
185
186 void InsetBox::metrics(MetricsInfo & mi, Dimension & dim) const
187 {
188         // back up textwidth.
189         int textwidth_backup = mi.base.textwidth;
190         if (hasFixedWidth())
191                 mi.base.textwidth = mi.base.inPixels(params_.width);
192         InsetCollapsible::metrics(mi, dim);
193         // restore textwidth.
194         mi.base.textwidth = textwidth_backup;
195 }
196
197
198 bool InsetBox::forcePlainLayout(idx_type) const
199 {
200         return (!params_.inner_box || params_.use_makebox)
201                 && params_.type != "Shaded" && params_.type != "Framed";
202 }
203
204
205 bool InsetBox::needsCProtection(bool const maintext, bool const fragile) const
206 {
207         // We need to cprotect boxes that use minipages as inner box
208         // in fragile context
209         if (fragile && params_.inner_box && !params_.use_parbox && !params_.use_makebox)
210                 return true;
211
212         return InsetText::needsCProtection(maintext, fragile);
213 }
214
215
216 ColorCode InsetBox::backgroundColor(PainterInfo const &) const
217 {
218         // we only support background color for 3 types
219         if (params_.type != "Shaded" && params_.type != "Frameless" && params_.type != "Boxed")
220                 return getLayout().bgcolor();
221
222         if (params_.type == "Shaded") {
223                 if (buffer().params().isboxbgcolor)
224                         return getLayout().bgcolor();
225
226                 ColorCode c = lcolor.getFromLyXName("boxbgcolor");
227                 if (c == Color_none)
228                         return getLayout().bgcolor();
229                 return c;
230         }
231
232         if (params_.backgroundcolor != "none")
233                 return lcolor.getFromLaTeXName(params_.backgroundcolor);
234
235         return getLayout().bgcolor();
236 }
237
238
239 LyXAlignment InsetBox::contentAlignment() const
240 {
241         // Custom horizontal alignment is only allowed with a fixed width
242         // and if either makebox or no inner box are used
243         if (params_.width.empty() || !(params_.use_makebox || !params_.inner_box))
244                 return LYX_ALIGN_NONE;
245
246         // The default value below is actually irrelevant
247         LyXAlignment align = LYX_ALIGN_NONE;
248         switch (params_.hor_pos) {
249         case 'l':
250                 align = LYX_ALIGN_LEFT;
251                 break;
252         case 'c':
253                 align = LYX_ALIGN_CENTER;
254                 break;
255         case 'r':
256                 align = LYX_ALIGN_RIGHT;
257                 break;
258         case 's':
259                 align = LYX_ALIGN_BLOCK;
260                 break;
261         }
262         return align;
263 }
264
265
266 void InsetBox::doDispatch(Cursor & cur, FuncRequest & cmd)
267 {
268         switch (cmd.action()) {
269
270         case LFUN_INSET_MODIFY: {
271                 //lyxerr << "InsetBox::dispatch MODIFY" << endl;
272                 string const first_arg = cmd.getArg(0);
273                 bool const change_type = first_arg == "changetype";
274                 bool const for_box = first_arg == "box";
275                 if (!change_type && !for_box) {
276                         // not for us
277                         // this will not be handled higher up
278                         cur.undispatched();
279                         return;
280                 }
281                 cur.recordUndoInset(this);
282                 if (change_type) {
283                         params_.type = cmd.getArg(1);
284                         // set a makebox if there is no inner box but Frameless was executed
285                         // otherwise the result would be a non existent box (no inner AND outer box)
286                         // (this was LyX bug 8712)
287                         if (params_.type == "Frameless" && !params_.inner_box) {
288                                 params_.use_makebox = true;
289                                 params_.inner_box = true;
290                         }
291                         // handle the opposite case
292                         if (params_.type == "Boxed" && params_.use_makebox) {
293                                 params_.use_makebox = false;
294                                 params_.inner_box = false;
295                         }
296                 } else
297                         string2params(to_utf8(cmd.argument()), params_);
298                 setButtonLabel();
299                 break;
300         }
301
302         default:
303                 InsetCollapsible::doDispatch(cur, cmd);
304                 break;
305         }
306 }
307
308
309 bool InsetBox::getStatus(Cursor & cur, FuncRequest const & cmd,
310                 FuncStatus & flag) const
311 {
312         switch (cmd.action()) {
313
314         case LFUN_INSET_MODIFY: {
315                 string const first_arg = cmd.getArg(0);
316                 if (first_arg == "changetype") {
317                         string const type = cmd.getArg(1);
318                         flag.setOnOff(type == params_.type);
319                         flag.setEnabled(!params_.inner_box || type != "Framed");
320                         return true;
321                 }
322                 if (first_arg == "box") {
323                         flag.setEnabled(true);
324                         return true;
325                 }
326                 return InsetCollapsible::getStatus(cur, cmd, flag);
327         }
328
329         case LFUN_INSET_DIALOG_UPDATE:
330                 flag.setEnabled(true);
331                 return true;
332
333         default:
334                 return InsetCollapsible::getStatus(cur, cmd, flag);
335         }
336 }
337
338
339 const string defaultThick = "0.4pt";
340 const string defaultSep = "3pt";
341 const string defaultShadow = "4pt";
342
343 void InsetBox::latex(otexstream & os, OutputParams const & runparams) const
344 {
345         BoxType btype = boxtranslator().find(params_.type);
346
347         string width_string = params_.width.asLatexString();
348         string thickness_string = params_.thickness.asLatexString();
349         string separation_string = params_.separation.asLatexString();
350         string shadowsize_string = params_.shadowsize.asLatexString();
351         bool stdwidth = false;
352         string const cprotect = hasCProtectContent(runparams.moving_arg) ? "\\cprotect" : string();
353         // Colored boxes in RTL need to be wrapped into \beginL...\endL
354         string maybeBeginL;
355         string maybeEndL;
356         bool needEndL = false;
357         if (!runparams.isFullUnicode() && runparams.local_font->isRightToLeft()) {
358                 maybeBeginL = "\\beginL";
359                 maybeEndL = "\\endL";
360         }
361         // in general the overall width of some decorated boxes is wider thean the inner box
362         // we could therefore calculate the real width for all sizes so that if the user wants
363         // e.g. 0.1\columnwidth or 2cm he gets exactly this size
364         // however this makes problems when importing TeX code
365         // therefore only recalculate for the most common case that the box should not protrude
366         // the page margins
367         if (params_.inner_box
368                 && ((width_string.find("1\\columnwidth") != string::npos
369                         || width_string.find("1\\textwidth") != string::npos)
370                         || width_string.find("1\\paperwidth") != string::npos
371                         || width_string.find("1\\linewidth") != string::npos)) {
372                 stdwidth = true;
373                 switch (btype) {
374                 case Frameless:
375                         break;
376                 case Framed:
377                         width_string += " - 2\\FrameSep - 2\\FrameRule";
378                         break;
379                 case Boxed:
380                         width_string += " - 2\\fboxsep - 2\\fboxrule";
381                         break;
382                 case Shaded:
383                         break;
384                 case ovalbox:
385                         width_string += " - 2\\fboxsep - 0.8pt";
386                         break;
387                 case Ovalbox:
388                         width_string += " - 2\\fboxsep - 1.6pt";
389                         break;
390                 case Shadowbox:
391                         width_string += " - 2\\fboxsep - 2\\fboxrule - \\shadowsize";
392                         break;
393                 case Doublebox:
394                         width_string += " - 2\\fboxsep - 7.5\\fboxrule - 1pt";
395                         break;
396                 }
397         }
398
399         os << safebreakln;
400         if (runparams.lastid != -1)
401                 os.texrow().start(runparams.lastid, runparams.lastpos);
402
403         // adapt column/text width correctly also if paragraphs indented
404         if (stdwidth && !(buffer().params().paragraph_separation))
405                 os << "\\noindent";
406
407         bool needendgroup = false;
408         switch (btype) {
409         case Frameless:
410                 break;
411         case Framed:
412                 if (thickness_string != defaultThick) {
413                         os << "{\\FrameRule " << from_ascii(thickness_string);
414                         if (separation_string != defaultSep)
415                                 os << "\\FrameSep " << from_ascii(separation_string);
416                 }
417                 if (separation_string != defaultSep && thickness_string == defaultThick)
418                         os << "{\\FrameSep " << from_ascii(separation_string);
419
420                 os << "\\begin{framed}%\n";
421                 break;
422         case Boxed:
423                 if (thickness_string != defaultThick) {
424                         os << "{\\fboxrule " << from_ascii(thickness_string);
425                         if (separation_string != defaultSep)
426                                 os << "\\fboxsep " << from_ascii(separation_string);
427                 }
428                 if (separation_string != defaultSep && thickness_string == defaultThick)
429                         os << "{\\fboxsep " << from_ascii(separation_string);
430                 if (!params_.inner_box && !width_string.empty()) {
431                         if (params_.framecolor != "black" || params_.backgroundcolor != "none") {
432                                 os << maybeBeginL << "\\fcolorbox{" << params_.framecolor << "}{" << params_.backgroundcolor << "}{";
433                                 os << "\\makebox";
434                                 needEndL = !maybeBeginL.empty();
435                         } else
436                                 os << "\\framebox";
437                         // Special widths, see usrguide sec. 3.5
438                         // FIXME UNICODE
439                         if (params_.special != "none") {
440                                 os << "[" << params_.width.value()
441                                    << '\\' << from_utf8(params_.special)
442                                    << ']';
443                         } else
444                                 os << '[' << from_ascii(width_string)
445                                    << ']';
446                         // default horizontal alignment is 'c'
447                         if (params_.hor_pos != 'c')
448                                 os << "[" << params_.hor_pos << "]";
449                 } else {
450                         if (params_.framecolor != "black" || params_.backgroundcolor != "none") {
451                                 os << maybeBeginL << "\\fcolorbox{" << params_.framecolor << "}{" << params_.backgroundcolor << "}";
452                                 needEndL = !maybeBeginL.empty();
453                         } else {
454                                 if (!cprotect.empty() && contains(runparams.active_chars, '^')) {
455                                         // cprotect relies on ^ being on catcode 7
456                                         os << "\\begingroup\\catcode`\\^=7";
457                                         needendgroup = true;
458                                 }
459                                 os << cprotect << "\\fbox";
460                         }
461                 }
462                 os << "{";
463                 break;
464         case ovalbox:
465                 if (!separation_string.empty() && separation_string != defaultSep)
466                         os << "{\\fboxsep " << from_ascii(separation_string);
467                 os << "\\ovalbox{";
468                 break;
469         case Ovalbox:
470                 if (!separation_string.empty() && separation_string != defaultSep)
471                         os << "{\\fboxsep " << from_ascii(separation_string);
472                 os << "\\Ovalbox{";
473                 break;
474         case Shadowbox:
475                 if (thickness_string != defaultThick) {
476                         os << "{\\fboxrule " << from_ascii(thickness_string);
477                         if (separation_string != defaultSep) {
478                                 os << "\\fboxsep " << from_ascii(separation_string);
479                                 if (shadowsize_string != defaultShadow)
480                                         os << "\\shadowsize " << from_ascii(shadowsize_string);
481                         }
482                         if (shadowsize_string != defaultShadow  && separation_string == defaultSep)
483                                 os << "\\shadowsize " << from_ascii(shadowsize_string);
484                 }
485                 if (separation_string != defaultSep && thickness_string == defaultThick) {
486                                 os << "{\\fboxsep " << from_ascii(separation_string);
487                                 if (shadowsize_string != defaultShadow)
488                                         os << "\\shadowsize " << from_ascii(shadowsize_string);
489                 }
490                 if (shadowsize_string != defaultShadow
491                                 && separation_string == defaultSep
492                                 && thickness_string == defaultThick)
493                                 os << "{\\shadowsize " << from_ascii(shadowsize_string);
494                 os << "\\shadowbox{";
495                 break;
496         case Shaded:
497                 // must be set later because e.g. the width settings only work when
498                 // it is inside a minipage or parbox
499                 os << maybeBeginL;
500                 needEndL = !maybeBeginL.empty();
501                 break;
502         case Doublebox:
503                 if (thickness_string != defaultThick) {
504                         os << "{\\fboxrule " << from_ascii(thickness_string);
505                         if (separation_string != defaultSep)
506                                 os << "\\fboxsep " << from_ascii(separation_string);
507                 }
508                 if (separation_string != defaultSep && thickness_string == defaultThick)
509                         os << "{\\fboxsep " << from_ascii(separation_string);
510                 os << "\\doublebox{";
511                 break;
512         }
513
514         if (params_.inner_box) {
515                 if (params_.use_parbox) {
516                         if (params_.backgroundcolor != "none" && btype == Frameless) {
517                                 os << maybeBeginL << "\\colorbox{" << params_.backgroundcolor << "}{";
518                                 needEndL = !maybeBeginL.empty();
519                         }
520                         os << "\\parbox";
521                 } else if (params_.use_makebox) {
522                         if (!width_string.empty()) {
523                                 if (params_.backgroundcolor != "none") {
524                                         os << maybeBeginL << "\\colorbox{" << params_.backgroundcolor << "}{";
525                                         needEndL = !maybeBeginL.empty();
526                                 }
527                                 os << "\\makebox";
528                                 // FIXME UNICODE
529                                 // output the width and horizontal position
530                                 if (params_.special != "none") {
531                                         os << "[" << params_.width.value()
532                                            << '\\' << from_utf8(params_.special)
533                                            << ']';
534                                 } else
535                                         os << '[' << from_ascii(width_string)
536                                            << ']';
537                                 if (params_.hor_pos != 'c')
538                                         os << "[" << params_.hor_pos << "]";
539                         } else {
540                                 if (params_.backgroundcolor != "none") {
541                                         os << maybeBeginL << "\\colorbox{" << params_.backgroundcolor << "}";
542                                         needEndL = !maybeBeginL.empty();
543                                 }
544                                 else
545                                         os << "\\mbox";
546                         }
547                         os << "{";
548                 }
549                 else {
550                         if (params_.backgroundcolor != "none" && btype == Frameless) {
551                                 os << maybeBeginL << "\\colorbox{" << params_.backgroundcolor << "}{";
552                                 needEndL = !maybeBeginL.empty();
553                         }
554                         os << "\\begin{minipage}";
555                 }
556
557                 // output parameters for parbox and minipage
558                 if (!params_.use_makebox) {
559                         os << "[" << params_.pos << "]";
560                         if (params_.height_special == "none") {
561                                 // FIXME UNICODE
562                                 os << "[" << from_ascii(params_.height.asLatexString()) << "]";
563                         } else {
564                                 // Special heights
565                                 // set no optional argument when the value is the default "1\height"
566                                 // (special units like \height are handled as "in")
567                                 // but when the user has chosen a non-default inner_pos, the height
568                                 // must be given: \minipage[pos][height][inner-pos]{width}
569                                 if ((params_.height != Length("1in") ||
570                                         params_.height_special != "totalheight") ||
571                                         params_.inner_pos != params_.pos) {
572                                                 // FIXME UNICODE
573                                                 os << "[" << params_.height.value()
574                                                         << "\\" << from_utf8(params_.height_special) << "]";
575                                 }
576                         }
577                         if (params_.inner_pos != params_.pos)
578                                 os << "[" << params_.inner_pos << "]";
579                         // FIXME UNICODE
580                         os << '{' << from_ascii(width_string) << '}';
581                         if (params_.use_parbox)
582                                 os << "{";
583                 }
584
585                 os << "%\n";
586         } // end if inner_box
587
588         if (btype == Shaded) {
589                 os << "\\begin{shaded}%\n";
590         }
591
592         InsetText::latex(os, runparams);
593
594         if (btype == Shaded)
595                 os << "\\end{shaded}";
596
597         if (params_.inner_box) {
598                 if (params_.use_parbox || params_.use_makebox)
599                         os << "%\n}";
600                 else
601                         os << "%\n\\end{minipage}";
602                 if (params_.backgroundcolor != "none" && btype == Frameless
603                         && !(params_.use_makebox && width_string.empty()))
604                         os << "}";
605         }
606
607         switch (btype) {
608         case Frameless:
609                 break;
610         case Framed:
611                 os << "\\end{framed}";
612                 if (separation_string != defaultSep || thickness_string != defaultThick)
613                         os << "}";
614                 break;
615         case Boxed:
616                 os << "}";
617                 if (!params_.inner_box && !width_string.empty()
618                         && (params_.framecolor != "black" || params_.backgroundcolor != "none"))
619                         os << "}";
620                 if (separation_string != defaultSep || thickness_string != defaultThick)
621                         os << "}";
622                 if (needendgroup)
623                         os << "\\endgroup";
624                 break;
625         case ovalbox:
626                 os << "}";
627                 if (separation_string != defaultSep)
628                         os << "}";
629                 break;
630         case Ovalbox:
631                 os << "}";
632                 if (separation_string != defaultSep)
633                         os << "}";
634                 break;
635         case Doublebox:
636                 os << "}";
637                 if (separation_string != defaultSep || thickness_string != defaultThick)
638                         os << "}";
639                 break;
640         case Shadowbox:
641                 os << "}";
642                 if (separation_string != defaultSep
643                         || thickness_string != defaultThick
644                         || shadowsize_string != defaultShadow)
645                         os << "}";
646                 break;
647         case Shaded:
648                 // already done
649                 break;
650         }
651         if (needEndL)
652                 os << maybeEndL;
653 }
654
655
656 int InsetBox::plaintext(odocstringstream & os,
657        OutputParams const & runparams, size_t max_length) const
658 {
659         BoxType const btype = boxtranslator().find(params_.type);
660
661         switch (btype) {
662                 case Frameless:
663                         break;
664                 case Framed:
665                 case Boxed:
666                         os << "[\n";
667                         break;
668                 case ovalbox:
669                         os << "(\n";
670                         break;
671                 case Ovalbox:
672                         os << "((\n";
673                         break;
674                 case Shadowbox:
675                 case Shaded:
676                         os << "[/\n";
677                         break;
678                 case Doublebox:
679                         os << "[[\n";
680                         break;
681         }
682
683         InsetText::plaintext(os, runparams, max_length);
684
685         int len = 0;
686         switch (btype) {
687                 case Frameless:
688                         os << "\n";
689                         break;
690                 case Framed:
691                 case Boxed:
692                         os << "\n]";
693                         len = 1;
694                         break;
695                 case ovalbox:
696                         os << "\n)";
697                         len = 1;
698                         break;
699                 case Ovalbox:
700                         os << "\n))";
701                         len = 2;
702                         break;
703                 case Shadowbox:
704                 case Shaded:
705                         os << "\n/]";
706                         len = 2;
707                         break;
708                 case Doublebox:
709                         os << "\n]]";
710                         len = 2;
711                         break;
712         }
713
714         return PLAINTEXT_NEWLINE + len; // len chars on a separate line
715 }
716
717
718 void InsetBox::docbook(XMLStream & xs, OutputParams const & runparams) const
719 {
720         // There really should be a wrapper tag for this layout.
721         bool hasBoxTag = !getLayout().docbookwrappertag().empty();
722         if (!hasBoxTag)
723                 LYXERR0("Assertion failed: box layout " + getLayout().name() + " missing DocBookWrapperTag.");
724
725         // Avoid nesting boxes in DocBook, it's not allowed. Only make the check for <sidebar> to avoid destroying
726         // tags if this is not the wrapper tag for this layout (unlikely).
727         bool isAlreadyInBox = hasBoxTag && xs.isTagOpen(xml::StartTag(getLayout().docbookwrappertag()));
728
729         bool outputBoxTag = hasBoxTag && !isAlreadyInBox;
730
731         // Generate the box tag (typically, <sidebar>).
732         if (outputBoxTag) {
733                 if (!xs.isLastTagCR())
734                         xs << xml::CR();
735
736                 xs << xml::StartTag(getLayout().docbookwrappertag(), getLayout().docbookwrapperattr());
737                 xs << xml::CR();
738         }
739
740         // If the box starts with a sectioning item, use as box title.
741         auto current_par = paragraphs().begin();
742         if (current_par->layout().category() == from_utf8("Sectioning")) {
743                 // Only generate the first paragraph.
744                 current_par = makeAny(text(), buffer(), xs, runparams, paragraphs().begin());
745         }
746
747         // Don't call InsetText::docbook, as this would generate all paragraphs in the inset, not the ones we are
748         // interested in. The best solution would be to call docbookParagraphs with an updated OutputParams object to only
749         // generate paragraphs after the title, but it leads to strange crashes, as if text().paragraphs() then returns
750         // a smaller set of paragrphs.
751         // Elements in the box must keep their paragraphs.
752         auto rp = runparams;
753         rp.docbook_in_par = false;
754         rp.docbook_force_pars = true;
755
756         xs.startDivision(false);
757         while (current_par != paragraphs().end())
758                 current_par = makeAny(text(), buffer(), xs, rp, current_par);
759         xs.endDivision();
760
761         // Close the box.
762         if (outputBoxTag) {
763                 if (!xs.isLastTagCR())
764                         xs << xml::CR();
765
766                 xs << xml::EndTag(getLayout().docbookwrappertag());
767                 xs << xml::CR();
768         }
769 }
770
771
772 docstring InsetBox::xhtml(XMLStream & xs, OutputParams const & runparams) const
773 {
774         // construct attributes
775         string attrs = "class='" + params_.type + "'";
776         string style;
777         if (!params_.width.empty()) {
778                 string w = params_.width.asHTMLString();
779                 if (w != "100%")
780                         style += ("width: " + params_.width.asHTMLString() + "; ");
781         }
782         // The special heights don't really mean anything for us.
783         if (!params_.height.empty() && params_.height_special == "none")
784                 style += ("height: " + params_.height.asHTMLString() + "; ");
785         if (!style.empty())
786                 attrs += " style='" + style + "'";
787
788         xs << xml::StartTag("div", attrs);
789         XHTMLOptions const opts = InsetText::WriteLabel | InsetText::WriteInnerTag;
790         docstring defer = InsetText::insetAsXHTML(xs, runparams, opts);
791         xs << xml::EndTag("div");
792         xs << defer;
793         return docstring();
794 }
795
796
797 void InsetBox::validate(LaTeXFeatures & features) const
798 {
799         BoxType btype = boxtranslator().find(params_.type);
800         switch (btype) {
801         case Frameless:
802                 if (params_.backgroundcolor != "none")
803                         features.require("xcolor");
804                 break;
805         case Framed:
806                 features.require("calc");
807                 features.require("framed");
808                 break;
809         case Boxed:
810                 features.require("calc");
811                 if (params_.framecolor != "black" || params_.backgroundcolor != "none")
812                         features.require("xcolor");
813                 break;
814         case ovalbox:
815         case Ovalbox:
816         case Shadowbox:
817         case Doublebox:
818                 features.require("calc");
819                 features.require("fancybox");
820                 break;
821         case Shaded:
822                 features.require("color");
823                 features.require("framed");
824                 break;
825         }
826         InsetCollapsible::validate(features);
827 }
828
829
830 string InsetBox::contextMenuName() const
831 {
832         return "context-box";
833 }
834
835
836 string InsetBox::params2string(InsetBoxParams const & params)
837 {
838         ostringstream data;
839         data << "box" << ' ';
840         params.write(data);
841         return data.str();
842 }
843
844
845 void InsetBox::string2params(string const & in, InsetBoxParams & params)
846 {
847         if (in.empty())
848                 return;
849
850         istringstream data(in);
851         Lexer lex;
852         lex.setStream(data);
853
854         string name;
855         lex >> name;
856         if (!lex || name != "box") {
857                 LYXERR0("InsetBox::string2params(" << in << ")\n"
858                                           "Expected arg 1 to be \"box\"\n");
859                 return;
860         }
861
862         // This is part of the inset proper that is usually swallowed
863         // by Text::readInset
864         string id;
865         lex >> id;
866         if (!lex || id != "Box") {
867                 LYXERR0("InsetBox::string2params(" << in << ")\n"
868                                           "Expected arg 2 to be \"Box\"\n");
869         }
870
871         params = InsetBoxParams(string());
872         params.read(lex);
873 }
874
875
876 /////////////////////////////////////////////////////////////////////////
877 //
878 // InsetBoxParams
879 //
880 /////////////////////////////////////////////////////////////////////////
881
882 InsetBoxParams::InsetBoxParams(string const & label)
883         : type(label),
884           use_parbox(false),
885           use_makebox(false),
886           inner_box(true),
887           width(Length("100col%")),
888           special("none"),
889           pos('t'),
890           hor_pos('c'),
891           inner_pos('t'),
892           height(Length("1in")),
893           height_special("totalheight"), // default is 1\\totalheight
894           thickness(Length(defaultThick)),
895           separation(Length(defaultSep)),
896           shadowsize(Length(defaultShadow)),
897           framecolor("black"),
898           backgroundcolor("none")
899 {}
900
901
902 void InsetBoxParams::write(ostream & os) const
903 {
904         os << "Box " << type << "\n";
905         os << "position \"" << pos << "\"\n";
906         os << "hor_pos \"" << hor_pos << "\"\n";
907         os << "has_inner_box " << inner_box << "\n";
908         os << "inner_pos \"" << inner_pos << "\"\n";
909         os << "use_parbox " << use_parbox << "\n";
910         os << "use_makebox " << use_makebox << "\n";
911         os << "width \"" << width.asString() << "\"\n";
912         os << "special \"" << special << "\"\n";
913         os << "height \"" << height.asString() << "\"\n";
914         os << "height_special \"" << height_special << "\"\n";
915         os << "thickness \"" << thickness.asString() << "\"\n";
916         os << "separation \"" << separation.asString() << "\"\n";
917         os << "shadowsize \"" << shadowsize.asString() << "\"\n";
918         os << "framecolor \"" << framecolor << "\"\n";
919         os << "backgroundcolor \"" << backgroundcolor << "\"\n";
920 }
921
922
923 void InsetBoxParams::read(Lexer & lex)
924 {
925         lex.setContext("InsetBoxParams::read");
926         lex >> type;
927         lex >> "position" >> pos;
928         lex >> "hor_pos" >> hor_pos;
929         lex >> "has_inner_box" >> inner_box;
930         if (type == "Framed")
931                 inner_box = false;
932         lex >> "inner_pos" >> inner_pos;
933         lex >> "use_parbox" >> use_parbox;
934         lex >> "use_makebox" >> use_makebox;
935         lex >> "width" >> width;
936         lex >> "special" >> special;
937         lex >> "height" >> height;
938         lex >> "height_special" >> height_special;
939         lex >> "thickness" >> thickness;
940         lex >> "separation" >> separation;
941         lex >> "shadowsize" >> shadowsize;
942         lex >> "framecolor" >> framecolor;
943         lex >> "backgroundcolor" >> backgroundcolor;
944 }
945
946
947 } // namespace lyx