]> git.lyx.org Git - lyx.git/blob - src/insets/InsetCollapsible.cpp
Amend 207eaeee9071cb
[lyx.git] / src / insets / InsetCollapsible.cpp
1 /**
2  * \file InsetCollapsible.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 "InsetCollapsible.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "BufferView.h"
20 #include "CutAndPaste.h"
21 #include "Cursor.h"
22 #include "Dimension.h"
23 #include "Format.h"
24 #include "FuncRequest.h"
25 #include "FuncStatus.h"
26 #include "InsetLayout.h"
27 #include "MetricsInfo.h"
28 #include "TextClass.h"
29 #include "TocBackend.h"
30
31 #include "frontends/FontMetrics.h"
32 #include "frontends/Painter.h"
33
34 #include "support/debug.h"
35 #include "support/docstream.h"
36 #include "support/FileName.h"
37 #include "support/gettext.h"
38 #include "support/lassert.h"
39 #include "support/Lexer.h"
40 #include "support/lstrings.h"
41 #include "support/Changer.h"
42 #include "support/TempFile.h"
43
44 using namespace std;
45
46
47 namespace lyx {
48
49 using support::Lexer;
50
51 InsetCollapsible::InsetCollapsible(Buffer * buf, InsetText::UsePlain ltype)
52         : InsetText(buf, ltype), status_(Open)
53 {
54         setDrawFrame(true);
55         setFrameColor(Color_collapsibleframe);
56 }
57
58
59 // The sole purpose of this copy constructor is to make sure
60 // that the view_ map is not copied and remains empty.
61 InsetCollapsible::InsetCollapsible(InsetCollapsible const & rhs)
62         : InsetText(rhs),
63           status_(rhs.status_),
64           labelstring_(rhs.labelstring_)
65 {
66         tempfile_.reset();
67 }
68
69
70 InsetCollapsible & InsetCollapsible::operator=(InsetCollapsible const & that)
71 {
72         if (&that == this)
73                 return *this;
74         *this = InsetCollapsible(that);
75         return *this;
76 }
77
78
79 InsetCollapsible::~InsetCollapsible()
80 {
81         map<BufferView const *, View>::iterator it = view_.begin();
82         map<BufferView const *, View>::iterator end = view_.end();
83         for (; it != end; ++it)
84                 if (it->second.mouse_hover_)
85                         it->first->clearLastInset(this);
86 }
87
88
89 InsetCollapsible::CollapseStatus InsetCollapsible::status(BufferView const & bv) const
90 {
91         if (decoration() == InsetDecoration::CONGLOMERATE)
92                 return status_;
93         return view_[&bv].auto_open_ ? Open : status_;
94 }
95
96
97 InsetCollapsible::Geometry InsetCollapsible::geometry(BufferView const & bv) const
98 {
99         switch (decoration()) {
100         case InsetDecoration::CLASSIC:
101                 if (status(bv) == Open)
102                         return view_[&bv].openinlined_ ? LeftButton : TopButton;
103                 return ButtonOnly;
104
105         case InsetDecoration::MINIMALISTIC: {
106                 return status(bv) == Open ?
107                         (tempfile_ ? LeftButton : NoButton)
108                         : ButtonOnly;
109         }
110
111         case InsetDecoration::CONGLOMERATE:
112                 return status(bv) == Open ? SubLabel : Corners ;
113
114         case InsetDecoration::DEFAULT:
115                 break; // this shouldn't happen
116         }
117
118         // dummy return value to shut down a warning,
119         // this is dead code.
120         return NoButton;
121 }
122
123
124 docstring InsetCollapsible::toolTip(BufferView const & bv, int x, int y) const
125 {
126         Dimension const dim = dimensionCollapsed(bv);
127         if (geometry(bv) == NoButton)
128                 return translateIfPossible(getLayout().labelstring());
129         if (x > xo(bv) + dim.wid || y > yo(bv) + dim.des || isOpen(bv))
130                 return docstring();
131
132         return toolTipText();
133 }
134
135
136 void InsetCollapsible::write(ostream & os) const
137 {
138         os << "status ";
139         switch (status_) {
140         case Open:
141                 os << "open";
142                 break;
143         case Collapsed:
144                 os << "collapsed";
145                 break;
146         }
147         os << "\n";
148         text().write(os);
149 }
150
151
152 void InsetCollapsible::read(Lexer & lex)
153 {
154         lex.setContext("InsetCollapsible::read");
155         string tmp_token;
156         status_ = Collapsed;
157         lex >> "status" >> tmp_token;
158         if (tmp_token == "open")
159                 status_ = Open;
160
161         InsetText::read(lex);
162         setButtonLabel();
163 }
164
165 int InsetCollapsible::topOffset(BufferView const * bv) const
166 {
167         switch (geometry(*bv)) {
168         case Corners:
169         case SubLabel:
170                 return 0;
171         default:
172                 return InsetText::topOffset(bv);
173         }
174 }
175
176 int InsetCollapsible::bottomOffset(BufferView const * bv) const
177 {
178         switch (geometry(*bv)) {
179         case Corners:
180         case SubLabel:
181                 return InsetText::bottomOffset(bv) / 4;
182         default:
183                 return InsetText::bottomOffset(bv);
184         }
185 }
186
187
188 Dimension InsetCollapsible::dimensionCollapsed(BufferView const & bv) const
189 {
190         Dimension dim;
191         FontInfo labelfont(getLabelfont());
192         labelfont.realize(sane_font);
193         int const offset = Inset::textOffset(&bv);
194         theFontMetrics(labelfont).buttonText(
195                 buttonLabel(bv), offset, dim.wid, dim.asc, dim.des);
196         // remove spacing on the right for left buttons; we also do it for
197         // TopButton (although it is not useful per se), because
198         // openinlined_ is not always set properly at this point.
199         Geometry const geom = geometry(bv);
200         if (geom == LeftButton || geom == TopButton)
201                 // this form makes a difference if offset is even
202                 dim.wid -= offset - offset / 2;
203         return dim;
204 }
205
206
207 void InsetCollapsible::metrics(MetricsInfo & mi, Dimension & dim) const
208 {
209         view_[mi.base.bv].auto_open_ = mi.base.bv->cursor().isInside(this);
210
211         BufferView const & bv = *mi.base.bv;
212
213         switch (geometry(bv)) {
214         case NoButton:
215                 InsetText::metrics(mi, dim);
216                 break;
217         case Corners:
218                 InsetText::metrics(mi, dim);
219                 break;
220         case SubLabel: {
221                 InsetText::metrics(mi, dim);
222                 // consider width of the inset label
223                 FontInfo font(getLabelfont());
224                 font.realize(sane_font);
225                 font.decSize();
226                 font.decSize();
227                 int w = 0;
228                 int a = 0;
229                 int d = 0;
230                 theFontMetrics(font).rectText(buttonLabel(bv), w, a, d);
231                 dim.des += a + d;
232                 dim.wid = max(dim.wid, w);
233                 break;
234                 }
235         case TopButton:
236         case LeftButton:
237         case ButtonOnly:
238                 if (hasFixedWidth()){
239                         int const mindim = view_[&bv].button_dim_.x2 - view_[&bv].button_dim_.x1;
240                         if (mi.base.textwidth < mindim)
241                                 mi.base.textwidth = mindim;
242                 }
243                 dim = dimensionCollapsed(bv);
244                 if (geometry(bv) == TopButton || geometry(bv) == LeftButton) {
245                         Dimension textdim;
246                         InsetText::metrics(mi, textdim);
247                         view_[&bv].openinlined_ = (textdim.wid + dim.wid) < mi.base.textwidth;
248                         if (view_[&bv].openinlined_) {
249                                 // Correct for button width but remove spacing before frame
250                                 dim.wid += textdim.wid - leftOffset(mi.base.bv) / 2;
251                                 dim.des = max(dim.des - textdim.asc + dim.asc, textdim.des);
252                                 dim.asc = textdim.asc;
253                         } else {
254                                 dim.des += textdim.height() + topOffset(mi.base.bv);
255                                 dim.wid = max(dim.wid, textdim.wid);
256                         }
257                 }
258                 break;
259         }
260 }
261
262
263 bool InsetCollapsible::setMouseHover(BufferView const * bv, bool mouse_hover)
264         const
265 {
266         view_[bv].mouse_hover_ = mouse_hover;
267         return true;
268 }
269
270
271 ColorCode InsetCollapsible::backgroundColor(PainterInfo const &) const
272 {
273         return getLayout().bgcolor();
274 }
275
276
277 ColorCode InsetCollapsible::labelColor() const
278 {
279         return getLayout().labelfont().color();
280 }
281
282
283 void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const
284 {
285         BufferView const & bv = *pi.base.bv;
286
287         view_[&bv].auto_open_ = bv.cursor().isInside(this);
288
289         Changer dummy = pi.base.font.change(getFont(), true);
290
291         // Draw button first -- top, left or only
292         Dimension dimc = dimensionCollapsed(bv);
293
294         if (geometry(bv) == TopButton ||
295             geometry(bv) == LeftButton ||
296             geometry(bv) == ButtonOnly) {
297                 view_[&bv].button_dim_.x1 = x + 0;
298                 view_[&bv].button_dim_.x2 = x + dimc.width();
299                 view_[&bv].button_dim_.y1 = y - dimc.asc;
300                 view_[&bv].button_dim_.y2 = y + dimc.des;
301
302                 FontInfo labelfont = getLabelfont();
303                 labelfont.setColor(labelColor());
304                 labelfont.realize(pi.base.font);
305                 pi.pain.buttonText(x, y, buttonLabel(bv), labelfont,
306                                    view_[&bv].mouse_hover_ ? Color_buttonhoverbg : Color_buttonbg,
307                                    Color_buttonframe, Inset::textOffset(pi.base.bv));
308                 // Draw the change tracking cue on the label, unless RowPainter already
309                 // takes care of it.
310                 if (canPaintChange(bv))
311                         pi.change.paintCue(pi, x, y, x + dimc.width(), labelfont);
312         } else {
313                 view_[&bv].button_dim_.x1 = 0;
314                 view_[&bv].button_dim_.y1 = 0;
315                 view_[&bv].button_dim_.x2 = 0;
316                 view_[&bv].button_dim_.y2 = 0;
317         }
318
319         Dimension const textdim = dimensionHelper(bv);
320         int const baseline = y;
321         int textx, texty;
322         Geometry g = geometry(bv);
323         switch (g) {
324         case LeftButton:
325         case TopButton: {
326                 if (g == LeftButton) {
327                         // correct for spacing added before the frame in
328                         // InsetText::draw. We want the button to touch the frame.
329                         textx = x + dimc.width() - leftOffset(pi.base.bv) / 2;
330                         texty = baseline;
331                 } else {
332                         textx = x;
333                         texty = baseline + dimc.des + textdim.asc;
334                 }
335                 // Do not draw the cue for INSERTED -- it is already in the button and
336                 // that's enough.
337                 Changer cdummy = (pi.change.type == Change::INSERTED)
338                         ? changeVar(pi.change, Change())
339                         : noChange();
340                 InsetText::draw(pi, textx, texty);
341                 break;
342         }
343         case ButtonOnly:
344                 break;
345         case NoButton:
346                 textx = x;
347                 texty = baseline;
348                 InsetText::draw(pi, textx, texty);
349                 break;
350         case SubLabel:
351         case Corners:
352                 textx = x;
353                 texty = baseline;
354                 // We will take care of the frame and the change tracking cue
355                 // ourselves, below.
356                 {
357                         Changer cdummy = changeVar(pi.change, Change());
358                         const_cast<InsetCollapsible *>(this)->setDrawFrame(false);
359                         InsetText::draw(pi, textx, texty);
360                         const_cast<InsetCollapsible *>(this)->setDrawFrame(true);
361                 }
362
363                 int desc = textdim.descent();
364
365                 // Colour the frame according to the change type. (Like for tables.)
366                 Color colour = pi.change.changed() ? pi.change.color()
367                                                     : Color_foreground;
368                 const int xx1 = x + leftOffset(pi.base.bv) - 1;
369                 const int xx2 = x + textdim.wid - rightOffset(pi.base.bv) + 1;
370                 pi.pain.line(xx1, y + desc - 4,
371                              xx1, y + desc, colour);
372                 if (status_ == Open)
373                         pi.pain.line(xx1, y + desc,
374                                      xx2, y + desc, colour);
375                 else {
376                         // Make status_ value visible:
377                         pi.pain.line(xx1, y + desc,
378                                      xx1 + 4, y + desc, colour);
379                         pi.pain.line(xx2 - 4, y + desc,
380                                      xx2, y + desc, colour);
381                 }
382                 pi.pain.line(x + textdim.wid - 3, y + desc, x + textdim.wid - 3,
383                              y + desc - 4, colour);
384
385                 // the label below the text. Can be toggled.
386                 if (g == SubLabel) {
387                         FontInfo font(getLabelfont());
388                         if (pi.change.changed())
389                                 font.setPaintColor(colour);
390                         font.realize(sane_font);
391                         font.decSize();
392                         font.decSize();
393                         int w = 0;
394                         int a = 0;
395                         int d = 0;
396                         Color const col = pi.full_repaint ? Color_none : pi.backgroundColor();
397                         theFontMetrics(font).rectText(buttonLabel(bv), w, a, d);
398                         int const ww = max(textdim.wid, w);
399                         pi.pain.rectText(x + (ww - w) / 2, y + desc + a,
400                                          buttonLabel(bv), font, col, Color_none);
401                 }
402
403                 int const y1 = y - textdim.asc + 3;
404                 // a visual cue when the cursor is inside the inset
405                 Cursor const & cur = bv.cursor();
406                 if (cur.isInside(this)) {
407                         pi.pain.line(xx1, y1 + 4, xx1, y1, colour);
408                         pi.pain.line(xx1 + 4, y1, xx1, y1, colour);
409                         pi.pain.line(xx2, y1 + 4, xx2, y1, colour);
410                         pi.pain.line(xx2 - 4, y1, xx2, y1, colour);
411                 }
412                 // Strike through the inset if deleted and not already handled by
413                 // RowPainter.
414                 if (pi.change.deleted() && canPaintChange(bv))
415                         pi.change.paintCue(pi, xx1, y1, xx2, y + desc);
416                 break;
417         }
418 }
419
420
421 void InsetCollapsible::cursorPos(BufferView const & bv,
422                 CursorSlice const & sl, bool boundary, int & x, int & y) const
423 {
424         if (geometry(bv) == ButtonOnly)
425                 status_ = Open;
426
427         InsetText::cursorPos(bv, sl, boundary, x, y);
428         Dimension const textdim = dimensionHelper(bv);
429
430         switch (geometry(bv)) {
431         case LeftButton:
432                 x += dimensionCollapsed(bv).wid - leftOffset(&bv) / 2;
433                 break;
434         case TopButton: {
435                 y += dimensionCollapsed(bv).des + textdim.asc;
436                 break;
437         }
438         case NoButton:
439         case SubLabel:
440         case Corners:
441                 // Do nothing
442                 break;
443         case ButtonOnly:
444                 // Cannot get here
445                 break;
446         }
447 }
448
449
450 bool InsetCollapsible::editable() const
451 {
452         if (tempfile_)
453                 return false;
454         
455         switch (decoration()) {
456         case InsetDecoration::CLASSIC:
457         case InsetDecoration::MINIMALISTIC:
458                 return status_ == Open;
459         default:
460                 return true;
461         }
462 }
463
464
465 bool InsetCollapsible::descendable(BufferView const & bv) const
466 {
467         if (tempfile_)
468                 return false;
469
470         return geometry(bv) != ButtonOnly;
471 }
472
473
474 bool InsetCollapsible::clickable(BufferView const & bv, int x, int y) const
475 {
476         return view_[&bv].button_dim_.contains(x, y);
477 }
478
479
480 docstring const InsetCollapsible::getNewLabel(docstring const & l) const
481 {
482         odocstringstream label;
483         pos_type const max_length = 15;
484         pos_type const p_siz = paragraphs().begin()->size();
485         pos_type const n = min(max_length, p_siz);
486         pos_type i = 0;
487         pos_type j = 0;
488         for (; i < n && j < p_siz; ++j) {
489                 if (paragraphs().begin()->isDeleted(j))
490                         continue;
491                 if (paragraphs().begin()->isInset(j)) {
492                         if (!paragraphs().begin()->getInset(j)->isChar())
493                                 continue;
494                         paragraphs().begin()->getInset(j)->toString(label);
495                 } else
496                         label.put(paragraphs().begin()->getChar(j));
497                 ++i;
498         }
499         if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) {
500                 label << "...";
501         }
502         return label.str().empty() ? l : label.str();
503 }
504
505
506 void InsetCollapsible::edit(Cursor & cur, bool front, EntryDirection entry_from)
507 {
508         //lyxerr << "InsetCollapsible: edit left/right" << endl;
509         // We might have a selection if we moved the mouse on the button only
510         cur.clearSelection();
511         cur.push(*this);
512         InsetText::edit(cur, front, entry_from);
513 }
514
515
516 Inset * InsetCollapsible::editXY(Cursor & cur, int x, int y)
517 {
518         //lyxerr << "InsetCollapsible: edit xy" << endl;
519         if (geometry(cur.bv()) == ButtonOnly
520                 || !descendable(cur.bv())
521             || (view_[&cur.bv()].button_dim_.contains(x, y)
522                 && geometry(cur.bv()) != NoButton))
523                 return this;
524         cur.push(*this);
525         return InsetText::editXY(cur, x, y);
526 }
527
528
529 void InsetCollapsible::doDispatch(Cursor & cur, FuncRequest & cmd)
530 {
531         //lyxerr << "InsetCollapsible::doDispatch (begin): cmd: " << cmd
532         //      << " cur: " << cur << " bvcur: " << cur.bv().cursor() << endl;
533
534         bool const hitButton = clickable(cur.bv(), cmd.x(), cmd.y());
535
536         switch (cmd.action()) {
537         case LFUN_MOUSE_PRESS:
538                 if (hitButton) {
539                         switch (cmd.button()) {
540                         case mouse_button::button1:
541                         case mouse_button::button3:
542                                 // Pass the command to the enclosing InsetText,
543                                 // so that the cursor gets set.
544                                 cur.undispatched();
545                                 break;
546                         case mouse_button::none:
547                         case mouse_button::button2:
548                         case mouse_button::button4:
549                         case mouse_button::button5:
550                                 // Nothing to do.
551                                 cur.noScreenUpdate();
552                                 break;
553                         }
554                 } else if (geometry(cur.bv()) != ButtonOnly)
555                         InsetText::doDispatch(cur, cmd);
556                 else
557                         cur.undispatched();
558                 break;
559
560         case LFUN_MOUSE_DOUBLE:
561         case LFUN_MOUSE_TRIPLE:
562                 if (hitButton)
563                         cur.noScreenUpdate();
564                 else if (geometry(cur.bv()) != ButtonOnly)
565                         InsetText::doDispatch(cur, cmd);
566                 else
567                         cur.undispatched();
568                 break;
569
570         case LFUN_MOUSE_RELEASE:
571                 if (!hitButton) {
572                         // The mouse click has to be within the inset!
573                         if (geometry(cur.bv()) != ButtonOnly)
574                                 InsetText::doDispatch(cur, cmd);
575                         else
576                                 cur.undispatched();
577                         break;
578                 }
579                 if (cmd.button() != mouse_button::button1) {
580                         // Nothing to do.
581                         cur.noScreenUpdate();
582                         break;
583                 }
584                 // If we are selecting, we do not want to toggle the inset
585                 // except if the selection started at the inset button we're still on.
586                 // The latter addresses #12820.
587                 if (cur.selection() && !clickable(cur.bv(), cur.bv().cursor().xClickPos(),
588                                                   cur.bv().cursor().yClickPos()))
589                         break;
590                 // Left button is clicked, the user asks to
591                 // toggle the inset visual state.
592                 cur.dispatched();
593                 cur.screenUpdateFlags(Update::Force | Update::FitCursor);
594                 if (geometry(cur.bv()) == ButtonOnly) {
595                         setStatus(cur, Open);
596                         edit(cur, true);
597                 }
598                 else
599                         setStatus(cur, Collapsed);
600                 cur.bv().cursor() = cur;
601                 break;
602
603         case LFUN_INSET_TOGGLE:
604                 if (cmd.argument() == "open")
605                         setStatus(cur, Open);
606                 else if (cmd.argument() == "close")
607                         setStatus(cur, Collapsed);
608                 else if (cmd.argument() == "toggle" || cmd.argument().empty())
609                         if (status_ == Open)
610                                 setStatus(cur, Collapsed);
611                         else
612                                 setStatus(cur, Open);
613                 else // if assign or anything else
614                         cur.undispatched();
615                 cur.dispatched();
616                 break;
617
618         case LFUN_INSET_EDIT: {
619                 cur.push(*this);
620                 text().selectAll(cur);
621                 string const format =
622                         cur.buffer()->params().documentClass().outputFormat();
623                 string const ext = theFormats().extension(format);
624                 tempfile_.reset(new support::TempFile("ert_editXXXXXX." + ext));
625                 support::FileName const tempfilename = tempfile_->name();
626                 string const name = tempfilename.toFilesystemEncoding();
627                 ofdocstream os(name.c_str());
628                 os << cur.selectionAsString(false);
629                 os.close();
630                 // Since we lock the inset while the external file is edited,
631                 // we need to move the cursor outside and clear any selection inside
632                 cur.clearSelection();
633                 cur.pop();
634                 cur.leaveInset(*this);
635
636                 if (cmd.argument() == "nogui")
637                         cur.message(from_utf8(name));
638                 else
639                         theFormats().edit(buffer(), tempfilename, format);
640
641                 break;
642         }
643         case LFUN_INSET_END_EDIT: {
644                 support::FileName const tempfilename = tempfile_->name();
645                 docstring const s = tempfilename.fileContents("UTF-8");
646                 cur.recordUndoInset(this);
647                 cur.push(*this);
648                 text().selectAll(cur);
649                 cap::replaceSelection(cur);
650                 cur.text()->insertStringAsLines(cur, s, cur.current_font);
651                 // FIXME (gb) it crashes without this
652                 cur.fixIfBroken();
653                 tempfile_.reset();
654                 cur.pop();
655                 break;
656         }
657
658         default:
659                 InsetText::doDispatch(cur, cmd);
660                 break;
661         }
662 }
663
664
665 bool InsetCollapsible::getStatus(Cursor & cur, FuncRequest const & cmd,
666                 FuncStatus & flag) const
667 {
668         switch (cmd.action()) {
669         case LFUN_INSET_TOGGLE:
670                 if (cmd.argument() == "open")
671                         flag.setEnabled(status_ != Open);
672                 else if (cmd.argument() == "close")
673                         flag.setEnabled(status_ == Open);
674                 else if (cmd.argument() == "toggle" || cmd.argument().empty()) {
675                         flag.setEnabled(true);
676                         flag.setOnOff(status_ == Open);
677                 } else
678                         flag.setEnabled(false);
679                 return true;
680
681         case LFUN_INSET_EDIT:
682                 flag.setEnabled(!buffer().hasReadonlyFlag() &&
683                         getLayout().editExternally() && tempfile_ == nullptr);
684                 return true;
685
686         case LFUN_INSET_END_EDIT:
687                 flag.setEnabled(!buffer().hasReadonlyFlag() &&
688                         getLayout().editExternally() && tempfile_ != nullptr);
689                 return true;
690
691         default:
692                 return InsetText::getStatus(cur, cmd, flag);
693         }
694 }
695
696
697 void InsetCollapsible::setLabel(docstring const & l)
698 {
699         labelstring_ = l;
700 }
701
702
703 docstring InsetCollapsible::getLabel() const
704 {
705         InsetLayout const & il = getLayout();
706         return labelstring_.empty() ?
707                 translateIfPossible(il.labelstring()) : labelstring_;
708 }
709
710
711 docstring const InsetCollapsible::buttonLabel(BufferView const & bv) const
712 {
713         // U+1F512 LOCK
714         docstring const locked = tempfile_ ? docstring(1, 0x1F512) : docstring();
715         // indicate changed content in label (#8645)
716         // ✎ U+270E LOWER RIGHT PENCIL
717         docstring const indicator = (isChanged() && geometry(bv) == ButtonOnly)
718                 ? docstring(1, 0x270E) : docstring();
719         InsetLayout const & il = getLayout();
720         docstring const label = getLabel();
721         if (!il.contentaslabel() || geometry(bv) != ButtonOnly)
722                 return locked + indicator + label;
723         return locked + indicator + getNewLabel(label);
724 }
725
726
727 void InsetCollapsible::setStatus(Cursor & cur, CollapseStatus status)
728 {
729         status_ = status;
730         setButtonLabel();
731         if (status_ == Collapsed) {
732                 cur.leaveInset(*this);
733                 // if cursor was inside the inset, it was now moved outside (#12830)
734                 cur.setCurrentFont();
735         }
736 }
737
738
739 InsetDecoration InsetCollapsible::decoration() const
740 {
741         InsetDecoration const dec = getLayout().decoration();
742         return dec == InsetDecoration::DEFAULT ? InsetDecoration::CLASSIC : dec;
743 }
744
745
746 string InsetCollapsible::contextMenu(BufferView const & bv, int x,
747         int y) const
748 {
749         string context_menu = contextMenuName();
750         string const it_context_menu = InsetText::contextMenuName();
751         if (decoration() == InsetDecoration::CONGLOMERATE)
752                 return context_menu + ";" + it_context_menu;
753
754         string const ic_context_menu = InsetCollapsible::contextMenuName();
755         if (ic_context_menu != context_menu)
756                 context_menu += ";" + ic_context_menu;
757
758         if (geometry(bv) == NoButton)
759                 return context_menu + ";" + it_context_menu;
760
761         Dimension dim = dimensionCollapsed(bv);
762         if (x < xo(bv) + dim.wid && y < yo(bv) + dim.des)
763                 return context_menu;
764
765         return it_context_menu;
766 }
767
768
769 string InsetCollapsible::contextMenuName() const
770 {
771         if (decoration() == InsetDecoration::CONGLOMERATE)
772                 return "context-conglomerate";
773         else
774                 return "context-collapsible";
775 }
776
777
778 bool InsetCollapsible::canPaintChange(BufferView const & bv) const
779 {
780         // return false to let RowPainter draw the change tracking cue consistently
781         // with the surrounding text, when the inset is inline: for buttons, for
782         // non-allowMultiPar insets.
783         switch (geometry(bv)) {
784         case Corners:
785         case SubLabel:
786                 return allowMultiPar();
787         case ButtonOnly:
788                 return false;
789         default:
790                 break;
791         }
792         return true;
793 }
794
795
796 void InsetCollapsible::addToToc(DocIterator const & cpit, bool output_active,
797                                 UpdateType utype, TocBackend & backend) const
798 {
799         bool doing_output = output_active && producesOutput();
800         InsetLayout const & layout = getLayout();
801         if (!layout.addToToc())
802                 return InsetText::addToToc(cpit, doing_output, utype, backend);
803
804         TocBuilder & b = backend.builder(layout.tocType());
805         // Cursor inside the inset
806         DocIterator pit = cpit;
807         pit.push_back(CursorSlice(const_cast<InsetCollapsible &>(*this)));
808         docstring const label = getLabel();
809         b.pushItem(pit, label + (label.empty() ? "" : ": "), output_active);
810         // Proceed with the rest of the inset.
811         InsetText::addToToc(cpit, doing_output, utype, backend);
812         if (layout.isTocCaption()) {
813                 docstring str;
814                 text().forOutliner(str, TOC_ENTRY_LENGTH);
815                 b.argumentItem(str);
816         }
817         b.pop();
818 }
819
820
821
822 } // namespace lyx