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