From bb344452c8eafaba4f0e7a51f8e4d99176570d9b Mon Sep 17 00:00:00 2001 From: Guillaume Munch Date: Sun, 4 Oct 2015 19:38:47 +0100 Subject: [PATCH] Consistency of ellipses across the UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the function support:truncateWithEllipsis() to shorten a docstring with ... at the end. Actually we use U+2026 HORIZONTAL ELLIPSIS instead of "..." when automatically shortening strings. This is to be consistent with Qt's own truncation and is much nicer on the screen. This includes the bugs #9575 and #9572 regarding broken text elision in the outliner. Known issues (non-regressions): * TocBackend::updateItem() should be rewritten to update all TOCs. (#8386) * "..." should be replaced with … everywhere else on the interface (including translation strings). * We should prefer to rely on QFontMetrics::elidedText() to truncate strings with an ellipsis whenever possible, or an equivalent for the buffer view dependent on the font metrics. See the warning in src/support/lstrings.h. --- src/BiblioInfo.cpp | 14 ++++---------- src/CutAndPaste.cpp | 14 ++++++-------- src/Paragraph.cpp | 10 +++++++--- src/Paragraph.h | 3 ++- src/Text.cpp | 24 +++++++++++++++++------ src/Text.h | 18 +++++++++-------- src/TocBackend.cpp | 25 +++++++++++++++--------- src/frontends/qt4/GuiCompleter.cpp | 7 ++++--- src/frontends/qt4/GuiWorkArea.cpp | 11 ++++++++--- src/frontends/qt4/Menus.cpp | 8 +++----- src/insets/Inset.cpp | 2 +- src/insets/Inset.h | 3 ++- src/insets/InsetBranch.cpp | 5 +++-- src/insets/InsetBranch.h | 2 +- src/insets/InsetCaptionable.cpp | 3 +-- src/insets/InsetCitation.cpp | 16 ++++----------- src/insets/InsetCitation.h | 2 +- src/insets/InsetHyperlink.cpp | 9 +++++---- src/insets/InsetHyperlink.h | 2 +- src/insets/InsetIPAMacro.cpp | 2 +- src/insets/InsetIPAMacro.h | 2 +- src/insets/InsetNomencl.cpp | 6 +----- src/insets/InsetQuotes.cpp | 2 +- src/insets/InsetQuotes.h | 2 +- src/insets/InsetRef.cpp | 13 ++++--------- src/insets/InsetRef.h | 2 +- src/insets/InsetScript.cpp | 3 +-- src/insets/InsetSpace.cpp | 2 +- src/insets/InsetSpace.h | 2 +- src/insets/InsetSpecialChar.cpp | 3 ++- src/insets/InsetSpecialChar.h | 2 +- src/insets/InsetText.cpp | 5 +++-- src/insets/InsetText.h | 2 +- src/mathed/InsetMathHull.cpp | 4 +++- src/mathed/InsetMathHull.h | 2 +- src/support/filetools.cpp | 10 ++++------ src/support/lstrings.cpp | 24 ++++++++++++++++------- src/support/lstrings.h | 31 ++++++++++++++++++++++++++++++ 38 files changed, 173 insertions(+), 124 deletions(-) diff --git a/src/BiblioInfo.cpp b/src/BiblioInfo.cpp index d306c87e5f..f2bf332a26 100644 --- a/src/BiblioInfo.cpp +++ b/src/BiblioInfo.cpp @@ -756,8 +756,7 @@ docstring BibTeXInfo::getValueForKey(string const & oldkey, Buffer const & buf, ret = html::cleanAttr(ret); // make sure it is not too big - if (ret.size() > maxsize) - ret = ret.substr(0, maxsize - 3) + from_ascii("..."); + support::truncateWithEllipsis(ret, maxsize); return ret; } @@ -928,14 +927,9 @@ docstring const BiblioInfo::getLabel(vector keys, before, after, dialog, key + 1 != ken); } - if (ret.size() > max_size) { - ret.resize(max_size - 3); - ret += "..."; - } else if (too_many_keys) { - if (ret.size() > max_size - 3) - ret.resize(max_size - 3); - ret += "..."; - } + if (too_many_keys) + ret.push_back(0x2026);//HORIZONTAL ELLIPSIS + support::truncateWithEllipsis(ret, max_size); return ret; } diff --git a/src/CutAndPaste.cpp b/src/CutAndPaste.cpp index 0bfd3ad4e9..a521e1434e 100644 --- a/src/CutAndPaste.cpp +++ b/src/CutAndPaste.cpp @@ -811,22 +811,20 @@ vector availableSelections(Buffer const * buf) // we do not use cit-> here because gcc 2.9x does not // like it (JMarc) ParagraphList const & pars = (*cit).first; - docstring asciiSel; + docstring textSel; ParagraphList::const_iterator pit = pars.begin(); ParagraphList::const_iterator pend = pars.end(); for (; pit != pend; ++pit) { - Paragraph par(*pit, 0, 26); + Paragraph par(*pit, 0, 46); // adapt paragraph to current buffer. par.setBuffer(const_cast(*buf)); - asciiSel += par.asString(AS_STR_INSETS); - if (asciiSel.size() > 25) { - asciiSel.replace(22, docstring::npos, - from_ascii("...")); + textSel += par.asString(AS_STR_INSETS); + if (textSel.size() > 45) { + support::truncateWithEllipsis(textSel,45); break; } } - - selList.push_back(asciiSel); + selList.push_back(textSel); } return selList; diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 5bbc16aaed..37067acb65 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -3257,11 +3257,13 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out } -void Paragraph::forOutliner(docstring & os, size_t maxlen) const +void Paragraph::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const { + size_t tmplen = shorten ? maxlen + 1 : maxlen; if (!d->params_.labelString().empty()) os += d->params_.labelString() + ' '; - for (pos_type i = 0; i < size() && os.length() < maxlen; ++i) { + for (pos_type i = 0; i < size() && os.length() < tmplen; ++i) { if (isDeleted(i)) continue; char_type const c = d->text_[i]; @@ -3270,8 +3272,10 @@ void Paragraph::forOutliner(docstring & os, size_t maxlen) const else if (c == '\t' || c == '\n') os += ' '; else if (c == META_INSET) - getInset(i)->forOutliner(os, maxlen); + getInset(i)->forOutliner(os, tmplen, false); } + if (shorten) + Text::shortenForOutliner(os, maxlen); } diff --git a/src/Paragraph.h b/src/Paragraph.h index 444ce26bdd..9f19f1d904 100644 --- a/src/Paragraph.h +++ b/src/Paragraph.h @@ -181,7 +181,8 @@ public: int options = AS_STR_NONE, const OutputParams *runparams = 0) const; /// - void forOutliner(docstring &, size_t maxlen) const; + void forOutliner(docstring &, size_t const maxlen, + bool const shorten = true) const; /// void write(std::ostream &, BufferParams const &, diff --git a/src/Text.cpp b/src/Text.cpp index bedf228b33..67776a0d40 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -2043,13 +2043,25 @@ docstring Text::asString(pit_type beg, pit_type end, int options) const } -void Text::forOutliner(docstring & os, size_t maxlen, bool shorten) const +void Text::shortenForOutliner(docstring & str, size_t const maxlen) { - LASSERT(maxlen >= 8, maxlen = TOC_ENTRY_LENGTH); - for (size_t i = 0; i != pars_.size() && os.length() < maxlen; ++i) - pars_[i].forOutliner(os, maxlen); - if (shorten && os.length() >= maxlen) - os = os.substr(0, maxlen - 3) + from_ascii("..."); + support::truncateWithEllipsis(str, maxlen); + docstring::iterator it = str.begin(); + docstring::iterator end = str.end(); + for (; it != end; ++it) + if ((*it) == L'\n' || (*it) == L'\t') + (*it) = L' '; +} + + +void Text::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const +{ + size_t tmplen = shorten ? maxlen + 1 : maxlen; + for (size_t i = 0; i != pars_.size() && os.length() < tmplen; ++i) + pars_[i].forOutliner(os, tmplen, false); + if (shorten) + shortenForOutliner(os, maxlen); } diff --git a/src/Text.h b/src/Text.h index d7777e3ac3..2d855ecc8f 100644 --- a/src/Text.h +++ b/src/Text.h @@ -123,14 +123,16 @@ public: /// docstring asString(pit_type beg, pit_type end, int options = AS_STR_NONE) const; - /// Appends a possibly abbreviated representation of our text - /// to \param os, where \param maxlen defines the maximum size - /// of \param os. If \param shorten is true, then we will shorten - /// \param os to maxlen chars and replace the final three by "..., - /// if \param os is longer than maxlen chars. - /// if \param maxlen is passed as 0, then it is ignored. (In fact, - /// it is reset to the maximum value for size_t.) - void forOutliner(docstring & os, size_t maxlen, bool shorten = true) const; + + /// truncates str to maxlenwith an ellipsis and replaces the characters '\n' + /// and '\t' with spaces + static void shortenForOutliner(docstring & str, size_t const maxlen); + + /// Appends a possibly abbreviated representation of our text to \param os, + /// where \param maxlen defines the maximum size of \param os. If \param + /// shorten is true, then os is shortened as above + void forOutliner(docstring & os, size_t const maxlen, + bool const shorten = true) const; /// insert a character at cursor position /// FIXME: replace Cursor with DocIterator. diff --git a/src/TocBackend.cpp b/src/TocBackend.cpp index f3511be8e5..145a23b3cc 100644 --- a/src/TocBackend.cpp +++ b/src/TocBackend.cpp @@ -31,8 +31,8 @@ #include "support/convert.h" #include "support/debug.h" #include "support/docstream.h" - #include "support/lassert.h" +#include "support/lstrings.h" using namespace std; @@ -259,6 +259,11 @@ shared_ptr TocBackend::builder(string const & type) } +// FIXME: This function duplicates functionality from InsetText::iterateForToc. +// Both have their own way of computing the TocItem for "tableofcontents". The +// TocItem creation and update should be made in a dedicated function and +// updateItem should be rewritten to uniformly update the matching items from +// all TOCs. bool TocBackend::updateItem(DocIterator const & dit) { if (dit.text()->getTocLevel(dit.pit()) == Layout::NOT_IN_TOC) @@ -280,28 +285,30 @@ bool TocBackend::updateItem(DocIterator const & dit) // For each paragraph, traverse its insets and let them add // their toc items + // + // FIXME: This is supposed to accomplish the same as the body of + // InsetText::iterateForToc(), probably Paragraph & par = toc_item->dit_.paragraph(); InsetList::const_iterator it = par.insetList().begin(); InsetList::const_iterator end = par.insetList().end(); for (; it != end; ++it) { Inset & inset = *it->inset; if (inset.lyxCode() == ARG_CODE) { + tocstring = par.labelString(); if (!tocstring.empty()) - break; - Paragraph const & inset_par = - *static_cast(inset).paragraphs().begin(); - if (!par.labelString().empty()) - tocstring = par.labelString() + ' '; - tocstring += inset_par.asString(AS_STR_INSETS); + tocstring += ' '; + inset.asInsetText()->text().forOutliner(tocstring,TOC_ENTRY_LENGTH); break; } } - int const toclevel = toc_item->dit_.text()->getTocLevel(toc_item->dit_.pit()); + int const toclevel = toc_item->dit_.text()-> + getTocLevel(toc_item->dit_.pit()); if (toclevel != Layout::NOT_IN_TOC && toclevel >= min_toclevel && tocstring.empty()) - tocstring = par.asString(AS_STR_LABEL | AS_STR_INSETS); + par.forOutliner(tocstring, TOC_ENTRY_LENGTH); + support::truncateWithEllipsis(tocstring, TOC_ENTRY_LENGTH); const_cast(*toc_item).str(tocstring); buffer_->updateTocItem("tableofcontents", dit); diff --git a/src/frontends/qt4/GuiCompleter.cpp b/src/frontends/qt4/GuiCompleter.cpp index 7c3c2ff2c8..0f974f1d97 100644 --- a/src/frontends/qt4/GuiCompleter.cpp +++ b/src/frontends/qt4/GuiCompleter.cpp @@ -26,6 +26,7 @@ #include "version.h" #include "support/lassert.h" +#include "support/lstrings.h" #include "support/debug.h" #include @@ -394,9 +395,9 @@ void GuiCompleter::updateInline(Cursor const & cur, QString const & completion) docstring postfix = qstring_to_ucs4(completion.mid(prefix.length())); // shorten it if necessary - if (lyxrc.completion_inline_dots != -1 - && postfix.size() > unsigned(lyxrc.completion_inline_dots)) - postfix = postfix.substr(0, lyxrc.completion_inline_dots - 1) + "..."; + if (lyxrc.completion_inline_dots != -1) + support::truncateWithEllipsis(postfix, + unsigned(lyxrc.completion_inline_dots)); // set inline completion at cursor position size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size()); diff --git a/src/frontends/qt4/GuiWorkArea.cpp b/src/frontends/qt4/GuiWorkArea.cpp index 2411206771..11596d5a1a 100644 --- a/src/frontends/qt4/GuiWorkArea.cpp +++ b/src/frontends/qt4/GuiWorkArea.cpp @@ -1852,7 +1852,7 @@ public: if (!dotted) { if (dottedPrefix_ && !prefix_.isEmpty()) - prefix_ += ".../"; + prefix_ += ellipsisSlash_; prefix_ += postfix_.front() + "/"; } dottedPrefix_ = dotted && !prefix_.isEmpty(); @@ -1865,7 +1865,7 @@ public: return filename_; bool dots = dottedPrefix_ || !postfix_.isEmpty(); - return prefix_ + (dots ? ".../" : "") + filename_; + return prefix_ + (dots ? ellipsisSlash_ : "") + filename_; } /// QString forecastPathString() const @@ -1874,7 +1874,7 @@ public: return displayString(); return prefix_ - + (dottedPrefix_ ? ".../" : "") + + (dottedPrefix_ ? ellipsisSlash_ : "") + postfix_.front() + "/"; } /// @@ -1883,6 +1883,8 @@ public: int tab() const { return tab_; } private: + /// ".../" + static QString const ellipsisSlash_; /// QString prefix_; /// @@ -1898,6 +1900,9 @@ private: }; +QString const DisplayPath::ellipsisSlash_ = QString(QChar(0x2026)) + "/"; + + /// bool operator<(DisplayPath const & a, DisplayPath const & b) { diff --git a/src/frontends/qt4/Menus.cpp b/src/frontends/qt4/Menus.cpp index 592a66a816..2507ae31d8 100644 --- a/src/frontends/qt4/Menus.cpp +++ b/src/frontends/qt4/Menus.cpp @@ -768,11 +768,9 @@ bool MenuDefinition::searchMenu(FuncRequest const & func, docstring_list & names QString limitStringLength(docstring const & str) { size_t const max_item_length = 45; - - if (str.size() > max_item_length) - return toqstr(str.substr(0, max_item_length - 3) + "..."); - - return toqstr(str); + docstring ret = str.substr(0, max_item_length + 1); + support::truncateWithEllipsis(ret, max_item_length); + return toqstr(ret); } diff --git a/src/insets/Inset.cpp b/src/insets/Inset.cpp index cce0f327fe..3d9b5e62b5 100644 --- a/src/insets/Inset.cpp +++ b/src/insets/Inset.cpp @@ -258,7 +258,7 @@ docstring Inset::toolTip(BufferView const &, int, int) const } -void Inset::forOutliner(docstring &, size_t) const +void Inset::forOutliner(docstring &, size_t const, bool const) const { } diff --git a/src/insets/Inset.h b/src/insets/Inset.h index b0fab8e998..f92883037b 100644 --- a/src/insets/Inset.h +++ b/src/insets/Inset.h @@ -328,7 +328,8 @@ public: /// Appends a potentially abbreviated version of the inset to /// \param str. Intended for use by the TOC. virtual void forOutliner(docstring & str, - size_t maxlen = TOC_ENTRY_LENGTH) const; + size_t const maxlen = TOC_ENTRY_LENGTH, + bool const shorten = true) const; /// can the contents of the inset be edited on screen ? // true for InsetCollapsables (not ButtonOnly) (not InsetInfo), InsetText diff --git a/src/insets/InsetBranch.cpp b/src/insets/InsetBranch.cpp index 0ed3bc49e6..5a278f94c3 100644 --- a/src/insets/InsetBranch.cpp +++ b/src/insets/InsetBranch.cpp @@ -299,10 +299,11 @@ void InsetBranch::toString(odocstream & os) const } -void InsetBranch::forOutliner(docstring & os, size_t maxlen) const +void InsetBranch::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const { if (isBranchSelected()) - InsetCollapsable::forOutliner(os, maxlen); + InsetCollapsable::forOutliner(os, maxlen, shorten); } diff --git a/src/insets/InsetBranch.h b/src/insets/InsetBranch.h index f4ba3f5d94..e005bce245 100644 --- a/src/insets/InsetBranch.h +++ b/src/insets/InsetBranch.h @@ -76,7 +76,7 @@ private: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// void validate(LaTeXFeatures &) const; /// diff --git a/src/insets/InsetCaptionable.cpp b/src/insets/InsetCaptionable.cpp index cbdcc0ab15..0434c2eb2f 100644 --- a/src/insets/InsetCaptionable.cpp +++ b/src/insets/InsetCaptionable.cpp @@ -40,8 +40,7 @@ docstring InsetCaptionable::floatName(string const & type) const BufferParams const & bp = buffer().params(); FloatList const & floats = bp.documentClass().floats(); FloatList::const_iterator it = floats[type]; - // FIXME UNICODE - return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name()); + return (it == floats.end()) ? from_utf8(type) : bp.B_(it->second.name()); } diff --git a/src/insets/InsetCitation.cpp b/src/insets/InsetCitation.cpp index b455c8654e..4dcec9474e 100644 --- a/src/insets/InsetCitation.cpp +++ b/src/insets/InsetCitation.cpp @@ -313,21 +313,13 @@ void InsetCitation::updateBuffer(ParIterator const &, UpdateType) { if (!cache.recalculate && buffer().citeLabelsValid()) return; - // The label may have changed, so we have to re-create it. docstring const glabel = generateLabel(); - - unsigned int const maxLabelChars = 45; - - docstring label = glabel; - if (label.size() > maxLabelChars) { - label.erase(maxLabelChars - 3); - label += "..."; - } - cache.recalculate = false; cache.generated_label = glabel; - cache.screen_label = label; + unsigned int const maxLabelChars = 45; + cache.screen_label = glabel.substr(0, maxLabelChars); + support::truncateWithEllipsis(cache.screen_label, maxLabelChars); } @@ -406,7 +398,7 @@ void InsetCitation::toString(odocstream & os) const } -void InsetCitation::forOutliner(docstring & os, size_t) const +void InsetCitation::forOutliner(docstring & os, size_t const, bool const) const { os += screenLabel(); } diff --git a/src/insets/InsetCitation.h b/src/insets/InsetCitation.h index 4b4b5ed69c..417b46f518 100644 --- a/src/insets/InsetCitation.h +++ b/src/insets/InsetCitation.h @@ -59,7 +59,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// void validate(LaTeXFeatures &) const {} /// diff --git a/src/insets/InsetHyperlink.cpp b/src/insets/InsetHyperlink.cpp index cbcf613b78..54f1f2c765 100644 --- a/src/insets/InsetHyperlink.cpp +++ b/src/insets/InsetHyperlink.cpp @@ -56,7 +56,7 @@ ParamInfo const & InsetHyperlink::findInfo(string const & /* cmdName */) docstring InsetHyperlink::screenLabel() const { - docstring const temp = from_ascii("Hyperlink: "); + docstring const temp = _("Hyperlink: "); docstring url; @@ -66,8 +66,9 @@ docstring InsetHyperlink::screenLabel() const // elide if long if (url.length() > 30) { - url = url.substr(0, 10) + "..." - + url.substr(url.length() - 17, url.length()); + docstring end = url.substr(url.length() - 17, url.length()); + support::truncateWithEllipsis(url, 13); + url += end; } return temp + url; } @@ -257,7 +258,7 @@ void InsetHyperlink::toString(odocstream & os) const } -void InsetHyperlink::forOutliner(docstring & os, size_t) const +void InsetHyperlink::forOutliner(docstring & os, size_t const, bool const) const { docstring const & n = getParam("name"); if (!n.empty()) { diff --git a/src/insets/InsetHyperlink.h b/src/insets/InsetHyperlink.h index 6bc0c64165..1dd3e08e2c 100644 --- a/src/insets/InsetHyperlink.h +++ b/src/insets/InsetHyperlink.h @@ -38,7 +38,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// docstring toolTip(BufferView const & bv, int x, int y) const; /// diff --git a/src/insets/InsetIPAMacro.cpp b/src/insets/InsetIPAMacro.cpp index 7d5290d565..fd96a19e74 100644 --- a/src/insets/InsetIPAMacro.cpp +++ b/src/insets/InsetIPAMacro.cpp @@ -596,7 +596,7 @@ void InsetIPAChar::toString(odocstream & os) const } -void InsetIPAChar::forOutliner(docstring & os, size_t) const +void InsetIPAChar::forOutliner(docstring & os, size_t const, bool const) const { odocstringstream ods; plaintext(ods, OutputParams(0)); diff --git a/src/insets/InsetIPAMacro.h b/src/insets/InsetIPAMacro.h index 9f2184bb1f..c29fc49c91 100644 --- a/src/insets/InsetIPAMacro.h +++ b/src/insets/InsetIPAMacro.h @@ -156,7 +156,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// InsetCode lyxCode() const { return IPACHAR_CODE; } /// We don't need \begin_inset and \end_inset diff --git a/src/insets/InsetNomencl.cpp b/src/insets/InsetNomencl.cpp index 227976623b..7ebfe4ae2c 100644 --- a/src/insets/InsetNomencl.cpp +++ b/src/insets/InsetNomencl.cpp @@ -75,12 +75,8 @@ ParamInfo const & InsetNomencl::findInfo(string const & /* cmdName */) docstring InsetNomencl::screenLabel() const { size_t const maxLabelChars = 25; - docstring label = _("Nom: ") + getParam("symbol"); - if (label.size() > maxLabelChars) { - label.erase(maxLabelChars - 3); - label += "..."; - } + support::truncateWithEllipsis(label, maxLabelChars); return label; } diff --git a/src/insets/InsetQuotes.cpp b/src/insets/InsetQuotes.cpp index 01776a30fd..039465da44 100644 --- a/src/insets/InsetQuotes.cpp +++ b/src/insets/InsetQuotes.cpp @@ -331,7 +331,7 @@ void InsetQuotes::toString(odocstream & os) const } -void InsetQuotes::forOutliner(docstring & os, size_t) const +void InsetQuotes::forOutliner(docstring & os, size_t const, bool const) const { os += displayString(); } diff --git a/src/insets/InsetQuotes.h b/src/insets/InsetQuotes.h index 0e4f46cbac..fec7fbca23 100644 --- a/src/insets/InsetQuotes.h +++ b/src/insets/InsetQuotes.h @@ -88,7 +88,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t maxlen) const; + void forOutliner(docstring &, size_t const maxlen, bool const) const; /// void validate(LaTeXFeatures &) const; diff --git a/src/insets/InsetRef.cpp b/src/insets/InsetRef.cpp index 4489270d4e..db8ca6ec60 100644 --- a/src/insets/InsetRef.cpp +++ b/src/insets/InsetRef.cpp @@ -260,7 +260,7 @@ void InsetRef::toString(odocstream & os) const } -void InsetRef::forOutliner(docstring & os, size_t) const +void InsetRef::forOutliner(docstring & os, size_t const, bool const) const { // There's no need for details in the TOC, and a long label // will just get in the way. @@ -288,18 +288,13 @@ void InsetRef::updateBuffer(ParIterator const & it, UpdateType) label += getParam("name"); } - screen_label_ = label; - bool shortened = false; unsigned int const maxLabelChars = 24; if (screen_label_.size() > maxLabelChars) { - screen_label_.erase(maxLabelChars - 3); - screen_label_ += "..."; - shortened = true; - } - if (shortened) tooltip_ = label; - else + support::truncateWithEllipsis(label, maxLabelChars); + } else tooltip_ = from_ascii(""); + screen_label_ = label; } diff --git a/src/insets/InsetRef.h b/src/insets/InsetRef.h index 5836d2ecfb..f885423678 100644 --- a/src/insets/InsetRef.h +++ b/src/insets/InsetRef.h @@ -64,7 +64,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// void validate(LaTeXFeatures & features) const; /// diff --git a/src/insets/InsetScript.cpp b/src/insets/InsetScript.cpp index a85444784f..3d912d375f 100644 --- a/src/insets/InsetScript.cpp +++ b/src/insets/InsetScript.cpp @@ -317,8 +317,7 @@ docstring InsetScript::toolTip(BufferView const &, int, int) const InsetText::plaintext(ods, rp, 200); docstring content_tip = ods.str(); // shorten it if necessary - if (content_tip.size() >= 200) - content_tip = content_tip.substr(0, 197) + "..."; + support::truncateWithEllipsis(content_tip, 200); docstring res = scripttranslator_loc().find(params_.type); if (!content_tip.empty()) res += from_ascii(": ") + content_tip; diff --git a/src/insets/InsetSpace.cpp b/src/insets/InsetSpace.cpp index 839ccc917b..3a5161a9ed 100644 --- a/src/insets/InsetSpace.cpp +++ b/src/insets/InsetSpace.cpp @@ -845,7 +845,7 @@ void InsetSpace::toString(odocstream & os) const } -void InsetSpace::forOutliner(docstring & os, size_t) const +void InsetSpace::forOutliner(docstring & os, size_t const, bool const) const { // There's no need to be cute here. os += " "; diff --git a/src/insets/InsetSpace.h b/src/insets/InsetSpace.h index b5a628a456..03ca7aebab 100644 --- a/src/insets/InsetSpace.h +++ b/src/insets/InsetSpace.h @@ -135,7 +135,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// bool hasSettings() const { return true; } /// diff --git a/src/insets/InsetSpecialChar.cpp b/src/insets/InsetSpecialChar.cpp index 99ac836916..8fa6eadc0e 100644 --- a/src/insets/InsetSpecialChar.cpp +++ b/src/insets/InsetSpecialChar.cpp @@ -542,7 +542,8 @@ void InsetSpecialChar::toString(odocstream & os) const } -void InsetSpecialChar::forOutliner(docstring & os, size_t) const +void InsetSpecialChar::forOutliner(docstring & os, size_t const, + bool const) const { odocstringstream ods; plaintext(ods, OutputParams(0)); diff --git a/src/insets/InsetSpecialChar.h b/src/insets/InsetSpecialChar.h index 70e2bf3f82..a5761864d8 100644 --- a/src/insets/InsetSpecialChar.h +++ b/src/insets/InsetSpecialChar.h @@ -78,7 +78,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// InsetCode lyxCode() const { return SPECIALCHAR_CODE; } /// We don't need \begin_inset and \end_inset diff --git a/src/insets/InsetText.cpp b/src/insets/InsetText.cpp index 204df96138..80b0bac013 100644 --- a/src/insets/InsetText.cpp +++ b/src/insets/InsetText.cpp @@ -789,11 +789,12 @@ void InsetText::toString(odocstream & os) const } -void InsetText::forOutliner(docstring & os, size_t maxlen) const +void InsetText::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const { if (!getLayout().isInToc()) return; - text().forOutliner(os, maxlen, false); + text().forOutliner(os, maxlen, shorten); } diff --git a/src/insets/InsetText.h b/src/insets/InsetText.h index 77dcfa9616..c0640a1115 100644 --- a/src/insets/InsetText.h +++ b/src/insets/InsetText.h @@ -169,7 +169,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// void addToToc(DocIterator const & di, bool output_active, UpdateType utype) const; diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp index 92372f02cb..560c9991c8 100644 --- a/src/mathed/InsetMathHull.cpp +++ b/src/mathed/InsetMathHull.cpp @@ -2381,11 +2381,13 @@ void InsetMathHull::toString(odocstream & os) const } -void InsetMathHull::forOutliner(docstring & os, size_t) const +void InsetMathHull::forOutliner(docstring & os, size_t const, bool const) const { odocstringstream ods; OutputParams op(0); op.for_toc = true; + // FIXME: this results in spilling TeX into the LyXHTML output since the + // outliner is used to generate the LyXHTML list of figures/etc. plaintext(ods, op); os += ods.str(); } diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h index b4a5be06c2..9598ad4281 100644 --- a/src/mathed/InsetMathHull.h +++ b/src/mathed/InsetMathHull.h @@ -152,7 +152,7 @@ public: /// void toString(odocstream &) const; /// - void forOutliner(docstring &, size_t) const; + void forOutliner(docstring &, size_t const, bool const) const; /// get notification when the cursor leaves this inset bool notifyCursorLeaves(Cursor const & old, Cursor & cur); diff --git a/src/support/filetools.cpp b/src/support/filetools.cpp index db07d56e61..6acfdf0df8 100644 --- a/src/support/filetools.cpp +++ b/src/support/filetools.cpp @@ -943,12 +943,10 @@ docstring const makeDisplayPath(string const & path, unsigned int threshold) // Yes, filename itself is too long. // Pick the start and the end of the filename. dstr = from_utf8(onlyFileName(path)); - docstring const head = dstr.substr(0, threshold / 2 - 3); - - docstring::size_type len = dstr.length(); - docstring const tail = - dstr.substr(len - threshold / 2 - 2, len - 1); - dstr = head + from_ascii("...") + tail; + docstring::size_type const len = dstr.length(); + if (len >= threshold) + dstr = support::truncateWithEllipsis(dstr, threshold / 2) + + dstr.substr(len - threshold / 2 - 2, len - 1); } return from_utf8(os::external_path(prefix + to_utf8(dstr))); diff --git a/src/support/lstrings.cpp b/src/support/lstrings.cpp index 5e68b755d0..d06458bd81 100644 --- a/src/support/lstrings.cpp +++ b/src/support/lstrings.cpp @@ -1201,6 +1201,17 @@ docstring const escape(docstring const & lab) } +bool truncateWithEllipsis(docstring & str, size_t const len) +{ + if (str.size() <= len) + return false; + str.resize(len); + if (len > 0) + str[len - 1] = 0x2026;// HORIZONTAL ELLIPSIS + return true; +} + + namespace { // this doesn't check whether str is empty, so do that first. @@ -1224,7 +1235,7 @@ vector wrapToVec(docstring const & str, int ind, size_t const i = s.find_last_of(' ', width - 1); if (i == docstring::npos || i <= size_t(ind)) { // no space found - s = s.substr(0, width - 3) + "..."; + truncateWithEllipsis(s, width); break; } retval.push_back(s.substr(0, i)); @@ -1253,7 +1264,6 @@ docstring wrap(docstring const & str, int const ind, size_t const width) docstring wrapParas(docstring const & str, int const indent, size_t const width, size_t const maxlines) { - docstring const dots = from_ascii("..."); if (str.empty()) return docstring(); @@ -1272,15 +1282,15 @@ docstring wrapParas(docstring const & str, int const indent, tmp.resize(maxlines - curlines); docstring last = tmp.back(); size_t const lsize = last.size(); - if (lsize > width - 3) { - size_t const i = last.find_last_of(' ', width - 3); + if (lsize > width - 1) { + size_t const i = last.find_last_of(' ', width - 1); if (i == docstring::npos || i <= size_t(indent)) // no space found - last = last.substr(0, lsize - 3) + dots; + truncateWithEllipsis(last, lsize); else - last = last.substr(0, i) + dots; + truncateWithEllipsis(last, i); } else - last += dots; + last.push_back(0x2026);//HORIZONTAL ELLIPSIS tmp.pop_back(); tmp.push_back(last); } diff --git a/src/support/lstrings.h b/src/support/lstrings.h index 395e18e04d..269b5a6564 100644 --- a/src/support/lstrings.h +++ b/src/support/lstrings.h @@ -266,6 +266,29 @@ docstring const rsplit(docstring const & a, char_type delim); /// problems in latex labels. docstring const escape(docstring const & lab); +/// Truncates a string with an ellipsis at the end. Leaves str unchanged and +/// returns false if it is shorter than len. Otherwise resizes str to len, with +/// U+2026 HORIZONTAL ELLIPSIS at the end, and returns true. +/// +/// Warning (Unicode): The cases where we want to truncate the text and it does +/// not end up converted into a QString for UI display must be really +/// rare. Whenever possible, we should prefer calling QFontMetrics::elidedText() +/// instead, which takes into account the actual length on the screen and the +/// layout direction (RTL or LTR). Or a similar function taking into account the +/// font metrics from the buffer view, which still has to be defined. Or set up +/// the widgets such that Qt elides the string automatically with the exact +/// needed width. Recall that not only graphemes vary greatly in width, but also +/// can be made of several code points. See: +/// +/// +/// What is acceptable is when we know that the string is probably going to be +/// elided by Qt anyway, and len is chosen such that our own ellipsis will only +/// be displayed in worst-case scenarios. +/// +/// FIXME: apply those principles in the current code. +/// +bool truncateWithEllipsis(docstring & str, size_t const len); + /// Word-wraps the provided docstring, returning a line-broken string /// of width no wider than width, with the string broken at spaces. /// If the string cannot be broken appropriately, it returns something @@ -274,6 +297,10 @@ docstring const escape(docstring const & lab); /// If indent is positive, then the first line is indented that many /// spaces. If it is negative, then successive lines are indented, as /// if the first line were "outdented". +/// +/// Warning (Unicode): uses truncateWithEllipsis() internally. Therefore it is +/// subject to the same warning and FIXME as above. +/// docstring wrap(docstring const & str, int const indent = 0, size_t const width = 80); @@ -281,6 +308,10 @@ docstring wrap(docstring const & str, int const indent = 0, /// that may contain embedded newlines. /// \param numlines Don't return more than numlines lines. If numlines /// is 0, we return everything. +/// +/// Warning (Unicode): uses truncateWithEllipsis() internally. Therefore it is +/// subject to the same warning and FIXME as above. +/// docstring wrapParas(docstring const & str, int const indent = 0, size_t const width = 80, size_t const maxlines = 10);