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