2 * \file InsetCollapsable.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Alejandro Aguilar Sierra
8 * \author Lars Gullik Bjønnes
10 * Full author contact details are available in file CREDITS.
15 #include "InsetCollapsable.h"
18 #include "BufferParams.h"
19 #include "BufferView.h"
22 #include "DispatchResult.h"
23 #include "FloatList.h"
24 #include "FuncStatus.h"
27 #include "LaTeXFeatures.h"
29 #include "FuncRequest.h"
30 #include "MetricsInfo.h"
31 #include "ParagraphParameters.h"
33 #include "frontends/FontMetrics.h"
34 #include "frontends/Painter.h"
45 InsetCollapsable::CollapseStatus InsetCollapsable::status() const
47 return autoOpen_ ? Open : status_;
51 InsetCollapsable::Geometry InsetCollapsable::geometry() const
53 switch (decoration()) {
55 if (status() == Open) {
64 return status() == Open ? NoButton : ButtonOnly ;
67 return status() == Open ? SubLabel : Corners ;
70 // dummy return value to shut down a warning,
76 InsetCollapsable::InsetCollapsable
77 (BufferParams const & bp, CollapseStatus status)
78 : InsetText(bp), status_(status),
79 openinlined_(false), autoOpen_(false), mouse_hover_(false)
81 setAutoBreakRows(true);
83 setFrameColor(Color_collapsableframe);
89 InsetCollapsable::InsetCollapsable(InsetCollapsable const & rhs)
91 button_dim(rhs.button_dim),
93 topbaseline(rhs.topbaseline),
96 openinlined_(rhs.openinlined_),
97 autoOpen_(rhs.autoOpen_),
98 // the sole purpose of this copy constructor
104 void InsetCollapsable::setLayout(BufferParams const & bp)
106 // Fallback for lacking inset layout item
107 layout_.bgcolor = Color_background;
109 // FIXME: it seems the default background is red!
110 layout_ = getLayout(bp);
113 // FIXME: it seems the provided font is partly realized... so we
114 // re-initialize the label font in any case.
116 if (layout_.labelfont != inherit_font)
120 // FIXME: it seems some insets don't properly initialise that...
121 layout_.labelfont = sane_font;
122 layout_.labelfont.decSize();
123 layout_.labelfont.decSize();
124 layout_.labelfont.setColor(Color_collapsable);
128 void InsetCollapsable::write(Buffer const & buf, ostream & os) const
140 text_.write(buf, os);
144 void InsetCollapsable::read(Buffer const & buf, Lexer & lex)
146 bool token_found = false;
149 string const token = lex.getString();
150 if (token == "status") {
152 string const tmp_token = lex.getString();
154 if (tmp_token == "collapsed") {
157 } else if (tmp_token == "open") {
161 lyxerr << "InsetCollapsable::read: Missing status!"
163 // Take countermeasures
164 lex.pushToken(token);
167 lyxerr << "InsetCollapsable::read: Missing 'status'-tag!"
169 // take countermeasures
170 lex.pushToken(token);
173 InsetText::read(buf, lex);
176 status_ = isOpen() ? Open : Collapsed;
179 setLayout(buf.params());
181 // Force default font, if so requested
182 // This avoids paragraphs in buffer language that would have a
183 // foreign language after a document language change, and it ensures
184 // that all new text in ERT and similar gets the "latex" language,
185 // since new text inherits the language from the last position of the
186 // existing text. As a side effect this makes us also robust against
187 // bugs in LyX that might lead to font changes in ERT in .lyx files.
188 resetParagraphsFont();
192 Dimension InsetCollapsable::dimensionCollapsed() const
195 theFontMetrics(layout_.labelfont).buttonText(
196 layout_.labelstring, dim.wid, dim.asc, dim.des);
201 void InsetCollapsable::metrics(MetricsInfo & mi, Dimension & dim) const
203 autoOpen_ = mi.base.bv->cursor().isInside(this);
205 FontInfo tmpfont = mi.base.font;
206 getDrawFont(mi.base.font);
207 mi.base.font.realize(tmpfont);
209 switch (geometry()) {
211 InsetText::metrics(mi, dim);
214 InsetText::metrics(mi, dim);
219 InsetText::metrics(mi, dim);
220 // consider width of the inset label
221 FontInfo font(layout_.labelfont);
222 font.realize(sane_font);
228 docstring s = layout_.labelstring;
229 theFontMetrics(font).rectText(s, w, a, d);
236 dim = dimensionCollapsed();
237 if (geometry() == TopButton
238 || geometry() == LeftButton) {
240 InsetText::metrics(mi, textdim);
241 openinlined_ = (textdim.wid + dim.wid) < mi.base.textwidth;
243 // Correct for button width.
244 dim.wid += textdim.wid;
245 dim.des = max(dim.des - textdim.asc + dim.asc, textdim.des);
246 dim.asc = textdim.asc;
248 dim.des += textdim.height() + TEXT_TO_INSET_OFFSET;
249 dim.wid = max(dim.wid, textdim.wid);
255 mi.base.font = tmpfont;
259 bool InsetCollapsable::setMouseHover(bool mouse_hover)
261 mouse_hover_ = mouse_hover;
266 void InsetCollapsable::draw(PainterInfo & pi, int x, int y) const
268 autoOpen_ = pi.base.bv->cursor().isInside(this);
269 ColorCode const old_color = pi.background_color;
270 pi.background_color = backgroundColor();
272 FontInfo tmpfont = pi.base.font;
273 getDrawFont(pi.base.font);
274 pi.base.font.realize(tmpfont);
276 // Draw button first -- top, left or only
277 Dimension dimc = dimensionCollapsed();
279 if (geometry() == TopButton ||
280 geometry() == LeftButton ||
281 geometry() == ButtonOnly) {
282 button_dim.x1 = x + 0;
283 button_dim.x2 = x + dimc.width();
284 button_dim.y1 = y - dimc.asc;
285 button_dim.y2 = y + dimc.des;
287 pi.pain.buttonText(x, y, layout_.labelstring, layout_.labelfont, mouse_hover_);
295 Dimension const textdim = InsetText::dimension(*pi.base.bv);
296 int const baseline = y;
298 switch (geometry()) {
300 textx = x + dimc.width();
302 InsetText::draw(pi, textx, texty);
306 texty = baseline + dimc.des + textdim.asc;
307 InsetText::draw(pi, textx, texty);
314 InsetText::draw(pi, textx, texty);
320 const_cast<InsetCollapsable *>(this)->setDrawFrame(false);
321 InsetText::draw(pi, textx, texty);
322 const_cast<InsetCollapsable *>(this)->setDrawFrame(true);
324 int desc = textdim.descent();
325 if (geometry() == Corners)
328 const int xx1 = x + TEXT_TO_INSET_OFFSET - 1;
329 const int xx2 = x + textdim.wid - TEXT_TO_INSET_OFFSET + 1;
330 pi.pain.line(xx1, y + desc - 4,
332 layout_.labelfont.color());
333 if (internalStatus() == Open)
334 pi.pain.line(xx1, y + desc,
336 layout_.labelfont.color());
338 // Make status_ value visible:
339 pi.pain.line(xx1, y + desc,
341 layout_.labelfont.color());
342 pi.pain.line(xx2 - 4, y + desc,
344 layout_.labelfont.color());
346 pi.pain.line(x + textdim.wid - 3, y + desc, x + textdim.wid - 3, y + desc - 4,
347 layout_.labelfont.color());
349 // the label below the text. Can be toggled.
350 if (geometry() == SubLabel) {
351 FontInfo font(layout_.labelfont);
352 font.realize(sane_font);
358 docstring s = layout_.labelstring;
359 theFontMetrics(font).rectText(s, w, a, d);
360 int const ww = max(textdim.wid, w);
361 pi.pain.rectText(x + (ww - w) / 2, y + desc + a,
362 s, font, Color_none, Color_none);
366 // a visual cue when the cursor is inside the inset
367 Cursor & cur = pi.base.bv->cursor();
368 if (cur.isInside(this)) {
371 pi.pain.line(xx1, y + 4, xx1, y, layout_.labelfont.color());
372 pi.pain.line(xx1 + 4, y, xx1, y, layout_.labelfont.color());
373 pi.pain.line(xx2, y + 4, xx2, y,
374 layout_.labelfont.color());
375 pi.pain.line(xx2 - 4, y, xx2, y,
376 layout_.labelfont.color());
380 pi.background_color = old_color;
382 pi.base.font = tmpfont;
386 void InsetCollapsable::cursorPos(BufferView const & bv,
387 CursorSlice const & sl, bool boundary, int & x, int & y) const
389 if (geometry() == ButtonOnly)
391 BOOST_ASSERT(geometry() != ButtonOnly);
393 InsetText::cursorPos(bv, sl, boundary, x, y);
394 Dimension const textdim = InsetText::dimension(bv);
396 switch (geometry()) {
398 x += dimensionCollapsed().wid;
401 y += dimensionCollapsed().des + textdim.asc;
416 Inset::EDITABLE InsetCollapsable::editable() const
418 return geometry() != ButtonOnly? HIGHLY_EDITABLE : IS_EDITABLE;
422 bool InsetCollapsable::descendable() const
424 return geometry() != ButtonOnly;
428 bool InsetCollapsable::hitButton(FuncRequest const & cmd) const
430 return button_dim.contains(cmd.x, cmd.y);
434 docstring const InsetCollapsable::getNewLabel(docstring const & l) const
437 pos_type const max_length = 15;
438 pos_type const p_siz = paragraphs().begin()->size();
439 pos_type const n = std::min(max_length, p_siz);
442 for (; i < n && j < p_siz; ++j) {
443 if (paragraphs().begin()->isInset(j))
445 label += paragraphs().begin()->getChar(j);
448 if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) {
451 return label.empty() ? l : label;
455 void InsetCollapsable::edit(Cursor & cur, bool left)
457 //lyxerr << "InsetCollapsable: edit left/right" << endl;
459 InsetText::edit(cur, left);
463 Inset * InsetCollapsable::editXY(Cursor & cur, int x, int y)
465 //lyxerr << "InsetCollapsable: edit xy" << endl;
466 if (geometry() == ButtonOnly
467 || (button_dim.contains(x, y)
468 && geometry() != NoButton))
471 return InsetText::editXY(cur, x, y);
475 void InsetCollapsable::doDispatch(Cursor & cur, FuncRequest & cmd)
477 //lyxerr << "InsetCollapsable::doDispatch (begin): cmd: " << cmd
478 // << " cur: " << cur << " bvcur: " << cur.bv().cursor() << endl;
480 switch (cmd.action) {
481 case LFUN_MOUSE_PRESS:
482 if (cmd.button() == mouse_button::button1
484 && geometry() != NoButton) {
485 // reset selection if necessary (see bug 3060)
487 cur.bv().cursor().clearSelection();
493 if (geometry() == NoButton)
494 InsetText::doDispatch(cur, cmd);
495 else if (geometry() != ButtonOnly
497 InsetText::doDispatch(cur, cmd);
502 case LFUN_MOUSE_MOTION:
503 case LFUN_MOUSE_DOUBLE:
504 case LFUN_MOUSE_TRIPLE:
505 if (geometry() == NoButton)
506 InsetText::doDispatch(cur, cmd);
507 else if (geometry() != ButtonOnly
509 InsetText::doDispatch(cur, cmd);
514 case LFUN_MOUSE_RELEASE:
515 if (cmd.button() == mouse_button::button3) {
516 // There is no button to right click:
517 if (geometry() == Corners ||
518 geometry() == SubLabel ||
519 geometry() == NoButton) {
520 if (internalStatus() == Open)
521 setStatus(cur, Collapsed);
523 setStatus(cur, Open);
527 // configuration dialog
528 showInsetDialog(&cur.bv());
533 if (geometry() == NoButton) {
534 // The mouse click has to be within the inset!
535 InsetText::doDispatch(cur, cmd);
539 if (cmd.button() == mouse_button::button1 && hitButton(cmd)) {
540 // if we are selecting, we do not want to
544 // Left button is clicked, the user asks to
545 // toggle the inset visual state.
547 cur.updateFlags(Update::Force | Update::FitCursor);
548 if (geometry() == ButtonOnly) {
549 setStatus(cur, Open);
553 setStatus(cur, Collapsed);
555 cur.bv().cursor() = cur;
559 // The mouse click is within the opened inset.
560 if (geometry() == TopButton
561 || geometry() == LeftButton)
562 InsetText::doDispatch(cur, cmd);
565 case LFUN_INSET_TOGGLE:
566 if (cmd.argument() == "open")
567 setStatus(cur, Open);
568 else if (cmd.argument() == "close")
569 setStatus(cur, Collapsed);
570 else if (cmd.argument() == "toggle" || cmd.argument().empty())
571 if (internalStatus() == Open) {
572 setStatus(cur, Collapsed);
573 if (geometry() == ButtonOnly)
574 cur.top().forwardPos();
576 setStatus(cur, Open);
577 else // if assign or anything else
583 case LFUN_CLIPBOARD_PASTE:
584 case LFUN_PRIMARY_SELECTION_PASTE: {
585 InsetText::doDispatch(cur, cmd);
586 // Since we can only store plain text, we must reset all
588 // FIXME: Change only the pasted paragraphs
590 resetParagraphsFont();
595 if (layout_.forceltr) {
596 // Force any new text to latex_language
597 // FIXME: This should only be necessary in constructor, but
598 // new paragraphs that are created by pressing enter at the
599 // start of an existing paragraph get the buffer language
600 // and not latex_language, so we take this brute force
602 cur.current_font.setLanguage(latex_language);
603 cur.real_current_font.setLanguage(latex_language);
605 InsetText::doDispatch(cur, cmd);
611 bool InsetCollapsable::allowMultiPar() const
613 return layout_.multipar;
617 void InsetCollapsable::resetParagraphsFont()
620 font.fontInfo() = sane_font;
621 if (layout_.forceltr)
622 font.setLanguage(latex_language);
623 if (layout_.passthru) {
624 ParagraphList::iterator par = paragraphs().begin();
625 ParagraphList::iterator const end = paragraphs().end();
627 par->resetFonts(font);
628 par->params().clear();
635 void InsetCollapsable::getDrawFont(FontInfo & font) const
641 bool InsetCollapsable::getStatus(Cursor & cur, FuncRequest const & cmd,
642 FuncStatus & flag) const
644 switch (cmd.action) {
646 case LFUN_ACCENT_ACUTE:
647 case LFUN_ACCENT_BREVE:
648 case LFUN_ACCENT_CARON:
649 case LFUN_ACCENT_CEDILLA:
650 case LFUN_ACCENT_CIRCLE:
651 case LFUN_ACCENT_CIRCUMFLEX:
652 case LFUN_ACCENT_DOT:
653 case LFUN_ACCENT_GRAVE:
654 case LFUN_ACCENT_HUNGARIAN_UMLAUT:
655 case LFUN_ACCENT_MACRON:
656 case LFUN_ACCENT_OGONEK:
657 case LFUN_ACCENT_SPECIAL_CARON:
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:
664 case LFUN_BIBITEM_INSERT:
665 case LFUN_BOX_INSERT:
666 case LFUN_BRANCH_INSERT:
667 case LFUN_BREAK_LINE:
668 case LFUN_CAPTION_INSERT:
669 case LFUN_CLEARPAGE_INSERT:
670 case LFUN_CLEARDOUBLEPAGE_INSERT:
671 case LFUN_DEPTH_DECREMENT:
672 case LFUN_DEPTH_INCREMENT:
673 case LFUN_DOTS_INSERT:
674 case LFUN_END_OF_SENTENCE_PERIOD_INSERT:
675 case LFUN_ENVIRONMENT_INSERT:
676 case LFUN_ERT_INSERT:
677 case LFUN_FILE_INSERT:
678 case LFUN_FLEX_INSERT:
679 case LFUN_FLOAT_INSERT:
680 case LFUN_FLOAT_LIST:
681 case LFUN_FLOAT_WIDE_INSERT:
683 case LFUN_FONT_TYPEWRITER:
684 case LFUN_FONT_DEFAULT:
686 case LFUN_FONT_FREE_APPLY:
687 case LFUN_FONT_FREE_UPDATE:
689 case LFUN_FONT_ROMAN:
694 case LFUN_FONT_STATE:
695 case LFUN_FONT_UNDERLINE:
696 case LFUN_FOOTNOTE_INSERT:
697 case LFUN_HFILL_INSERT:
698 case LFUN_HYPERLINK_INSERT:
699 case LFUN_HYPHENATION_POINT_INSERT:
700 case LFUN_INDEX_INSERT:
701 case LFUN_INDEX_PRINT:
702 case LFUN_INSET_INSERT:
703 case LFUN_LABEL_GOTO:
704 case LFUN_LABEL_INSERT:
705 case LFUN_LIGATURE_BREAK_INSERT:
706 case LFUN_LINE_INSERT:
707 case LFUN_PAGEBREAK_INSERT:
709 case LFUN_LAYOUT_PARAGRAPH:
710 case LFUN_LAYOUT_TABULAR:
711 case LFUN_MARGINALNOTE_INSERT:
712 case LFUN_MATH_DISPLAY:
713 case LFUN_MATH_INSERT:
714 case LFUN_MATH_MATRIX:
717 case LFUN_MENU_SEPARATOR_INSERT:
719 case LFUN_NOMENCL_INSERT:
720 case LFUN_NOMENCL_PRINT:
721 case LFUN_NOTE_INSERT:
723 case LFUN_OPTIONAL_INSERT:
724 case LFUN_PARAGRAPH_PARAMS:
725 case LFUN_PARAGRAPH_PARAMS_APPLY:
726 case LFUN_PARAGRAPH_SPACING:
727 case LFUN_PARAGRAPH_UPDATE:
728 case LFUN_REFERENCE_NEXT:
729 case LFUN_SERVER_GOTO_FILE_ROW:
730 case LFUN_SERVER_NOTIFY:
731 case LFUN_SERVER_SET_XY:
732 case LFUN_SPACE_INSERT:
733 case LFUN_TABULAR_INSERT:
734 case LFUN_TOC_INSERT:
735 case LFUN_WRAP_INSERT:
736 if (layout_.passthru) {
740 return InsetText::getStatus(cur, cmd, flag);
742 case LFUN_INSET_TOGGLE:
743 if (cmd.argument() == "open" || cmd.argument() == "close" ||
744 cmd.argument() == "toggle")
751 flag.enabled(!layout_.forceltr);
752 return InsetText::getStatus(cur, cmd, flag);
754 case LFUN_BREAK_PARAGRAPH:
755 case LFUN_BREAK_PARAGRAPH_SKIP:
756 flag.enabled(layout_.multipar);
760 return InsetText::getStatus(cur, cmd, flag);
765 void InsetCollapsable::setLabel(docstring const & l)
767 layout_.labelstring = l;
771 void InsetCollapsable::setStatus(Cursor & cur, CollapseStatus status)
775 if (status_ == Collapsed)
776 cur.leaveInset(*this);
780 void InsetCollapsable::setLabelFont(FontInfo const & font)
782 layout_.labelfont = font;
786 void InsetCollapsable::setLabelColor(ColorCode code)
788 layout_.labelfont.setColor(code);
792 docstring InsetCollapsable::floatName(string const & type, BufferParams const & bp) const
794 FloatList const & floats = bp.getTextClass().floats();
795 FloatList::const_iterator it = floats[type];
797 return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name());
801 InsetCollapsable::Decoration InsetCollapsable::decoration() const
803 if (layout_.decoration == "classic")
805 if (layout_.decoration == "minimalistic")
807 if (layout_.decoration == "conglomerate")
809 if (name() == from_ascii("Flex"))
815 int InsetCollapsable::latex(Buffer const & buf, odocstream & os,
816 OutputParams const & runparams) const
818 // This implements the standard way of handling the LaTeX output of
819 // a collapsable inset, either a command or an environment. Standard
820 // collapsable insets should not redefine this, non-standard ones may
822 if (!layout_.latexname.empty()) {
823 if (layout_.latextype == "command") {
825 if (runparams.moving_arg)
827 os << '\\' << from_utf8(layout_.latexname);
828 if (!layout_.latexparam.empty())
829 os << from_utf8(layout_.latexparam);
831 } else if (layout_.latextype == "environment") {
832 os << "%\n\\begin{" << from_utf8(layout_.latexname) << "}\n";
833 if (!layout_.latexparam.empty())
834 os << from_utf8(layout_.latexparam);
837 OutputParams rp = runparams;
838 if (layout_.passthru)
840 if (layout_.needprotect)
841 rp.moving_arg = true;
842 int i = InsetText::latex(buf, os, rp);
843 if (!layout_.latexname.empty()) {
844 if (layout_.latextype == "command") {
846 } else if (layout_.latextype == "environment") {
847 os << "\n\\end{" << from_utf8(layout_.latexname) << "}\n";
855 void InsetCollapsable::validate(LaTeXFeatures & features) const
857 // Force inclusion of preamble snippet in layout file
858 features.require(layout_.name);
859 InsetText::validate(features);