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