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