]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCollapsable.cpp
Manage the initial setting of Default or Plain Layout more simply.
[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
44 using namespace std;
45
46
47 namespace lyx {
48
49 InsetCollapsable::CollapseStatus InsetCollapsable::status(BufferView const & bv) const
50 {
51         if (decoration() == InsetLayout::CONGLOMERATE)
52                 return status_;
53         return auto_open_[&bv] ? Open : status_;
54 }
55
56
57 InsetCollapsable::Geometry InsetCollapsable::geometry(BufferView const & bv) const
58 {
59         switch (decoration()) {
60         case InsetLayout::CLASSIC:
61                 if (status(bv) == Open)
62                         return openinlined_ ? LeftButton : TopButton;
63                 return ButtonOnly;
64
65         case InsetLayout::MINIMALISTIC:
66                 return status(bv) == Open ? NoButton : ButtonOnly ;
67
68         case InsetLayout::CONGLOMERATE:
69                 return status(bv) == Open ? SubLabel : Corners ;
70
71         case InsetLayout::DEFAULT:
72                 break; // this shouldn't happen
73         }
74
75         // dummy return value to shut down a warning,
76         // this is dead code.
77         return NoButton;
78 }
79
80
81 InsetCollapsable::Geometry InsetCollapsable::geometry() const
82 {
83         switch (decoration()) {
84         case InsetLayout::CLASSIC:
85                 if (status_ == Open)
86                         return openinlined_ ? LeftButton : TopButton;
87                 return ButtonOnly;
88
89         case InsetLayout::MINIMALISTIC:
90                 return status_ == Open ? NoButton : ButtonOnly ;
91
92         case InsetLayout::CONGLOMERATE:
93                 return status_ == Open ? SubLabel : Corners ;
94
95         case InsetLayout::DEFAULT:
96                 break; // this shouldn't happen
97         }
98
99         // dummy return value to shut down a warning,
100         // this is dead code.
101         return NoButton;
102 }
103
104
105 InsetCollapsable::InsetCollapsable(Buffer const & buf, InsetText::UsePlain ltype)
106         : InsetText(buf, ltype), status_(Inset::Open),
107           openinlined_(false), mouse_hover_(false)
108 {
109         setLayout(&buf.params().documentClass());
110         setAutoBreakRows(true);
111         setDrawFrame(true);
112         setFrameColor(Color_collapsableframe);
113 }
114
115
116 InsetCollapsable::InsetCollapsable(InsetCollapsable const & rhs)
117         : InsetText(rhs),
118           status_(rhs.status_),
119           layout_(rhs.layout_),
120           labelstring_(rhs.labelstring_),
121           button_dim(rhs.button_dim),
122           openinlined_(rhs.openinlined_),
123           auto_open_(rhs.auto_open_),
124           // the sole purpose of this copy constructor
125           mouse_hover_(false)
126 {
127 }
128
129
130 docstring InsetCollapsable::toolTip(BufferView const & bv, int x, int y) const
131 {
132         Dimension dim = dimensionCollapsed(bv);
133         if (geometry(bv) == NoButton)
134                 return translateIfPossible(layout_->labelstring());
135         if (x > xo(bv) + dim.wid || y > yo(bv) + dim.des || isOpen(bv))
136                 return docstring();
137
138         OutputParams rp(&buffer().params().encoding());
139         odocstringstream ods;
140         InsetText::plaintext(ods, rp);
141         docstring content_tip = ods.str();
142         // shorten it if necessary
143         if (content_tip.size() > 200)
144                 content_tip = content_tip.substr(0, 200) + "...";
145         return content_tip;
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 Inset::EDITABLE InsetCollapsable::editable() const
441 {
442         return geometry() != ButtonOnly ? HIGHLY_EDITABLE : IS_EDITABLE;
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_PRIMARY_SELECTION_PASTE: {
590                 InsetText::doDispatch(cur, cmd);
591                 // Since we can only store plain text, we must reset all
592                 // attributes.
593                 // FIXME: Change only the pasted paragraphs
594
595                 resetParagraphsFont();
596                 break;
597         }
598
599         default:
600                 if (layout_ && layout_->isForceLtr()) {
601                         // Force any new text to latex_language
602                         // FIXME: This should only be necessary in constructor, but
603                         // new paragraphs that are created by pressing enter at the
604                         // start of an existing paragraph get the buffer language
605                         // and not latex_language, so we take this brute force
606                         // approach.
607                         cur.current_font.setLanguage(latex_language);
608                         cur.real_current_font.setLanguage(latex_language);
609                 }
610                 InsetText::doDispatch(cur, cmd);
611                 break;
612         }
613 }
614
615
616 bool InsetCollapsable::allowMultiPar() const
617 {
618         return layout_->isMultiPar();
619 }
620
621
622 void InsetCollapsable::resetParagraphsFont()
623 {
624         Font font(inherit_font, buffer().params().language);
625         if (layout_->isForceLtr())
626                 font.setLanguage(latex_language);
627         if (layout_->isPassThru()) {
628                 ParagraphList::iterator par = paragraphs().begin();
629                 ParagraphList::iterator const end = paragraphs().end();
630                 while (par != end) {
631                         par->resetFonts(font);
632                         par->params().clear();
633                         ++par;
634                 }
635         }
636 }
637
638
639 bool InsetCollapsable::getStatus(Cursor & cur, FuncRequest const & cmd,
640                 FuncStatus & flag) const
641 {
642         switch (cmd.action) {
643         // FIXME At present, these are being enabled and disabled according to
644         // whether PASSTHRU has been set in the InsetLayout. This makes some
645         // sense, but there are other checks that should really be done. E.g.,
646         // one should not be able to inset IndexPrint inside an optional argument!!
647         case LFUN_ACCENT_ACUTE:
648         case LFUN_ACCENT_BREVE:
649         case LFUN_ACCENT_CARON:
650         case LFUN_ACCENT_CEDILLA:
651         case LFUN_ACCENT_CIRCLE:
652         case LFUN_ACCENT_CIRCUMFLEX:
653         case LFUN_ACCENT_DOT:
654         case LFUN_ACCENT_GRAVE:
655         case LFUN_ACCENT_HUNGARIAN_UMLAUT:
656         case LFUN_ACCENT_MACRON:
657         case LFUN_ACCENT_OGONEK:
658         case LFUN_ACCENT_TIE:
659         case LFUN_ACCENT_TILDE:
660         case LFUN_ACCENT_UMLAUT:
661         case LFUN_ACCENT_UNDERBAR:
662         case LFUN_ACCENT_UNDERDOT:
663         case LFUN_APPENDIX:
664         case LFUN_BOX_INSERT:
665         case LFUN_BRANCH_INSERT:
666         case LFUN_NEWLINE_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_TEXTSTYLE_APPLY:
682         case LFUN_TEXTSTYLE_UPDATE:
683         case LFUN_FONT_NOUN:
684         case LFUN_FONT_ROMAN:
685         case LFUN_FONT_SANS:
686         case LFUN_FONT_FRAK:
687         case LFUN_FONT_ITAL:
688         case LFUN_FONT_SIZE:
689         case LFUN_FONT_STATE:
690         case LFUN_FONT_UNDERLINE:
691         case LFUN_FOOTNOTE_INSERT:
692         case LFUN_HYPERLINK_INSERT:
693         case LFUN_INDEX_INSERT:
694         case LFUN_INDEX_PRINT:
695         case LFUN_INSET_INSERT:
696         case LFUN_LABEL_GOTO:
697         case LFUN_LABEL_INSERT:
698         case LFUN_LINE_INSERT:
699         case LFUN_NEWPAGE_INSERT:
700         case LFUN_LAYOUT_TABULAR:
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_NOACTION:
708         case LFUN_NOMENCL_INSERT:
709         case LFUN_NOMENCL_PRINT:
710         case LFUN_NOTE_INSERT:
711         case LFUN_NOTE_NEXT:
712         case LFUN_OPTIONAL_INSERT:
713         case LFUN_PHANTOM_INSERT:
714         case LFUN_REFERENCE_NEXT:
715         case LFUN_SERVER_GOTO_FILE_ROW:
716         case LFUN_SERVER_NOTIFY:
717         case LFUN_SERVER_SET_XY:
718         case LFUN_SPACE_INSERT:
719         case LFUN_SPECIALCHAR_INSERT:
720         case LFUN_TABULAR_INSERT:
721         case LFUN_TOC_INSERT:
722         case LFUN_WRAP_INSERT:
723                 if (layout_->isPassThru()) {
724                         flag.setEnabled(false);
725                         return true;
726                 }
727                 return InsetText::getStatus(cur, cmd, flag);
728
729         case LFUN_INSET_TOGGLE:
730                 if (cmd.argument() == "open")
731                         flag.setEnabled(status_ != Open);
732                 else if (cmd.argument() == "close")
733                         flag.setEnabled(status_ == Open);
734                 else if (cmd.argument() == "toggle" || cmd.argument().empty()) {
735                         flag.setEnabled(true);
736                         flag.setOnOff(status_ == Open);
737                 } else
738                         flag.setEnabled(false);
739                 return true;
740
741         case LFUN_LANGUAGE:
742                 flag.setEnabled(!layout_->isForceLtr());
743                 return InsetText::getStatus(cur, cmd, flag);
744
745         case LFUN_BREAK_PARAGRAPH:
746                 flag.setEnabled(layout_->isMultiPar());
747                 return true;
748
749         default:
750                 return InsetText::getStatus(cur, cmd, flag);
751         }
752 }
753
754
755 void InsetCollapsable::setLabel(docstring const & l)
756 {
757         labelstring_ = l;
758 }
759
760
761 void InsetCollapsable::setStatus(Cursor & cur, CollapseStatus status)
762 {
763         status_ = status;
764         setButtonLabel();
765         if (status_ == Collapsed) {
766                 cur.leaveInset(*this);
767                 mouse_hover_ = false;
768         }
769 }
770
771
772 docstring InsetCollapsable::floatName(
773                 string const & type, BufferParams const & bp) const
774 {
775         FloatList const & floats = bp.documentClass().floats();
776         FloatList::const_iterator it = floats[type];
777         // FIXME UNICODE
778         return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name());
779 }
780
781
782 InsetLayout::InsetDecoration InsetCollapsable::decoration() const
783 {
784         if (!layout_)
785                 return InsetLayout::CLASSIC;
786         InsetLayout::InsetDecoration const dec = layout_->decoration();
787         switch (dec) {
788         case InsetLayout::CLASSIC:
789         case InsetLayout::MINIMALISTIC:
790         case InsetLayout::CONGLOMERATE:
791                 return dec;
792         case InsetLayout::DEFAULT:
793                 break;
794         }
795         if (lyxCode() == FLEX_CODE)
796                 return InsetLayout::CONGLOMERATE;
797         return InsetLayout::CLASSIC;
798 }
799
800
801 int InsetCollapsable::latex(odocstream & os,
802                           OutputParams const & runparams) const
803 {
804         // FIXME: What should we do layout_ is 0?
805         // 1) assert
806         // 2) throw an error
807         if (!layout_)
808                 return 0;
809
810         // This implements the standard way of handling the LaTeX output of
811         // a collapsable inset, either a command or an environment. Standard 
812         // collapsable insets should not redefine this, non-standard ones may
813         // call this.
814         if (!layout_->latexname().empty()) {
815                 if (layout_->latextype() == InsetLayout::COMMAND) {
816                         // FIXME UNICODE
817                         if (runparams.moving_arg)
818                                 os << "\\protect";
819                         os << '\\' << from_utf8(layout_->latexname());
820                         if (!layout_->latexparam().empty())
821                                 os << from_utf8(layout_->latexparam());
822                         os << '{';
823                 } else if (layout_->latextype() == InsetLayout::ENVIRONMENT) {
824                         os << "%\n\\begin{" << from_utf8(layout_->latexname()) << "}\n";
825                         if (!layout_->latexparam().empty())
826                                 os << from_utf8(layout_->latexparam());
827                 }
828         }
829         OutputParams rp = runparams;
830         if (layout_->isPassThru())
831                 rp.verbatim = true;
832         if (layout_->isNeedProtect())
833                 rp.moving_arg = true;
834         int i = InsetText::latex(os, rp);
835         if (!layout_->latexname().empty()) {
836                 if (layout_->latextype() == InsetLayout::COMMAND) {
837                         os << "}";
838                 } else if (layout_->latextype() == InsetLayout::ENVIRONMENT) {
839                         os << "\n\\end{" << from_utf8(layout_->latexname()) << "}\n";
840                         i += 4;
841                 }
842         }
843         return i;
844 }
845
846
847 // FIXME It seems as if it ought to be possible to do this more simply,
848 // maybe by calling InsetText::docbook() in the middle there.
849 int InsetCollapsable::docbook(odocstream & os, OutputParams const & runparams) const
850 {
851         ParagraphList::const_iterator const beg = paragraphs().begin();
852         ParagraphList::const_iterator par = paragraphs().begin();
853         ParagraphList::const_iterator const end = paragraphs().end();
854
855         if (!undefined())
856                 sgml::openTag(os, getLayout().latexname(),
857                               par->getID(buffer(), runparams) + getLayout().latexparam());
858
859         for (; par != end; ++par) {
860                 par->simpleDocBookOnePar(buffer(), os, runparams,
861                                          outerFont(distance(beg, par),
862                                                    paragraphs()));
863         }
864
865         if (!undefined())
866                 sgml::closeTag(os, getLayout().latexname());
867
868         return 0;
869 }
870
871
872 void InsetCollapsable::validate(LaTeXFeatures & features) const
873 {
874         string const preamble = getLayout().preamble();
875         if (!preamble.empty())
876                 features.addPreambleSnippet(preamble);
877         features.require(getLayout().requires());
878         InsetText::validate(features);
879 }
880
881
882 bool InsetCollapsable::undefined() const
883 {
884         docstring const & n = getLayout().name();
885         return n.empty() || n == DocumentClass::plainInsetLayout().name();
886 }
887
888
889 docstring InsetCollapsable::contextMenu(BufferView const & bv, int x,
890         int y) const
891 {
892         if (decoration() == InsetLayout::CONGLOMERATE)
893                 return from_ascii("context-conglomerate");
894
895         if (geometry(bv) == NoButton)
896                 return from_ascii("context-collapsable");
897
898         Dimension dim = dimensionCollapsed(bv);
899         if (x < xo(bv) + dim.wid && y < yo(bv) + dim.des)
900                 return from_ascii("context-collapsable");
901
902         return InsetText::contextMenu(bv, x, y);
903 }
904
905 void InsetCollapsable::tocString(odocstream & os) const
906 {
907         if (!getLayout().isInToc())
908                 return;
909         os << text().asString(0, 1, AS_STR_LABEL | AS_STR_INSETS);
910 }
911
912
913 } // namespace lyx