Chris@50: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@50: Chris@50: /* Chris@57: EasyMercurial Chris@57: Chris@57: Based on HgExplorer by Jari Korhonen Chris@57: Copyright (c) 2010 Jari Korhonen Chris@244: Copyright (c) 2011 Chris Cannam Chris@244: Copyright (c) 2011 Queen Mary, University of London Chris@50: Chris@50: This program is free software; you can redistribute it and/or Chris@50: modify it under the terms of the GNU General Public License as Chris@50: published by the Free Software Foundation; either version 2 of the Chris@50: License, or (at your option) any later version. See the file Chris@50: COPYING included with this distribution for more information. Chris@50: */ Chris@50: Chris@50: #include "textabbrev.h" Chris@50: Chris@50: #include Chris@50: #include Chris@50: Chris@50: #include Chris@50: Chris@50: QString Chris@50: TextAbbrev::getDefaultEllipsis() Chris@50: { Chris@50: return "..."; Chris@50: } Chris@50: Chris@50: int Chris@50: TextAbbrev::getFuzzLength(QString ellipsis) Chris@50: { Chris@50: int len = ellipsis.length(); Chris@50: if (len < 3) return len + 3; Chris@50: else if (len > 5) return len + 5; Chris@50: else return len * 2; Chris@50: } Chris@50: Chris@50: int Chris@50: TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis) Chris@50: { Chris@50: int width = metrics.width(ellipsis); Chris@50: return width * 2; Chris@50: } Chris@50: Chris@50: QString Chris@50: TextAbbrev::abbreviateTo(QString text, int characters, Policy policy, Chris@50: QString ellipsis) Chris@50: { Chris@50: switch (policy) { Chris@50: Chris@50: case ElideEnd: Chris@50: case ElideEndAndCommonPrefixes: Chris@50: text = text.left(characters) + ellipsis; Chris@50: break; Chris@50: Chris@50: case ElideStart: Chris@50: text = ellipsis + text.right(characters); Chris@50: break; Chris@50: Chris@50: case ElideMiddle: Chris@50: if (characters > 2) { Chris@50: text = text.left(characters/2 + 1) + ellipsis Chris@50: + text.right(characters - (characters/2 + 1)); Chris@50: } else { Chris@50: text = text.left(characters) + ellipsis; Chris@50: } Chris@50: break; Chris@50: } Chris@50: Chris@50: return text; Chris@50: } Chris@50: Chris@50: QString Chris@50: TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy, Chris@50: QString ellipsis) Chris@50: { Chris@50: if (ellipsis == "") ellipsis = getDefaultEllipsis(); Chris@50: int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); Chris@50: if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; Chris@50: if (text.length() <= maxLength + fl) return text; Chris@50: Chris@50: int truncated = maxLength - ellipsis.length(); Chris@50: return abbreviateTo(text, truncated, policy, ellipsis); Chris@50: } Chris@50: Chris@50: QString Chris@50: TextAbbrev::abbreviate(QString text, Chris@50: const QFontMetrics &metrics, int &maxWidth, Chris@53: Policy policy, QString ellipsis, int wrapLines) Chris@50: { Chris@50: if (ellipsis == "") ellipsis = getDefaultEllipsis(); Chris@50: Chris@50: int tw = metrics.width(text); Chris@50: Chris@50: if (tw <= maxWidth) { Chris@50: maxWidth = tw; Chris@50: return text; Chris@50: } Chris@50: Chris@50: int truncated = text.length(); Chris@50: QString original = text; Chris@50: Chris@53: if (wrapLines < 2) { Chris@50: Chris@53: while (tw > maxWidth && truncated > 1) { Chris@50: Chris@53: truncated--; Chris@53: Chris@53: if (truncated > ellipsis.length()) { Chris@53: text = abbreviateTo(original, truncated, policy, ellipsis); Chris@53: } else { Chris@53: break; Chris@53: } Chris@53: Chris@53: tw = metrics.width(text); Chris@53: } Chris@53: Chris@53: maxWidth = tw; Chris@53: return text; Chris@53: Chris@53: } else { Chris@53: Chris@53: QStringList words = text.split(' ', QString::SkipEmptyParts); Chris@53: text = ""; Chris@53: Chris@53: tw = 0; Chris@53: int i = 0; Chris@53: QString good = ""; Chris@53: int lastUsed = 0; Chris@53: while (tw < maxWidth && i < words.size()) { Chris@53: if (text != "") text += " "; Chris@53: text += words[i++]; Chris@53: tw = metrics.width(text); Chris@53: if (tw < maxWidth) { Chris@53: good = text; Chris@53: lastUsed = i; Chris@53: } Chris@53: } Chris@53: Chris@53: if (tw < maxWidth) { Chris@53: maxWidth = tw; Chris@53: return text; Chris@50: } Chris@50: Chris@53: text = good; Chris@53: Chris@53: QString remainder; Chris@53: while (lastUsed < words.size()) { Chris@53: if (remainder != "") remainder += " "; Chris@53: remainder += words[lastUsed++]; Chris@53: } Chris@53: remainder = abbreviate(remainder, metrics, maxWidth, Chris@53: policy, ellipsis, wrapLines - 1); Chris@53: Chris@53: maxWidth = std::max(maxWidth, tw); Chris@53: text = text + '\n' + remainder; Chris@53: return text; Chris@50: } Chris@50: } Chris@50: Chris@50: QStringList Chris@50: TextAbbrev::abbreviate(const QStringList &texts, int maxLength, Chris@50: Policy policy, bool fuzzy, QString ellipsis) Chris@50: { Chris@50: if (policy == ElideEndAndCommonPrefixes && Chris@50: texts.size() > 1) { Chris@50: Chris@50: if (ellipsis == "") ellipsis = getDefaultEllipsis(); Chris@50: int fl = (fuzzy ? getFuzzLength(ellipsis) : 0); Chris@50: if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1; Chris@50: Chris@50: int maxOrigLength = 0; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: int len = texts[i].length(); Chris@50: if (len > maxOrigLength) maxOrigLength = len; Chris@50: } Chris@50: if (maxOrigLength <= maxLength + fl) return texts; Chris@50: Chris@50: return abbreviate(elidePrefixes Chris@50: (texts, maxOrigLength - maxLength, ellipsis), Chris@50: maxLength, ElideEnd, fuzzy, ellipsis); Chris@50: } Chris@50: Chris@50: QStringList results; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: results.push_back Chris@50: (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis)); Chris@50: } Chris@50: return results; Chris@50: } Chris@50: Chris@50: QStringList Chris@50: TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics, Chris@50: int &maxWidth, Policy policy, QString ellipsis) Chris@50: { Chris@50: if (policy == ElideEndAndCommonPrefixes && Chris@50: texts.size() > 1) { Chris@50: Chris@50: if (ellipsis == "") ellipsis = getDefaultEllipsis(); Chris@50: Chris@50: int maxOrigWidth = 0; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: int w = metrics.width(texts[i]); Chris@50: if (w > maxOrigWidth) maxOrigWidth = w; Chris@50: } Chris@50: Chris@50: return abbreviate(elidePrefixes(texts, metrics, Chris@50: maxOrigWidth - maxWidth, ellipsis), Chris@50: metrics, maxWidth, ElideEnd, ellipsis); Chris@50: } Chris@50: Chris@50: QStringList results; Chris@50: int maxAbbrWidth = 0; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: int width = maxWidth; Chris@50: QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis); Chris@50: if (width > maxAbbrWidth) maxAbbrWidth = width; Chris@50: results.push_back(abbr); Chris@50: } Chris@50: maxWidth = maxAbbrWidth; Chris@50: return results; Chris@50: } Chris@50: Chris@50: QStringList Chris@50: TextAbbrev::elidePrefixes(const QStringList &texts, Chris@50: int targetReduction, Chris@50: QString ellipsis) Chris@50: { Chris@50: if (texts.empty()) return texts; Chris@50: int plen = getPrefixLength(texts); Chris@50: int fl = getFuzzLength(ellipsis); Chris@50: if (plen < fl) return texts; Chris@50: Chris@50: QString prefix = texts[0].left(plen); Chris@50: int truncated = plen; Chris@50: if (plen >= targetReduction + fl) { Chris@50: truncated = plen - targetReduction; Chris@50: } else { Chris@50: truncated = fl; Chris@50: } Chris@50: prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis); Chris@50: Chris@50: QStringList results; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: results.push_back Chris@50: (prefix + texts[i].right(texts[i].length() - plen)); Chris@50: } Chris@50: return results; Chris@50: } Chris@50: Chris@50: QStringList Chris@50: TextAbbrev::elidePrefixes(const QStringList &texts, Chris@50: const QFontMetrics &metrics, Chris@50: int targetWidthReduction, Chris@50: QString ellipsis) Chris@50: { Chris@50: if (texts.empty()) return texts; Chris@50: int plen = getPrefixLength(texts); Chris@50: int fl = getFuzzLength(ellipsis); Chris@50: if (plen < fl) return texts; Chris@50: Chris@50: QString prefix = texts[0].left(plen); Chris@50: int pwid = metrics.width(prefix); Chris@50: int twid = pwid - targetWidthReduction; Chris@50: if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2; Chris@50: prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis); Chris@50: Chris@50: QStringList results; Chris@50: for (int i = 0; i < texts.size(); ++i) { Chris@50: results.push_back Chris@50: (prefix + texts[i].right(texts[i].length() - plen)); Chris@50: } Chris@50: return results; Chris@50: } Chris@50: Chris@50: static bool Chris@50: havePrefix(QString prefix, const QStringList &texts) Chris@50: { Chris@50: for (int i = 1; i < texts.size(); ++i) { Chris@50: if (!texts[i].startsWith(prefix)) return false; Chris@50: } Chris@50: return true; Chris@50: } Chris@50: Chris@50: int Chris@50: TextAbbrev::getPrefixLength(const QStringList &texts) Chris@50: { Chris@50: QString reference = texts[0]; Chris@50: Chris@50: if (reference == "" || havePrefix(reference, texts)) { Chris@50: return reference.length(); Chris@50: } Chris@50: Chris@50: int candidate = reference.length(); Chris@50: QString splitChars(";:,./#-!()$_+=[]{}\\"); Chris@50: Chris@50: while (--candidate > 1) { Chris@50: if (splitChars.contains(reference[candidate])) { Chris@50: if (havePrefix(reference.left(candidate), texts)) { Chris@50: break; Chris@50: } Chris@50: } Chris@50: } Chris@50: Chris@50: // std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl; Chris@50: // for (int i = 0; i < texts.size(); ++i) { Chris@50: // std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl; Chris@50: // } Chris@50: Chris@50: return candidate; Chris@50: } Chris@50: