X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Finsets%2FInsetCollapsible.cpp;h=a0a789cc6759416bd14f50b149928c8809870bf1;hb=8124e6c02ea1fd6779bb6c47ffe2bca2c8bd2d97;hp=11c9e2a43d42de598cbd0897ef119adf3db2edd7;hpb=c466baaa5b99e44ea25616556bd0918197f4b54c;p=lyx.git diff --git a/src/insets/InsetCollapsible.cpp b/src/insets/InsetCollapsible.cpp index 11c9e2a43d..a0a789cc67 100644 --- a/src/insets/InsetCollapsible.cpp +++ b/src/insets/InsetCollapsible.cpp @@ -15,15 +15,18 @@ #include "InsetCollapsible.h" #include "Buffer.h" +#include "BufferParams.h" #include "BufferView.h" +#include "CutAndPaste.h" #include "Cursor.h" #include "Dimension.h" +#include "Format.h" #include "FuncRequest.h" #include "FuncStatus.h" #include "InsetLayout.h" #include "Lexer.h" #include "MetricsInfo.h" -#include "OutputParams.h" +#include "TextClass.h" #include "TocBackend.h" #include "frontends/FontMetrics.h" @@ -31,10 +34,12 @@ #include "support/debug.h" #include "support/docstream.h" +#include "support/FileName.h" #include "support/gettext.h" #include "support/lassert.h" #include "support/lstrings.h" -#include "support/RefChanger.h" +#include "support/Changer.h" +#include "support/TempFile.h" using namespace std; @@ -55,7 +60,18 @@ InsetCollapsible::InsetCollapsible(InsetCollapsible const & rhs) : InsetText(rhs), status_(rhs.status_), labelstring_(rhs.labelstring_) -{} +{ + tempfile_.reset(); +} + + +InsetCollapsible & InsetCollapsible::operator=(InsetCollapsible const & that) +{ + if (&that == this) + return *this; + *this = InsetCollapsible(that); + return *this; +} InsetCollapsible::~InsetCollapsible() @@ -70,7 +86,7 @@ InsetCollapsible::~InsetCollapsible() InsetCollapsible::CollapseStatus InsetCollapsible::status(BufferView const & bv) const { - if (decoration() == InsetLayout::CONGLOMERATE) + if (decoration() == InsetDecoration::CONGLOMERATE) return status_; return view_[&bv].auto_open_ ? Open : status_; } @@ -79,18 +95,21 @@ InsetCollapsible::CollapseStatus InsetCollapsible::status(BufferView const & bv) InsetCollapsible::Geometry InsetCollapsible::geometry(BufferView const & bv) const { switch (decoration()) { - case InsetLayout::CLASSIC: + case InsetDecoration::CLASSIC: if (status(bv) == Open) return view_[&bv].openinlined_ ? LeftButton : TopButton; return ButtonOnly; - case InsetLayout::MINIMALISTIC: - return status(bv) == Open ? NoButton : ButtonOnly ; + case InsetDecoration::MINIMALISTIC: { + return status(bv) == Open ? + (tempfile_ ? LeftButton : NoButton) + : ButtonOnly; + } - case InsetLayout::CONGLOMERATE: + case InsetDecoration::CONGLOMERATE: return status(bv) == Open ? SubLabel : Corners ; - case InsetLayout::DEFAULT: + case InsetDecoration::DEFAULT: break; // this shouldn't happen } @@ -141,14 +160,44 @@ void InsetCollapsible::read(Lexer & lex) setButtonLabel(); } +int InsetCollapsible::topOffset(BufferView const * bv) const +{ + switch (geometry(*bv)) { + case Corners: + case SubLabel: + return 0; + default: + return InsetText::topOffset(bv); + } +} + +int InsetCollapsible::bottomOffset(BufferView const * bv) const +{ + switch (geometry(*bv)) { + case Corners: + case SubLabel: + return InsetText::bottomOffset(bv) / 4; + default: + return InsetText::bottomOffset(bv); + } +} + Dimension InsetCollapsible::dimensionCollapsed(BufferView const & bv) const { Dimension dim; FontInfo labelfont(getLabelfont()); labelfont.realize(sane_font); + int const offset = Inset::textOffset(&bv); theFontMetrics(labelfont).buttonText( - buttonLabel(bv), TEXT_TO_INSET_OFFSET, dim.wid, dim.asc, dim.des); + buttonLabel(bv), offset, dim.wid, dim.asc, dim.des); + // remove spacing on the right for left buttons; we also do it for + // TopButton (although it is not useful per se), because + // openinlined_ is not always set properly at this point. + Geometry const geom = geometry(bv); + if (geom == LeftButton || geom == TopButton) + // this form makes a difference if offset is even + dim.wid -= offset - offset / 2; return dim; } @@ -157,10 +206,6 @@ void InsetCollapsible::metrics(MetricsInfo & mi, Dimension & dim) const { view_[mi.base.bv].auto_open_ = mi.base.bv->cursor().isInside(this); - FontInfo tmpfont = mi.base.font; - mi.base.font = getFont(); - mi.base.font.realize(tmpfont); - BufferView const & bv = *mi.base.bv; switch (geometry(bv)) { @@ -169,8 +214,6 @@ void InsetCollapsible::metrics(MetricsInfo & mi, Dimension & dim) const break; case Corners: InsetText::metrics(mi, dim); - dim.des -= 3; - dim.asc -= 1; break; case SubLabel: { InsetText::metrics(mi, dim); @@ -184,6 +227,7 @@ void InsetCollapsible::metrics(MetricsInfo & mi, Dimension & dim) const int d = 0; theFontMetrics(font).rectText(buttonLabel(bv), w, a, d); dim.des += a + d; + dim.wid = max(dim.wid, w); break; } case TopButton: @@ -200,19 +244,17 @@ void InsetCollapsible::metrics(MetricsInfo & mi, Dimension & dim) const InsetText::metrics(mi, textdim); view_[&bv].openinlined_ = (textdim.wid + dim.wid) < mi.base.textwidth; if (view_[&bv].openinlined_) { - // Correct for button width. - dim.wid += textdim.wid; + // Correct for button width but remove spacing before frame + dim.wid += textdim.wid - leftOffset(mi.base.bv) / 2; dim.des = max(dim.des - textdim.asc + dim.asc, textdim.des); dim.asc = textdim.asc; } else { - dim.des += textdim.height() + TEXT_TO_INSET_OFFSET; + dim.des += textdim.height() + topOffset(mi.base.bv); dim.wid = max(dim.wid, textdim.wid); } } break; } - - mi.base.font = tmpfont; } @@ -224,6 +266,18 @@ bool InsetCollapsible::setMouseHover(BufferView const * bv, bool mouse_hover) } +ColorCode InsetCollapsible::backgroundColor(PainterInfo const &) const +{ + return getLayout().bgcolor(); +} + + +ColorCode InsetCollapsible::labelColor() const +{ + return getLayout().labelfont().color(); +} + + void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const { BufferView const & bv = *pi.base.bv; @@ -248,11 +302,11 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const labelfont.realize(pi.base.font); pi.pain.buttonText(x, y, buttonLabel(bv), labelfont, view_[&bv].mouse_hover_ ? Color_buttonhoverbg : Color_buttonbg, - Color_buttonframe, TEXT_TO_INSET_OFFSET); + Color_buttonframe, Inset::textOffset(pi.base.bv)); // Draw the change tracking cue on the label, unless RowPainter already // takes care of it. if (canPaintChange(bv)) - pi.change_.paintCue(pi, x, y, x + dimc.width(), labelfont); + pi.change.paintCue(pi, x, y, x + dimc.width(), labelfont); } else { view_[&bv].button_dim_.x1 = 0; view_[&bv].button_dim_.y1 = 0; @@ -268,7 +322,9 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const case LeftButton: case TopButton: { if (g == LeftButton) { - textx = x + dimc.width(); + // correct for spacing added before the frame in + // InsetText::draw. We want the button to touch the frame. + textx = x + dimc.width() - leftOffset(pi.base.bv) / 2; texty = baseline; } else { textx = x; @@ -276,9 +332,9 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const } // Do not draw the cue for INSERTED -- it is already in the button and // that's enough. - Changer dummy = (pi.change_.type == Change::INSERTED) - ? make_change(pi.change_, Change()) - : Changer(); + Changer cdummy = (pi.change.type == Change::INSERTED) + ? changeVar(pi.change, Change()) + : noChange(); InsetText::draw(pi, textx, texty); break; } @@ -293,23 +349,22 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const case Corners: textx = x; texty = baseline; - { // We will take care of the frame and the change tracking cue - // ourselves, below. - Changer dummy = make_change(pi.change_, Change()); + // We will take care of the frame and the change tracking cue + // ourselves, below. + { + Changer cdummy = changeVar(pi.change, Change()); const_cast(this)->setDrawFrame(false); InsetText::draw(pi, textx, texty); const_cast(this)->setDrawFrame(true); } int desc = textdim.descent(); - if (g == Corners) - desc -= 3; // Colour the frame according to the change type. (Like for tables.) - Color colour = pi.change_.changed() ? pi.change_.color() + Color colour = pi.change.changed() ? pi.change.color() : Color_foreground; - const int xx1 = x + TEXT_TO_INSET_OFFSET - 1; - const int xx2 = x + textdim.wid - TEXT_TO_INSET_OFFSET + 1; + const int xx1 = x + leftOffset(pi.base.bv) - 1; + const int xx2 = x + textdim.wid - rightOffset(pi.base.bv) + 1; pi.pain.line(xx1, y + desc - 4, xx1, y + desc, colour); if (status_ == Open) @@ -328,7 +383,7 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const // the label below the text. Can be toggled. if (g == SubLabel) { FontInfo font(getLabelfont()); - if (pi.change_.changed()) + if (pi.change.changed()) font.setPaintColor(colour); font.realize(sane_font); font.decSize(); @@ -336,7 +391,7 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const int w = 0; int a = 0; int d = 0; - Color const col = pi.full_repaint ? Color_none : pi.backgroundColor(this); + Color const col = pi.full_repaint ? Color_none : pi.backgroundColor(); theFontMetrics(font).rectText(buttonLabel(bv), w, a, d); int const ww = max(textdim.wid, w); pi.pain.rectText(x + (ww - w) / 2, y + desc + a, @@ -354,8 +409,8 @@ void InsetCollapsible::draw(PainterInfo & pi, int x, int y) const } // Strike through the inset if deleted and not already handled by // RowPainter. - if (pi.change_.deleted() && canPaintChange(bv)) - pi.change_.paintCue(pi, xx1, y1, xx2, y + desc); + if (pi.change.deleted() && canPaintChange(bv)) + pi.change.paintCue(pi, xx1, y1, xx2, y + desc); break; } } @@ -372,7 +427,7 @@ void InsetCollapsible::cursorPos(BufferView const & bv, switch (geometry(bv)) { case LeftButton: - x += dimensionCollapsed(bv).wid; + x += dimensionCollapsed(bv).wid - leftOffset(&bv) / 2; break; case TopButton: { y += dimensionCollapsed(bv).des + textdim.asc; @@ -392,9 +447,12 @@ void InsetCollapsible::cursorPos(BufferView const & bv, bool InsetCollapsible::editable() const { + if (tempfile_) + return false; + switch (decoration()) { - case InsetLayout::CLASSIC: - case InsetLayout::MINIMALISTIC: + case InsetDecoration::CLASSIC: + case InsetDecoration::MINIMALISTIC: return status_ == Open; default: return true; @@ -404,6 +462,9 @@ bool InsetCollapsible::editable() const bool InsetCollapsible::descendable(BufferView const & bv) const { + if (tempfile_) + return false; + return geometry(bv) != ButtonOnly; } @@ -423,6 +484,8 @@ docstring const InsetCollapsible::getNewLabel(docstring const & l) const pos_type i = 0; pos_type j = 0; for (; i < n && j < p_siz; ++j) { + if (paragraphs().begin()->isDeleted(j)) + continue; if (paragraphs().begin()->isInset(j)) { if (!paragraphs().begin()->getInset(j)->isChar()) continue; @@ -434,8 +497,7 @@ docstring const InsetCollapsible::getNewLabel(docstring const & l) const if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) { label << "..."; } - docstring const lbl = label.str(); - return lbl.empty() ? l : lbl; + return label.str().empty() ? l : label.str(); } @@ -451,6 +513,7 @@ Inset * InsetCollapsible::editXY(Cursor & cur, int x, int y) { //lyxerr << "InsetCollapsible: edit xy" << endl; if (geometry(cur.bv()) == ButtonOnly + || !descendable(cur.bv()) || (view_[&cur.bv()].button_dim_.contains(x, y) && geometry(cur.bv()) != NoButton)) return this; @@ -546,6 +609,41 @@ void InsetCollapsible::doDispatch(Cursor & cur, FuncRequest & cmd) cur.dispatched(); break; + case LFUN_INSET_EDIT: { + cur.push(*this); + text().selectAll(cur); + string const format = + cur.buffer()->params().documentClass().outputFormat(); + string const ext = theFormats().extension(format); + tempfile_.reset(new support::TempFile("ert_editXXXXXX." + ext)); + support::FileName const tempfilename = tempfile_->name(); + string const name = tempfilename.toFilesystemEncoding(); + ofdocstream os(name.c_str()); + os << cur.selectionAsString(false); + os.close(); + // Since we lock the inset while the external file is edited, + // we need to move the cursor outside and clear any selection inside + cur.clearSelection(); + cur.pop(); + cur.leaveInset(*this); + theFormats().edit(buffer(), tempfilename, format); + break; + } + case LFUN_INSET_END_EDIT: { + support::FileName const tempfilename = tempfile_->name(); + docstring const s = tempfilename.fileContents("UTF-8"); + cur.recordUndoInset(this); + cur.push(*this); + text().selectAll(cur); + cap::replaceSelection(cur); + cur.text()->insertStringAsLines(cur, s, cur.current_font); + // FIXME (gb) it crashes without this + cur.fixIfBroken(); + tempfile_.reset(); + cur.pop(); + break; + } + default: InsetText::doDispatch(cur, cmd); break; @@ -569,6 +667,16 @@ bool InsetCollapsible::getStatus(Cursor & cur, FuncRequest const & cmd, flag.setEnabled(false); return true; + case LFUN_INSET_EDIT: + flag.setEnabled(!buffer().hasReadonlyFlag() && + getLayout().editExternally() && tempfile_ == nullptr); + return true; + + case LFUN_INSET_END_EDIT: + flag.setEnabled(!buffer().hasReadonlyFlag() && + getLayout().editExternally() && tempfile_ != nullptr); + return true; + default: return InsetText::getStatus(cur, cmd, flag); } @@ -591,11 +699,17 @@ docstring InsetCollapsible::getLabel() const docstring const InsetCollapsible::buttonLabel(BufferView const & bv) const { + // U+1F512 LOCK + docstring const locked = tempfile_ ? docstring(1, 0x1F512) : docstring(); + // indicate changed content in label (#8645) + // ✎ U+270E LOWER RIGHT PENCIL + docstring const indicator = (isChanged() && geometry(bv) == ButtonOnly) + ? docstring(1, 0x270E) : docstring(); InsetLayout const & il = getLayout(); docstring const label = getLabel(); if (!il.contentaslabel() || geometry(bv) != ButtonOnly) - return label; - return getNewLabel(label); + return locked + indicator + label; + return locked + indicator + getNewLabel(label); } @@ -608,10 +722,10 @@ void InsetCollapsible::setStatus(Cursor & cur, CollapseStatus status) } -InsetLayout::InsetDecoration InsetCollapsible::decoration() const +InsetDecoration InsetCollapsible::decoration() const { - InsetLayout::InsetDecoration const dec = getLayout().decoration(); - return dec == InsetLayout::DEFAULT ? InsetLayout::CLASSIC : dec; + InsetDecoration const dec = getLayout().decoration(); + return dec == InsetDecoration::DEFAULT ? InsetDecoration::CLASSIC : dec; } @@ -620,7 +734,7 @@ string InsetCollapsible::contextMenu(BufferView const & bv, int x, { string context_menu = contextMenuName(); string const it_context_menu = InsetText::contextMenuName(); - if (decoration() == InsetLayout::CONGLOMERATE) + if (decoration() == InsetDecoration::CONGLOMERATE) return context_menu + ";" + it_context_menu; string const ic_context_menu = InsetCollapsible::contextMenuName(); @@ -640,7 +754,7 @@ string InsetCollapsible::contextMenu(BufferView const & bv, int x, string InsetCollapsible::contextMenuName() const { - if (decoration() == InsetLayout::CONGLOMERATE) + if (decoration() == InsetDecoration::CONGLOMERATE) return "context-conglomerate"; else return "context-collapsible"; @@ -670,23 +784,23 @@ void InsetCollapsible::addToToc(DocIterator const & cpit, bool output_active, { bool doing_output = output_active && producesOutput(); InsetLayout const & layout = getLayout(); - if (layout.addToToc()) { - TocBuilder & b = backend.builder(layout.tocType()); - // Cursor inside the inset - DocIterator pit = cpit; - pit.push_back(CursorSlice(const_cast(*this))); - docstring const label = getLabel(); - b.pushItem(pit, label + (label.empty() ? "" : ": "), output_active); - // Proceed with the rest of the inset. - InsetText::addToToc(cpit, doing_output, utype, backend); - if (layout.isTocCaption()) { - docstring str; - text().forOutliner(str, TOC_ENTRY_LENGTH); - b.argumentItem(str); - } - b.pop(); - } else - InsetText::addToToc(cpit, doing_output, utype, backend); + if (!layout.addToToc()) + return InsetText::addToToc(cpit, doing_output, utype, backend); + + TocBuilder & b = backend.builder(layout.tocType()); + // Cursor inside the inset + DocIterator pit = cpit; + pit.push_back(CursorSlice(const_cast(*this))); + docstring const label = getLabel(); + b.pushItem(pit, label + (label.empty() ? "" : ": "), output_active); + // Proceed with the rest of the inset. + InsetText::addToToc(cpit, doing_output, utype, backend); + if (layout.isTocCaption()) { + docstring str; + text().forOutliner(str, TOC_ENTRY_LENGTH); + b.argumentItem(str); + } + b.pop(); }