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