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