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