]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCollapsable.cpp
4af355c29c8f175f80dd3f3411c1b3c88a81d913
[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 "FuncRequest.h"
25 #include "FuncStatus.h"
26 #include "InsetLayout.h"
27 #include "Language.h"
28 #include "LaTeXFeatures.h"
29 #include "Lexer.h"
30 #include "MetricsInfo.h"
31 #include "paragraph_funcs.h"
32 #include "ParagraphParameters.h"
33 #include "sgml.h"
34 #include "TextClass.h"
35
36 #include "frontends/FontMetrics.h"
37 #include "frontends/Painter.h"
38
39 #include "support/debug.h"
40 #include "support/docstream.h"
41 #include "support/gettext.h"
42 #include "support/lassert.h"
43 #include "support/lstrings.h"
44
45 using namespace std;
46
47
48 namespace lyx {
49
50 InsetCollapsable::CollapseStatus InsetCollapsable::status(BufferView const & bv) const
51 {
52         if (decoration() == InsetLayout::CONGLOMERATE)
53                 return status_;
54         return auto_open_[&bv] ? Open : status_;
55 }
56
57
58 InsetCollapsable::Geometry InsetCollapsable::geometry(BufferView const & bv) const
59 {
60         switch (decoration()) {
61         case InsetLayout::CLASSIC:
62                 if (status(bv) == Open)
63                         return openinlined_ ? LeftButton : TopButton;
64                 return ButtonOnly;
65
66         case InsetLayout::MINIMALISTIC:
67                 return status(bv) == Open ? NoButton : ButtonOnly ;
68
69         case InsetLayout::CONGLOMERATE:
70                 return status(bv) == Open ? SubLabel : Corners ;
71
72         case InsetLayout::DEFAULT:
73                 break; // this shouldn't happen
74         }
75
76         // dummy return value to shut down a warning,
77         // this is dead code.
78         return NoButton;
79 }
80
81
82 InsetCollapsable::Geometry InsetCollapsable::geometry() const
83 {
84         switch (decoration()) {
85         case InsetLayout::CLASSIC:
86                 if (status_ == Open)
87                         return openinlined_ ? LeftButton : TopButton;
88                 return ButtonOnly;
89
90         case InsetLayout::MINIMALISTIC:
91                 return status_ == Open ? NoButton : ButtonOnly ;
92
93         case InsetLayout::CONGLOMERATE:
94                 return status_ == Open ? SubLabel : Corners ;
95
96         case InsetLayout::DEFAULT:
97                 break; // this shouldn't happen
98         }
99
100         // dummy return value to shut down a warning,
101         // this is dead code.
102         return NoButton;
103 }
104
105
106 InsetCollapsable::InsetCollapsable(Buffer const & buf, InsetText::UsePlain ltype)
107         : InsetText(buf, ltype), status_(Inset::Open),
108           openinlined_(false), mouse_hover_(false)
109 {
110         setLayout(&buf.params().documentClass());
111         setAutoBreakRows(true);
112         setDrawFrame(true);
113         setFrameColor(Color_collapsableframe);
114 }
115
116
117 InsetCollapsable::InsetCollapsable(InsetCollapsable const & rhs)
118         : InsetText(rhs),
119           status_(rhs.status_),
120           layout_(rhs.layout_),
121           labelstring_(rhs.labelstring_),
122           button_dim(rhs.button_dim),
123           openinlined_(rhs.openinlined_),
124           auto_open_(rhs.auto_open_),
125           // the sole purpose of this copy constructor
126           mouse_hover_(false)
127 {
128 }
129
130
131 docstring InsetCollapsable::toolTip(BufferView const & bv, int x, int y) const
132 {
133         Dimension dim = dimensionCollapsed(bv);
134         if (geometry(bv) == NoButton)
135                 return translateIfPossible(layout_->labelstring());
136         if (x > xo(bv) + dim.wid || y > yo(bv) + dim.des || isOpen(bv))
137                 return docstring();
138
139         OutputParams rp(&buffer().params().encoding());
140         odocstringstream ods;
141         InsetText::plaintext(ods, rp);
142         docstring const content_tip = ods.str();
143         return support::wrapParas(content_tip, 4);
144 }
145
146
147 void InsetCollapsable::setLayout(BufferParams const & bp)
148 {
149         setLayout(bp.documentClassPtr());
150 }
151
152
153 void InsetCollapsable::setLayout(DocumentClass const * const dc)
154 {
155         if (dc) {
156                 layout_ = &(dc->insetLayout(name()));
157                 labelstring_ = translateIfPossible(layout_->labelstring());
158         } else {
159                 layout_ = &DocumentClass::plainInsetLayout();
160                 labelstring_ = _("UNDEFINED");
161         }
162
163         setButtonLabel();
164 }
165
166
167 void InsetCollapsable::write(ostream & os) const
168 {
169         os << "status ";
170         switch (status_) {
171         case Open:
172                 os << "open";
173                 break;
174         case Collapsed:
175                 os << "collapsed";
176                 break;
177         }
178         os << "\n";
179         text().write(buffer(), os);
180 }
181
182
183 void InsetCollapsable::read(Lexer & lex)
184 {
185         lex.setContext("InsetCollapsable::read");
186         string tmp_token;
187         status_ = Collapsed;
188         lex >> "status" >> tmp_token;
189         if (tmp_token == "open")
190                 status_ = Open;
191
192         // this must be set before we enter InsetText::read()
193         setLayout(buffer().params());
194         InsetText::read(lex);
195         // set button label again as the inset contents was not read yet at
196         // setLayout() time.
197         setButtonLabel();
198
199         // Force default font, if so requested
200         // This avoids paragraphs in buffer language that would have a
201         // foreign language after a document language change, and it ensures
202         // that all new text in ERT and similar gets the "latex" language,
203         // since new text inherits the language from the last position of the
204         // existing text.  As a side effect this makes us also robust against
205         // bugs in LyX that might lead to font changes in ERT in .lyx files.
206         resetParagraphsFont();
207 }
208
209
210 Dimension InsetCollapsable::dimensionCollapsed(BufferView const & bv) const
211 {
212         LASSERT(layout_, /**/);
213         Dimension dim;
214         theFontMetrics(layout_->labelfont()).buttonText(
215                 buttonLabel(bv), dim.wid, dim.asc, dim.des);
216         return dim;
217 }
218
219
220 void InsetCollapsable::metrics(MetricsInfo & mi, Dimension & dim) const
221 {
222         LASSERT(layout_, /**/);
223
224         auto_open_[mi.base.bv] =  mi.base.bv->cursor().isInside(this);
225
226         FontInfo tmpfont = mi.base.font;
227         mi.base.font = layout_->font();
228         mi.base.font.realize(tmpfont);
229
230         BufferView const & bv = *mi.base.bv;
231
232         switch (geometry(bv)) {
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(buttonLabel(bv), w, a, d);
252                 dim.des += a + d;
253                 break;
254                 }
255         case TopButton:
256         case LeftButton:
257         case ButtonOnly:
258                 dim = dimensionCollapsed(bv);
259                 if (geometry(bv) == TopButton 
260                           || geometry(bv) == LeftButton) {
261                         Dimension textdim;
262                         InsetText::metrics(mi, textdim);
263                         openinlined_ = (textdim.wid + dim.wid) < mi.base.textwidth;
264                         if (openinlined_) {
265                                 // Correct for button width.
266                                 dim.wid += textdim.wid;
267                                 dim.des = max(dim.des - textdim.asc + dim.asc, textdim.des);
268                                 dim.asc = textdim.asc;
269                         } else {
270                                 dim.des += textdim.height() + TEXT_TO_INSET_OFFSET;
271                                 dim.wid = max(dim.wid, textdim.wid);
272                         }
273                 }
274                 break;
275         }
276
277         mi.base.font = tmpfont;
278 }
279
280
281 bool InsetCollapsable::setMouseHover(bool mouse_hover)
282 {
283         mouse_hover_ = mouse_hover;
284         return true;
285 }
286
287
288 void InsetCollapsable::draw(PainterInfo & pi, int x, int y) const
289 {
290         LASSERT(layout_, /**/);
291         BufferView const & bv = *pi.base.bv;
292
293         auto_open_[&bv] =  bv.cursor().isInside(this);
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(bv);
301
302         if (geometry(*pi.base.bv) == TopButton ||
303             geometry(*pi.base.bv) == LeftButton ||
304             geometry(*pi.base.bv) == 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, buttonLabel(bv), 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(bv);
320         int const baseline = y;
321         int textx, texty;
322         switch (geometry(bv)) {
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(bv) == 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(bv) == 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(buttonLabel(bv), w, a, d);
383                         int const ww = max(textdim.wid, w);
384                         pi.pain.rectText(x + (ww - w) / 2, y + desc + a,
385                                 buttonLabel(bv), font, Color_none, Color_none);
386                         desc += d;
387                 }
388
389                 // a visual cue when the cursor is inside the inset
390                 Cursor const & cur = 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
404         pi.base.font = tmpfont;
405 }
406
407
408 void InsetCollapsable::cursorPos(BufferView const & bv,
409                 CursorSlice const & sl, bool boundary, int & x, int & y) const
410 {
411         if (geometry(bv) == ButtonOnly)
412                 status_ = Open;
413         LASSERT(geometry(bv) != ButtonOnly, /**/);
414
415         InsetText::cursorPos(bv, sl, boundary, x, y);
416         Dimension const textdim = InsetText::dimension(bv);
417
418         switch (geometry(bv)) {
419         case LeftButton:
420                 x += dimensionCollapsed(bv).wid;
421                 break;
422         case TopButton: {
423                 y += dimensionCollapsed(bv).des + textdim.asc;
424                 break;
425         }
426         case NoButton:
427         case SubLabel:
428         case Corners:
429                 // Do nothing
430                 break;
431         case ButtonOnly:
432                 // Cannot get here
433                 break;
434         }
435 }
436
437
438 bool InsetCollapsable::editable() const
439 {
440         return geometry() != ButtonOnly;
441 }
442
443
444 bool InsetCollapsable::descendable() const
445 {
446         return geometry() != ButtonOnly;
447 }
448
449
450 bool InsetCollapsable::hitButton(FuncRequest const & cmd) const
451 {
452         return button_dim.contains(cmd.x, cmd.y);
453 }
454
455
456 docstring const InsetCollapsable::getNewLabel(docstring const & l) const
457 {
458         docstring label;
459         pos_type const max_length = 15;
460         pos_type const p_siz = paragraphs().begin()->size();
461         pos_type const n = min(max_length, p_siz);
462         pos_type i = 0;
463         pos_type j = 0;
464         for (; i < n && j < p_siz; ++j) {
465                 if (paragraphs().begin()->isInset(j))
466                         continue;
467                 label += paragraphs().begin()->getChar(j);
468                 ++i;
469         }
470         if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) {
471                 label += "...";
472         }
473         return label.empty() ? l : label;
474 }
475
476
477 void InsetCollapsable::edit(Cursor & cur, bool front, EntryDirection entry_from)
478 {
479         //lyxerr << "InsetCollapsable: edit left/right" << endl;
480         cur.push(*this);
481         InsetText::edit(cur, front, entry_from);
482 }
483
484
485 Inset * InsetCollapsable::editXY(Cursor & cur, int x, int y)
486 {
487         //lyxerr << "InsetCollapsable: edit xy" << endl;
488         if (geometry(cur.bv()) == ButtonOnly
489          || (button_dim.contains(x, y) 
490           && geometry(cur.bv()) != NoButton))
491                 return this;
492         cur.push(*this);
493         return InsetText::editXY(cur, x, y);
494 }
495
496
497 void InsetCollapsable::doDispatch(Cursor & cur, FuncRequest & cmd)
498 {
499         //lyxerr << "InsetCollapsable::doDispatch (begin): cmd: " << cmd
500         //      << " cur: " << cur << " bvcur: " << cur.bv().cursor() << endl;
501
502         switch (cmd.action) {
503         case LFUN_MOUSE_PRESS:
504                 if (hitButton(cmd)) {
505                         switch (cmd.button()) {
506                         case mouse_button::button1:
507                         case mouse_button::button3:
508                                 // Pass the command to the enclosing InsetText,
509                                 // so that the cursor gets set.
510                                 cur.undispatched();
511                                 break;
512                         case mouse_button::none:
513                         case mouse_button::button2:
514                         case mouse_button::button4:
515                         case mouse_button::button5:
516                                 // Nothing to do.
517                                 cur.noUpdate();
518                                 break;
519                         }
520                 } else if (geometry(cur.bv()) != ButtonOnly)
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 (hitButton(cmd)) 
530                         cur.noUpdate();
531                 else if (geometry(cur.bv()) != ButtonOnly)
532                         InsetText::doDispatch(cur, cmd);
533                 else
534                         cur.undispatched();
535                 break;
536
537         case LFUN_MOUSE_RELEASE:
538                 if (!hitButton(cmd)) {
539                         // The mouse click has to be within the inset!
540                         if (geometry(cur.bv()) != ButtonOnly)
541                                 InsetText::doDispatch(cur, cmd);
542                         else
543                                 cur.undispatched();                     
544                         break;
545                 }
546                 if (cmd.button() != mouse_button::button1) {
547                         // Nothing to do.
548                         cur.noUpdate();
549                         break;
550                 }
551                 // if we are selecting, we do not want to
552                 // toggle the inset.
553                 if (cur.selection())
554                         break;
555                 // Left button is clicked, the user asks to
556                 // toggle the inset visual state.
557                 cur.dispatched();
558                 cur.updateFlags(Update::Force | Update::FitCursor);
559                 if (geometry(cur.bv()) == ButtonOnly) {
560                         setStatus(cur, Open);
561                         edit(cur, true);
562                 }
563                 else
564                         setStatus(cur, Collapsed);
565                 cur.bv().cursor() = cur;
566                 break;
567
568         case LFUN_INSET_TOGGLE:
569                 if (cmd.argument() == "open")
570                         setStatus(cur, Open);
571                 else if (cmd.argument() == "close")
572                         setStatus(cur, Collapsed);
573                 else if (cmd.argument() == "toggle" || cmd.argument().empty())
574                         if (status_ == Open) {
575                                 setStatus(cur, Collapsed);
576                                 if (geometry(cur.bv()) == ButtonOnly)
577                                         cur.top().forwardPos();
578                         } else
579                                 setStatus(cur, Open);
580                 else // if assign or anything else
581                         cur.undispatched();
582                 cur.dispatched();
583                 break;
584
585         case LFUN_PASTE:
586         case LFUN_CLIPBOARD_PASTE:
587         case LFUN_PRIMARY_SELECTION_PASTE: {
588                 InsetText::doDispatch(cur, cmd);
589                 // Since we can only store plain text, we must reset all
590                 // attributes.
591                 // FIXME: Change only the pasted paragraphs
592
593                 resetParagraphsFont();
594                 break;
595         }
596
597         default:
598                 if (layout_ && layout_->isForceLtr()) {
599                         // Force any new text to latex_language
600                         // FIXME: This should only be necessary in constructor, but
601                         // new paragraphs that are created by pressing enter at the
602                         // start of an existing paragraph get the buffer language
603                         // and not latex_language, so we take this brute force
604                         // approach.
605                         cur.current_font.setLanguage(latex_language);
606                         cur.real_current_font.setLanguage(latex_language);
607                 }
608                 InsetText::doDispatch(cur, cmd);
609                 break;
610         }
611 }
612
613
614 bool InsetCollapsable::allowMultiPar() const
615 {
616         return layout_->isMultiPar();
617 }
618
619
620 void InsetCollapsable::resetParagraphsFont()
621 {
622         Font font(inherit_font, buffer().params().language);
623         if (layout_->isForceLtr())
624                 font.setLanguage(latex_language);
625         if (layout_->isPassThru()) {
626                 ParagraphList::iterator par = paragraphs().begin();
627                 ParagraphList::iterator const end = paragraphs().end();
628                 while (par != end) {
629                         par->resetFonts(font);
630                         par->params().clear();
631                         ++par;
632                 }
633         }
634 }
635
636
637 bool InsetCollapsable::getStatus(Cursor & cur, FuncRequest const & cmd,
638                 FuncStatus & flag) const
639 {
640         switch (cmd.action) {
641         // FIXME At present, these are being enabled and disabled according to
642         // whether PASSTHRU has been set in the InsetLayout. This makes some
643         // sense, but there are other checks that should really be done. E.g.,
644         // one should not be able to inset IndexPrint inside an optional argument!!
645         case LFUN_ACCENT_ACUTE:
646         case LFUN_ACCENT_BREVE:
647         case LFUN_ACCENT_CARON:
648         case LFUN_ACCENT_CEDILLA:
649         case LFUN_ACCENT_CIRCLE:
650         case LFUN_ACCENT_CIRCUMFLEX:
651         case LFUN_ACCENT_DOT:
652         case LFUN_ACCENT_GRAVE:
653         case LFUN_ACCENT_HUNGARIAN_UMLAUT:
654         case LFUN_ACCENT_MACRON:
655         case LFUN_ACCENT_OGONEK:
656         case LFUN_ACCENT_TIE:
657         case LFUN_ACCENT_TILDE:
658         case LFUN_ACCENT_UMLAUT:
659         case LFUN_ACCENT_UNDERBAR:
660         case LFUN_ACCENT_UNDERDOT:
661         case LFUN_APPENDIX:
662         case LFUN_BOX_INSERT:
663         case LFUN_BRANCH_INSERT:
664         case LFUN_NEWLINE_INSERT:
665         case LFUN_CAPTION_INSERT:
666         case LFUN_DEPTH_DECREMENT:
667         case LFUN_DEPTH_INCREMENT:
668         case LFUN_ERT_INSERT:
669         case LFUN_FILE_INSERT:
670         case LFUN_FLEX_INSERT:
671         case LFUN_FLOAT_INSERT:
672         case LFUN_FLOAT_LIST_INSERT:
673         case LFUN_FLOAT_WIDE_INSERT:
674         case LFUN_FONT_BOLD:
675         case LFUN_FONT_BOLDSYMBOL:
676         case LFUN_FONT_TYPEWRITER:
677         case LFUN_FONT_DEFAULT:
678         case LFUN_FONT_EMPH:
679         case LFUN_TEXTSTYLE_APPLY:
680         case LFUN_TEXTSTYLE_UPDATE:
681         case LFUN_FONT_NOUN:
682         case LFUN_FONT_ROMAN:
683         case LFUN_FONT_SANS:
684         case LFUN_FONT_FRAK:
685         case LFUN_FONT_ITAL:
686         case LFUN_FONT_SIZE:
687         case LFUN_FONT_STATE:
688         case LFUN_FONT_UNDERLINE:
689         case LFUN_FONT_STRIKEOUT:
690         case LFUN_FONT_UULINE:
691         case LFUN_FONT_UWAVE:
692         case LFUN_FOOTNOTE_INSERT:
693         case LFUN_HYPERLINK_INSERT:
694         case LFUN_INDEX_INSERT:
695         case LFUN_INDEX_PRINT:
696         case LFUN_INSET_INSERT:
697         case LFUN_LABEL_GOTO:
698         case LFUN_LABEL_INSERT:
699         case LFUN_LINE_INSERT:
700         case LFUN_NEWPAGE_INSERT:
701         case LFUN_LAYOUT_TABULAR:
702         case LFUN_MARGINALNOTE_INSERT:
703         case LFUN_MATH_DISPLAY:
704         case LFUN_MATH_INSERT:
705         case LFUN_MATH_MATRIX:
706         case LFUN_MATH_MODE:
707         case LFUN_MENU_OPEN:
708         case LFUN_NOACTION:
709         case LFUN_NOMENCL_INSERT:
710         case LFUN_NOMENCL_PRINT:
711         case LFUN_NOTE_INSERT:
712         case LFUN_NOTE_NEXT:
713         case LFUN_OPTIONAL_INSERT:
714         case LFUN_PHANTOM_INSERT:
715         case LFUN_REFERENCE_NEXT:
716         case LFUN_SERVER_GOTO_FILE_ROW:
717         case LFUN_SERVER_NOTIFY:
718         case LFUN_SERVER_SET_XY:
719         case LFUN_SPACE_INSERT:
720         case LFUN_SPECIALCHAR_INSERT:
721         case LFUN_TABULAR_INSERT:
722         case LFUN_TOC_INSERT:
723         case LFUN_WRAP_INSERT:
724                 if (layout_->isPassThru()) {
725                         flag.setEnabled(false);
726                         return true;
727                 }
728                 return InsetText::getStatus(cur, cmd, flag);
729
730         case LFUN_INSET_TOGGLE:
731                 if (cmd.argument() == "open")
732                         flag.setEnabled(status_ != Open);
733                 else if (cmd.argument() == "close")
734                         flag.setEnabled(status_ == Open);
735                 else if (cmd.argument() == "toggle" || cmd.argument().empty()) {
736                         flag.setEnabled(true);
737                         flag.setOnOff(status_ == Open);
738                 } else
739                         flag.setEnabled(false);
740                 return true;
741
742         case LFUN_LANGUAGE:
743                 flag.setEnabled(!layout_->isForceLtr());
744                 return InsetText::getStatus(cur, cmd, flag);
745
746         case LFUN_BREAK_PARAGRAPH:
747                 flag.setEnabled(layout_->isMultiPar());
748                 return true;
749
750         default:
751                 return InsetText::getStatus(cur, cmd, flag);
752         }
753 }
754
755
756 void InsetCollapsable::setLabel(docstring const & l)
757 {
758         labelstring_ = l;
759 }
760
761
762 void InsetCollapsable::setStatus(Cursor & cur, CollapseStatus status)
763 {
764         status_ = status;
765         setButtonLabel();
766         if (status_ == Collapsed) {
767                 cur.leaveInset(*this);
768                 mouse_hover_ = false;
769         }
770 }
771
772
773 docstring InsetCollapsable::floatName(
774                 string const & type, BufferParams const & bp) const
775 {
776         FloatList const & floats = bp.documentClass().floats();
777         FloatList::const_iterator it = floats[type];
778         // FIXME UNICODE
779         return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name());
780 }
781
782
783 InsetLayout::InsetDecoration InsetCollapsable::decoration() const
784 {
785         if (!layout_)
786                 return InsetLayout::CLASSIC;
787         InsetLayout::InsetDecoration const dec = layout_->decoration();
788         switch (dec) {
789         case InsetLayout::CLASSIC:
790         case InsetLayout::MINIMALISTIC:
791         case InsetLayout::CONGLOMERATE:
792                 return dec;
793         case InsetLayout::DEFAULT:
794                 break;
795         }
796         if (lyxCode() == FLEX_CODE)
797                 return InsetLayout::CONGLOMERATE;
798         return InsetLayout::CLASSIC;
799 }
800
801
802 int InsetCollapsable::latex(odocstream & os,
803                           OutputParams const & runparams) const
804 {
805         // FIXME: What should we do layout_ is 0?
806         // 1) assert
807         // 2) throw an error
808         if (!layout_)
809                 return 0;
810
811         // This implements the standard way of handling the LaTeX output of
812         // a collapsable inset, either a command or an environment. Standard 
813         // collapsable insets should not redefine this, non-standard ones may
814         // call this.
815         if (!layout_->latexname().empty()) {
816                 if (layout_->latextype() == InsetLayout::COMMAND) {
817                         // FIXME UNICODE
818                         if (runparams.moving_arg)
819                                 os << "\\protect";
820                         os << '\\' << from_utf8(layout_->latexname());
821                         if (!layout_->latexparam().empty())
822                                 os << from_utf8(layout_->latexparam());
823                         os << '{';
824                 } else if (layout_->latextype() == InsetLayout::ENVIRONMENT) {
825                         os << "%\n\\begin{" << from_utf8(layout_->latexname()) << "}\n";
826                         if (!layout_->latexparam().empty())
827                                 os << from_utf8(layout_->latexparam());
828                 }
829         }
830         OutputParams rp = runparams;
831         if (layout_->isPassThru())
832                 rp.verbatim = true;
833         if (layout_->isNeedProtect())
834                 rp.moving_arg = true;
835         int i = InsetText::latex(os, rp);
836         if (!layout_->latexname().empty()) {
837                 if (layout_->latextype() == InsetLayout::COMMAND) {
838                         os << "}";
839                 } else if (layout_->latextype() == InsetLayout::ENVIRONMENT) {
840                         os << "\n\\end{" << from_utf8(layout_->latexname()) << "}\n";
841                         i += 4;
842                 }
843         }
844         return i;
845 }
846
847
848 // FIXME It seems as if it ought to be possible to do this more simply,
849 // maybe by calling InsetText::docbook() in the middle there.
850 int InsetCollapsable::docbook(odocstream & os, OutputParams const & runparams) const
851 {
852         ParagraphList::const_iterator const beg = paragraphs().begin();
853         ParagraphList::const_iterator par = paragraphs().begin();
854         ParagraphList::const_iterator const end = paragraphs().end();
855
856         if (!undefined())
857                 sgml::openTag(os, getLayout().latexname(),
858                               par->getID(buffer(), runparams) + getLayout().latexparam());
859
860         for (; par != end; ++par) {
861                 par->simpleDocBookOnePar(buffer(), os, runparams,
862                                          outerFont(distance(beg, par),
863                                                    paragraphs()));
864         }
865
866         if (!undefined())
867                 sgml::closeTag(os, getLayout().latexname());
868
869         return 0;
870 }
871
872
873 void InsetCollapsable::validate(LaTeXFeatures & features) const
874 {
875         string const preamble = getLayout().preamble();
876         if (!preamble.empty())
877                 features.addPreambleSnippet(preamble);
878         features.require(getLayout().requires());
879         InsetText::validate(features);
880 }
881
882
883 bool InsetCollapsable::undefined() const
884 {
885         docstring const & n = getLayout().name();
886         return n.empty() || n == DocumentClass::plainInsetLayout().name();
887 }
888
889
890 docstring InsetCollapsable::contextMenu(BufferView const & bv, int x,
891         int y) const
892 {
893         if (decoration() == InsetLayout::CONGLOMERATE)
894                 return from_ascii("context-conglomerate");
895
896         if (geometry(bv) == NoButton)
897                 return from_ascii("context-collapsable");
898
899         Dimension dim = dimensionCollapsed(bv);
900         if (x < xo(bv) + dim.wid && y < yo(bv) + dim.des)
901                 return from_ascii("context-collapsable");
902
903         return InsetText::contextMenu(bv, x, y);
904 }
905
906 void InsetCollapsable::tocString(odocstream & os) const
907 {
908         if (!getLayout().isInToc())
909                 return;
910         os << text().asString(0, 1, AS_STR_LABEL | AS_STR_INSETS);
911 }
912
913
914 } // namespace lyx