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