]> git.lyx.org Git - features.git/blob - src/insets/InsetCollapsable.cpp
Move Color::color enum to ColorCode.h
[features.git] / src / insets / InsetCollapsable.cpp
1 /**
2  * \file InsetCollapsable.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author Jürgen Vigna
8  * \author Lars Gullik Bjønnes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetCollapsable.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "BufferView.h"
20 #include "Cursor.h"
21 #include "debug.h"
22 #include "DispatchResult.h"
23 #include "FloatList.h"
24 #include "FuncStatus.h"
25 #include "gettext.h"
26 #include "LaTeXFeatures.h"
27 #include "Lexer.h"
28 #include "FuncRequest.h"
29 #include "MetricsInfo.h"
30
31 #include "frontends/FontMetrics.h"
32 #include "frontends/Painter.h"
33
34
35 namespace lyx {
36
37 using std::endl;
38 using std::max;
39 using std::ostream;
40 using std::string;
41
42
43 InsetCollapsable::CollapseStatus InsetCollapsable::status() const
44 {
45         return autoOpen_ ? Open : status_;
46 }
47
48
49 InsetCollapsable::Geometry InsetCollapsable::geometry() const
50 {
51         switch (decoration()) {
52         case Classic:
53                 if (status() == Open) {
54                         if (openinlined_)
55                                 return LeftButton;
56                         else
57                                 return TopButton;
58                 } else
59                         return ButtonOnly;
60
61         case Minimalistic:
62                 return status() == Open ? NoButton : ButtonOnly ;
63
64         case Conglomerate:
65                 return status() == Open ? SubLabel : Corners ;
66         }
67
68         // dummy return value to shut down a warning,
69         // this is dead code.
70         return NoButton;
71 }
72
73
74 InsetCollapsable::InsetCollapsable
75                 (BufferParams const & bp, CollapseStatus status)
76         : InsetText(bp), status_(status),
77           openinlined_(false), autoOpen_(false), mouse_hover_(false)
78 {
79         setAutoBreakRows(true);
80         setDrawFrame(true);
81         setFrameColor(Color_collapsableframe);
82         setButtonLabel();
83         // Fallback for lacking inset layout item
84         layout_.bgcolor = Color_background;
85 }
86
87
88 InsetCollapsable::InsetCollapsable(InsetCollapsable const & rhs)
89         : InsetText(rhs),
90                 button_dim(rhs.button_dim),
91                 topx(rhs.topx),
92                 topbaseline(rhs.topbaseline),
93                 layout_(rhs.layout_),
94                 status_(rhs.status_),
95                 openinlined_(rhs.openinlined_),
96                 autoOpen_(rhs.autoOpen_),
97                 // the sole purpose of this copy constructor
98                 mouse_hover_(false)
99 {
100 }
101
102
103 void  InsetCollapsable::setLayout(BufferParams const & bp)
104 {
105         layout_ = getLayout(bp);
106 }
107
108
109 void InsetCollapsable::write(Buffer const & buf, ostream & os) const
110 {
111         os << "status ";
112         switch (status_) {
113         case Open:
114                 os << "open";
115                 break;
116         case Collapsed:
117                 os << "collapsed";
118                 break;
119         }
120         os << "\n";
121         text_.write(buf, os);
122 }
123
124
125 void InsetCollapsable::read(Buffer const & buf, Lexer & lex)
126 {
127         bool token_found = false;
128         if (lex.isOK()) {
129                 lex.next();
130                 string const token = lex.getString();
131                 if (token == "status") {
132                         lex.next();
133                         string const tmp_token = lex.getString();
134
135                         if (tmp_token == "collapsed") {
136                                 status_ = Collapsed;
137                                 token_found = true;
138                         } else if (tmp_token == "open") {
139                                 status_ = Open;
140                                 token_found = true;
141                         } else {
142                                 lyxerr << "InsetCollapsable::read: Missing status!"
143                                        << endl;
144                                 // Take countermeasures
145                                 lex.pushToken(token);
146                         }
147                 } else {
148                         lyxerr << "InsetCollapsable::read: Missing 'status'-tag!"
149                                    << endl;
150                         // take countermeasures
151                         lex.pushToken(token);
152                 }
153         }
154         InsetText::read(buf, lex);
155
156         if (!token_found)
157                 status_ = isOpen() ? Open : Collapsed;
158
159         setButtonLabel();
160 }
161
162
163 Dimension InsetCollapsable::dimensionCollapsed() const
164 {
165         Dimension dim;
166         theFontMetrics(layout_.labelfont).buttonText(
167                 layout_.labelstring, dim.wid, dim.asc, dim.des);
168         return dim;
169 }
170
171
172 void InsetCollapsable::metrics(MetricsInfo & mi, Dimension & dim) const
173 {
174         autoOpen_ = mi.base.bv->cursor().isInside(this);
175
176         switch (geometry()) {
177         case NoButton:
178                 InsetText::metrics(mi, dim);
179                 break;
180         case Corners:
181                 InsetText::metrics(mi, dim);
182                 dim.des -= 3;
183                 dim.asc -= 1;
184                 break;
185         case SubLabel: {
186                 InsetText::metrics(mi, dim);
187                 // consider width of the inset label
188                 Font font(layout_.labelfont);
189                 font.realize(Font(Font::ALL_SANE));
190                 font.decSize();
191                 font.decSize();
192                 int w = 0;
193                 int a = 0;
194                 int d = 0;
195                 docstring s = layout_.labelstring;
196                 theFontMetrics(font).rectText(s, w, a, d);
197                 dim.des += a + d;
198                 break;
199                 }
200         case TopButton:
201         case LeftButton:
202         case ButtonOnly:
203                 dim = dimensionCollapsed();
204                 if (geometry() == TopButton
205                  || geometry() == LeftButton) {
206                         Dimension textdim;
207                         InsetText::metrics(mi, textdim);
208                         openinlined_ = (textdim.wid + dim.wid) < mi.base.textwidth;
209                         if (openinlined_) {
210                                 // Correct for button width.
211                                 dim.wid += textdim.wid;
212                                 dim.des = max(dim.des - textdim.asc + dim.asc, textdim.des);
213                                 dim.asc = textdim.asc;
214                         } else {
215                                 dim.des += textdim.height() + TEXT_TO_BOTTOM_OFFSET;
216                                 dim.wid = max(dim.wid, textdim.wid);
217                         }
218                 }
219                 break;
220         }
221 }
222
223
224 bool InsetCollapsable::setMouseHover(bool mouse_hover)
225 {
226         mouse_hover_ = mouse_hover;
227         return true;
228 }
229
230
231 void InsetCollapsable::draw(PainterInfo & pi, int x, int y) const
232 {
233         autoOpen_ = pi.base.bv->cursor().isInside(this);
234         int const old_color = pi.background_color;
235         pi.background_color = backgroundColor();
236
237         // Draw button first -- top, left or only
238         Dimension dimc = dimensionCollapsed();
239
240         if (geometry() == TopButton ||
241             geometry() == LeftButton ||
242             geometry() == ButtonOnly) {
243                 button_dim.x1 = x + 0;
244                 button_dim.x2 = x + dimc.width();
245                 button_dim.y1 = y - dimc.asc;
246                 button_dim.y2 = y + dimc.des;
247
248                 pi.pain.buttonText(x, y, layout_.labelstring, layout_.labelfont, mouse_hover_);
249         } else {
250                 button_dim.x1 = 0;
251                 button_dim.y1 = 0;
252                 button_dim.x2 = 0;
253                 button_dim.y2 = 0;
254         }
255
256         Dimension const textdim = InsetText::dimension(*pi.base.bv);
257         int const baseline = y;
258         int textx, texty;
259         switch (geometry()) {
260         case LeftButton:
261                 textx = x + dimc.width();
262                 texty = baseline;
263                 InsetText::draw(pi, textx, texty);
264                 break;
265         case TopButton:
266                 textx = x;
267                 texty = baseline + dimc.des + textdim.asc;
268                 InsetText::draw(pi, textx, texty);
269                 break;
270         case ButtonOnly:
271                 break;
272         case NoButton:
273                 textx = x;
274                 texty = baseline;
275                 InsetText::draw(pi, textx, texty);
276                 break;
277         case SubLabel:
278         case Corners:
279                 textx = x;
280                 texty = baseline;
281                 const_cast<InsetCollapsable *>(this)->setDrawFrame(false);
282                 InsetText::draw(pi, textx, texty);
283                 const_cast<InsetCollapsable *>(this)->setDrawFrame(true);
284
285                 int desc = textdim.descent();
286                 if (geometry() == Corners)
287                         desc -= 3;
288
289                 const int xx1 = x + TEXT_TO_INSET_OFFSET - 1;
290                 const int xx2 = x + textdim.wid - TEXT_TO_INSET_OFFSET + 1;
291                 pi.pain.line(xx1, y + desc - 4, 
292                              xx1, y + desc, 
293                         layout_.labelfont.color());
294                 if (internalStatus() == Open)
295                         pi.pain.line(xx1, y + desc, 
296                                 xx2, y + desc,
297                                 layout_.labelfont.color());
298                 else {
299                         // Make status_ value visible:
300                         pi.pain.line(xx1, y + desc,
301                                 xx1 + 4, y + desc,
302                                 layout_.labelfont.color());
303                         pi.pain.line(xx2 - 4, y + desc,
304                                 xx2, y + desc,
305                                 layout_.labelfont.color());
306                 }
307                 pi.pain.line(x + textdim.wid - 3, y + desc, x + textdim.wid - 3, y + desc - 4,
308                         layout_.labelfont.color());
309
310                 // the label below the text. Can be toggled.
311                 if (geometry() == SubLabel) {
312                         Font font(layout_.labelfont);
313                         font.realize(Font(Font::ALL_SANE));
314                         font.decSize();
315                         font.decSize();
316                         int w = 0;
317                         int a = 0;
318                         int d = 0;
319                         docstring s = layout_.labelstring;
320                         theFontMetrics(font).rectText(s, w, a, d);
321                         int const ww = max(textdim.wid, w);
322                         pi.pain.rectText(x + (ww - w) / 2, y + desc + a,
323                                 s, font, Color_none, Color_none);
324                         desc += d;
325                 }
326
327                 // a visual cue when the cursor is inside the inset
328                 Cursor & cur = pi.base.bv->cursor();
329                 if (cur.isInside(this)) {
330                         y -= textdim.asc;
331                         y += 3;
332                         pi.pain.line(xx1, y + 4, xx1, y, layout_.labelfont.color());
333                         pi.pain.line(xx1 + 4, y, xx1, y, layout_.labelfont.color());
334                         pi.pain.line(xx2, y + 4, xx2, y,
335                                 layout_.labelfont.color());
336                         pi.pain.line(xx2 - 4, y, xx2, y,
337                                 layout_.labelfont.color());
338                 }
339                 break;
340         }
341         pi.background_color = old_color;
342 }
343
344
345 void InsetCollapsable::cursorPos(BufferView const & bv,
346                 CursorSlice const & sl, bool boundary, int & x, int & y) const
347 {
348         if (geometry() == ButtonOnly)
349                 status_ = Open;
350         BOOST_ASSERT(geometry() != ButtonOnly);
351
352         InsetText::cursorPos(bv, sl, boundary, x, y);
353         Dimension const textdim = InsetText::dimension(bv);
354
355         switch (geometry()) {
356         case LeftButton:
357                 x += dimensionCollapsed().wid;
358                 break;
359         case TopButton: {
360                 y += dimensionCollapsed().des + textdim.asc;
361                 break;
362         }
363         case NoButton:
364         case SubLabel:
365         case Corners:
366                 // Do nothing
367                 break;
368         case ButtonOnly:
369                 // Cannot get here
370                 break;
371         }
372 }
373
374
375 Inset::EDITABLE InsetCollapsable::editable() const
376 {
377         return geometry() != ButtonOnly? HIGHLY_EDITABLE : IS_EDITABLE;
378 }
379
380
381 bool InsetCollapsable::descendable() const
382 {
383         return geometry() != ButtonOnly;
384 }
385
386
387 bool InsetCollapsable::hitButton(FuncRequest const & cmd) const
388 {
389         return button_dim.contains(cmd.x, cmd.y);
390 }
391
392
393 docstring const InsetCollapsable::getNewLabel(docstring const & l) const
394 {
395         docstring label;
396         pos_type const max_length = 15;
397         pos_type const p_siz = paragraphs().begin()->size();
398         pos_type const n = std::min(max_length, p_siz);
399         pos_type i = 0;
400         pos_type j = 0;
401         for (; i < n && j < p_siz; ++j) {
402                 if (paragraphs().begin()->isInset(j))
403                         continue;
404                 label += paragraphs().begin()->getChar(j);
405                 ++i;
406         }
407         if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) {
408                 label += "...";
409         }
410         return label.empty() ? l : label;
411 }
412
413
414 void InsetCollapsable::edit(Cursor & cur, bool left)
415 {
416         //lyxerr << "InsetCollapsable: edit left/right" << endl;
417         cur.push(*this);
418         InsetText::edit(cur, left);
419 }
420
421
422 Inset * InsetCollapsable::editXY(Cursor & cur, int x, int y)
423 {
424         //lyxerr << "InsetCollapsable: edit xy" << endl;
425         if (geometry() == ButtonOnly
426          || (button_dim.contains(x, y) 
427           && geometry() != NoButton))
428                 return this;
429         cur.push(*this);
430         return InsetText::editXY(cur, x, y);
431 }
432
433
434 void InsetCollapsable::doDispatch(Cursor & cur, FuncRequest & cmd)
435 {
436         //lyxerr << "InsetCollapsable::doDispatch (begin): cmd: " << cmd
437         //      << " cur: " << cur << " bvcur: " << cur.bv().cursor() << endl;
438
439         switch (cmd.action) {
440         case LFUN_MOUSE_PRESS:
441                 if (cmd.button() == mouse_button::button1 
442                  && hitButton(cmd) 
443                  && geometry() != NoButton) {
444                         // reset selection if necessary (see bug 3060)
445                         if (cur.selection())
446                                 cur.bv().cursor().clearSelection();
447                         else
448                                 cur.noUpdate();
449                         cur.dispatched();
450                         break;
451                 }
452                 if (geometry() == NoButton)
453                         InsetText::doDispatch(cur, cmd);
454                 else if (geometry() != ButtonOnly 
455                      && !hitButton(cmd))
456                         InsetText::doDispatch(cur, cmd);
457                 else
458                         cur.undispatched();
459                 break;
460
461         case LFUN_MOUSE_MOTION:
462         case LFUN_MOUSE_DOUBLE:
463         case LFUN_MOUSE_TRIPLE:
464                 if (geometry() == NoButton)
465                         InsetText::doDispatch(cur, cmd);
466                 else if (geometry() != ButtonOnly
467                      && !hitButton(cmd))
468                         InsetText::doDispatch(cur, cmd);
469                 else
470                         cur.undispatched();
471                 break;
472
473         case LFUN_MOUSE_RELEASE:
474                 if (cmd.button() == mouse_button::button3) {
475                         // There is no button to right click:
476                         if (geometry() == Corners ||
477                             geometry() == SubLabel ||
478                             geometry() == NoButton)  {
479                                 if (internalStatus() == Open)
480                                         setStatus(cur, Collapsed);
481                                 else
482                                         setStatus(cur, Open);
483                                 break;
484                         } else {
485                                 // Open the Inset 
486                                 // configuration dialog
487                                 showInsetDialog(&cur.bv());
488                                 break;
489                         }
490                 }
491
492                 if (geometry() == NoButton) {
493                         // The mouse click has to be within the inset!
494                         InsetText::doDispatch(cur, cmd);
495                         break;
496                 }
497
498                 if (cmd.button() == mouse_button::button1 && hitButton(cmd)) {
499                         // if we are selecting, we do not want to
500                         // toggle the inset.
501                         if (cur.selection())
502                                 break;
503                         // Left button is clicked, the user asks to
504                         // toggle the inset visual state.
505                         cur.dispatched();
506                         cur.updateFlags(Update::Force | Update::FitCursor);
507                         if (geometry() == ButtonOnly) {
508                                 setStatus(cur, Open);
509                                 edit(cur, true);
510                         }
511                         else {
512                                 setStatus(cur, Collapsed);
513                         }
514                         cur.bv().cursor() = cur;
515                         break;
516                 }
517
518                 // The mouse click is within the opened inset.
519                 if (geometry() == TopButton
520                  || geometry() == LeftButton)
521                         InsetText::doDispatch(cur, cmd);
522                 break;
523
524         case LFUN_INSET_TOGGLE:
525                 if (cmd.argument() == "open")
526                         setStatus(cur, Open);
527                 else if (cmd.argument() == "close")
528                         setStatus(cur, Collapsed);
529                 else if (cmd.argument() == "toggle" || cmd.argument().empty())
530                         if (internalStatus() == Open) {
531                                 setStatus(cur, Collapsed);
532                                 if (geometry() == ButtonOnly)
533                                         cur.top().forwardPos();
534                         } else
535                                 setStatus(cur, Open);
536                 else // if assign or anything else
537                         cur.undispatched();
538                 cur.dispatched();
539                 break;
540
541         default:
542                 InsetText::doDispatch(cur, cmd);
543                 break;
544         }
545 }
546
547
548 bool InsetCollapsable::allowMultiPar() const
549 {
550         return layout_.multipar;
551 }
552
553
554 bool InsetCollapsable::getStatus(Cursor & cur, FuncRequest const & cmd,
555                 FuncStatus & flag) const
556 {
557         switch (cmd.action) {
558                 // suppress these
559                 case LFUN_ACCENT_ACUTE:
560                 case LFUN_ACCENT_BREVE:
561                 case LFUN_ACCENT_CARON:
562                 case LFUN_ACCENT_CEDILLA:
563                 case LFUN_ACCENT_CIRCLE:
564                 case LFUN_ACCENT_CIRCUMFLEX:
565                 case LFUN_ACCENT_DOT:
566                 case LFUN_ACCENT_GRAVE:
567                 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
568                 case LFUN_ACCENT_MACRON:
569                 case LFUN_ACCENT_OGONEK:
570                 case LFUN_ACCENT_SPECIAL_CARON:
571                 case LFUN_ACCENT_TIE:
572                 case LFUN_ACCENT_TILDE:
573                 case LFUN_ACCENT_UMLAUT:
574                 case LFUN_ACCENT_UNDERBAR:
575                 case LFUN_ACCENT_UNDERDOT:
576                 case LFUN_APPENDIX:
577                 case LFUN_BIBITEM_INSERT:
578                 case LFUN_BOX_INSERT:
579                 case LFUN_BRANCH_INSERT:
580                 case LFUN_BREAK_LINE:
581                 case LFUN_CAPTION_INSERT:
582                 case LFUN_CLEARPAGE_INSERT:
583                 case LFUN_CLEARDOUBLEPAGE_INSERT:
584                 case LFUN_DEPTH_DECREMENT:
585                 case LFUN_DEPTH_INCREMENT:
586                 case LFUN_DOTS_INSERT:
587                 case LFUN_END_OF_SENTENCE_PERIOD_INSERT:
588                 case LFUN_ENVIRONMENT_INSERT:
589                 case LFUN_ERT_INSERT:
590                 case LFUN_FILE_INSERT:
591                 case LFUN_FLEX_INSERT:
592                 case LFUN_FLOAT_INSERT:
593                 case LFUN_FLOAT_LIST:
594                 case LFUN_FLOAT_WIDE_INSERT:
595                 case LFUN_FONT_BOLD:
596                 case LFUN_FONT_TYPEWRITER:
597                 case LFUN_FONT_DEFAULT:
598                 case LFUN_FONT_EMPH:
599                 case LFUN_FONT_FREE_APPLY:
600                 case LFUN_FONT_FREE_UPDATE:
601                 case LFUN_FONT_NOUN:
602                 case LFUN_FONT_ROMAN:
603                 case LFUN_FONT_SANS:
604                 case LFUN_FONT_FRAK:
605                 case LFUN_FONT_ITAL:
606                 case LFUN_FONT_SIZE:
607                 case LFUN_FONT_STATE:
608                 case LFUN_FONT_UNDERLINE:
609                 case LFUN_FOOTNOTE_INSERT:
610                 case LFUN_HFILL_INSERT:
611                 case LFUN_HYPERLINK_INSERT:
612                 case LFUN_HYPHENATION_POINT_INSERT:
613                 case LFUN_INDEX_INSERT:
614                 case LFUN_INDEX_PRINT:
615                 case LFUN_INSET_INSERT:
616                 case LFUN_LABEL_GOTO:
617                 case LFUN_LABEL_INSERT:
618                 case LFUN_LIGATURE_BREAK_INSERT:
619                 case LFUN_LINE_INSERT:
620                 case LFUN_PAGEBREAK_INSERT:
621                 case LFUN_LANGUAGE:
622                 case LFUN_LAYOUT:
623                 case LFUN_LAYOUT_PARAGRAPH:
624                 case LFUN_LAYOUT_TABULAR:
625                 case LFUN_MARGINALNOTE_INSERT:
626                 case LFUN_MATH_DISPLAY:
627                 case LFUN_MATH_INSERT:
628                 case LFUN_MATH_MATRIX:
629                 case LFUN_MATH_MODE:
630                 case LFUN_MENU_OPEN:
631                 case LFUN_MENU_SEPARATOR_INSERT:
632                 case LFUN_NOACTION:
633                 case LFUN_NOMENCL_INSERT:
634                 case LFUN_NOMENCL_PRINT:
635                 case LFUN_NOTE_INSERT:
636                 case LFUN_NOTE_NEXT:
637                 case LFUN_OPTIONAL_INSERT:
638                 case LFUN_PARAGRAPH_PARAMS:
639                 case LFUN_PARAGRAPH_PARAMS_APPLY:
640                 case LFUN_PARAGRAPH_SPACING:
641                 case LFUN_PARAGRAPH_UPDATE:
642                 case LFUN_REFERENCE_NEXT:
643                 case LFUN_SERVER_GOTO_FILE_ROW:
644                 case LFUN_SERVER_NOTIFY:
645                 case LFUN_SERVER_SET_XY:
646                 case LFUN_SPACE_INSERT:
647                 case LFUN_TABULAR_INSERT:
648                 case LFUN_TOC_INSERT:
649                 case LFUN_WRAP_INSERT:
650                 if (layout_.passthru) {
651                         flag.enabled(false);
652                         return true;
653                 } else
654                         return InsetText::getStatus(cur, cmd, flag);
655
656         case LFUN_INSET_TOGGLE:
657                 if (cmd.argument() == "open" || cmd.argument() == "close" ||
658                     cmd.argument() == "toggle")
659                         flag.enabled(true);
660                 else
661                         flag.enabled(false);
662                 return true;
663
664         default:
665                 return InsetText::getStatus(cur, cmd, flag);
666         }
667 }
668
669
670 void InsetCollapsable::setLabel(docstring const & l)
671 {
672         layout_.labelstring = l;
673 }
674
675
676 void InsetCollapsable::setStatus(Cursor & cur, CollapseStatus status)
677 {
678         status_ = status;
679         setButtonLabel();
680         if (status_ == Collapsed)
681                 cur.leaveInset(*this);
682 }
683
684
685 void InsetCollapsable::setLabelFont(Font const & font)
686 {
687         layout_.labelfont = font;
688 }
689
690 docstring InsetCollapsable::floatName(string const & type, BufferParams const & bp) const
691 {
692         FloatList const & floats = bp.getTextClass().floats();
693         FloatList::const_iterator it = floats[type];
694         // FIXME UNICODE
695         return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name());
696 }
697
698
699 InsetCollapsable::Decoration InsetCollapsable::decoration() const
700 {
701         if (layout_.decoration == "classic")
702                 return Classic;
703         if (layout_.decoration == "minimalistic")
704                 return Minimalistic;
705         if (layout_.decoration == "conglomerate")
706                 return Conglomerate;
707         if (name() == from_ascii("Flex"))
708                 return Conglomerate;
709         return Classic;
710 }
711
712
713 int InsetCollapsable::latex(Buffer const & buf, odocstream & os,
714                           OutputParams const & runparams) const
715 {
716         // This implements the standard way of handling the LaTeX output of
717         // a collapsable inset, either a command or an environment. Standard 
718         // collapsable insets should not redefine this, non-standard ones may
719         // call this.
720         if (!layout_.latexname.empty()) {
721                 if (layout_.latextype == "command") {
722                         // FIXME UNICODE
723                         os << '\\' << from_utf8(layout_.latexname);
724                         if (!layout_.latexparam.empty())
725                                 os << from_utf8(layout_.latexparam);
726                         os << '{';
727                 } else if (layout_.latextype == "environment") {
728                         os << "%\n\\begin{" << from_utf8(layout_.latexname) << "}\n";
729                         if (!layout_.latexparam.empty())
730                                 os << from_utf8(layout_.latexparam);
731                 }
732         }
733         OutputParams rp = runparams;
734         if (layout_.passthru)
735                 rp.verbatim = true;
736         int i = InsetText::latex(buf, os, rp);
737         if (!layout_.latexname.empty()) {
738                 if (layout_.latextype == "command") {
739                         os << "}";
740                 } else if (layout_.latextype == "environment") {
741                         os << "\n\\end{" << from_utf8(layout_.latexname) << "}\n";
742                         i += 4;
743                 }
744         }
745         return i;
746 }
747
748
749 void InsetCollapsable::validate(LaTeXFeatures & features) const
750 {
751         // Force inclusion of preamble snippet in layout file
752         features.require(layout_.name);
753         InsetText::validate(features);
754 }
755
756
757 } // namespace lyx