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