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