view src/textabbrev.cpp @ 703:bfd6014ee278

URL typo fix
author Chris Cannam
date Wed, 12 Dec 2018 10:30:21 +0000
parents ae67ea0af696
children
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    EasyMercurial

    Based on HgExplorer by Jari Korhonen
    Copyright (c) 2010 Jari Korhonen
    Copyright (c) 2013 Chris Cannam
    Copyright (c) 2013 Queen Mary, University of London
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include "textabbrev.h"
#include "debug.h"

#include <QFontMetrics>
#include <QApplication>

#include <iostream>

QString
TextAbbrev::getDefaultEllipsis()
{
    return "...";
}

int
TextAbbrev::getFuzzLength(QString ellipsis)
{
    int len = ellipsis.length();
    if (len < 3) return len + 3;
    else if (len > 5) return len + 5;
    else return len * 2;
}

int
TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
{
    int width = metrics.width(ellipsis);
    return width * 2;
}

QString
TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
                         QString ellipsis)
{
    switch (policy) {

    case ElideEnd:
    case ElideEndAndCommonPrefixes:
        text = text.left(characters) + ellipsis;
        break;
        
    case ElideStart:
        text = ellipsis + text.right(characters);
        break;

    case ElideMiddle:
        if (characters > 2) {
            text = text.left(characters/2 + 1) + ellipsis
                + text.right(characters - (characters/2 + 1));
        } else {
            text = text.left(characters) + ellipsis;
        }
        break;
    }

    return text;
}

QString
TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
                       QString ellipsis)
{
    if (ellipsis == "") ellipsis = getDefaultEllipsis();
    int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
    if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
    if (text.length() <= maxLength + fl) return text;

    int truncated = maxLength - ellipsis.length();
    return abbreviateTo(text, truncated, policy, ellipsis);
}

QString
TextAbbrev::abbreviate(QString text,
                       const QFontMetrics &metrics, int &maxWidth,
                       Policy policy, QString ellipsis, int wrapLines)
{
    if (ellipsis == "") ellipsis = getDefaultEllipsis();

//    std::cerr << "TextAbbrev::abbreviate: text = " << text << std::endl;

    int tw = metrics.width(text);
    int tl = text.length();

//    std::cerr << "\n*** TextAbbrev::abbreviate: tw = " << tw << "\n***\n" << std::endl;

    if (tw <= maxWidth) {
        maxWidth = tw;
        return text;
    }

    QString original = text;

    int acw = metrics.averageCharWidth();

    if (wrapLines < 2) {

//        std::cerr << "only " << wrapLines << " line(s) remaining, going by character..." << std::endl;

        int truncateTo = tl;

        while (tw > maxWidth && truncateTo > 1) {

            if (tw > maxWidth + acw * (ellipsis.length() + 5)) {
                float ratio = float(maxWidth) / float(tw);
                truncateTo = int(truncateTo * ratio * 1.2) + 10;
                if (truncateTo >= tl) truncateTo = tl - 1;
            } else {
                truncateTo--;
            }
            
//            std::cerr << "truncating from " << tl << " to " << truncateTo << std::endl;

            if (truncateTo > ellipsis.length()) {
                text = abbreviateTo(original, truncateTo, policy, ellipsis);
            } else {
                break;
            }
            
            tw = metrics.width(text);
            tl = text.length() - ellipsis.length();
        }

//        std::cerr << "done, final tw = " << tw << std::endl;
        
        maxWidth = std::max(maxWidth, tw);
        return text;

    } else {
        
        QStringList words = text.split(' ', QString::SkipEmptyParts);
        text = "";

        tw = 0;
        int i = 0;
        QString good = "";
        int lastUsed = 0;

//        std::cerr << "have " << wrapLines << " lines remaining, going by word..." << std::endl;

        while (tw < maxWidth && i < words.size()) {
            if (text != "") text += " ";
            text += words[i++];
            tw = metrics.width(text);
            if (tw < maxWidth) {
                good = text;
                lastUsed = i;
            }
        }

//        std::cerr << "done" << std::endl;
            
        if (tw < maxWidth) {
            maxWidth = tw;
            return text;
        }

        text = good;

        QString remainder;
        while (lastUsed < words.size()) {
            if (remainder != "") remainder += " ";
            remainder += words[lastUsed++];
        }
        remainder = abbreviate(remainder, metrics, maxWidth,
                               policy, ellipsis, wrapLines - 1);

        maxWidth = std::max(maxWidth, tw);
        text = text + '\n' + remainder;
        return text;
    }
}

QStringList
TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
                       Policy policy, bool fuzzy, QString ellipsis)
{
    if (policy == ElideEndAndCommonPrefixes &&
        texts.size() > 1) {

        if (ellipsis == "") ellipsis = getDefaultEllipsis();
        int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
        if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;

        int maxOrigLength = 0;
        for (int i = 0; i < texts.size(); ++i) {
            int len = texts[i].length();
            if (len > maxOrigLength) maxOrigLength = len;
        }
        if (maxOrigLength <= maxLength + fl) return texts;

        return abbreviate(elidePrefixes
                          (texts, maxOrigLength - maxLength, ellipsis),
                          maxLength, ElideEnd, fuzzy, ellipsis);
    }

    QStringList results;
    for (int i = 0; i < texts.size(); ++i) {
        results.push_back
            (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
    }
    return results;
}

QStringList
TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
                       int &maxWidth, Policy policy, QString ellipsis)
{
    if (policy == ElideEndAndCommonPrefixes &&
        texts.size() > 1) {

        if (ellipsis == "") ellipsis = getDefaultEllipsis();

        int maxOrigWidth = 0;
        for (int i = 0; i < texts.size(); ++i) {
            int w = metrics.width(texts[i]);
            if (w > maxOrigWidth) maxOrigWidth = w;
        }

        return abbreviate(elidePrefixes(texts, metrics,
                                        maxOrigWidth - maxWidth, ellipsis),
                          metrics, maxWidth, ElideEnd, ellipsis);
    }

    QStringList results;
    int maxAbbrWidth = 0;
    for (int i = 0; i < texts.size(); ++i) {
        int width = maxWidth;
        QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
        if (width > maxAbbrWidth) maxAbbrWidth = width;
        results.push_back(abbr);
    }
    maxWidth = maxAbbrWidth;
    return results;
}

QStringList
TextAbbrev::elidePrefixes(const QStringList &texts,
                          int targetReduction,
                          QString ellipsis)
{
    if (texts.empty()) return texts;
    int plen = getPrefixLength(texts);
    int fl = getFuzzLength(ellipsis);
    if (plen < fl) return texts;

    QString prefix = texts[0].left(plen);
    int truncated = plen;
    if (plen >= targetReduction + fl) {
        truncated = plen - targetReduction;
    } else {
        truncated = fl;
    }
    prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);

    QStringList results;
    for (int i = 0; i < texts.size(); ++i) {
        results.push_back
            (prefix + texts[i].right(texts[i].length() - plen));
    }
    return results;
}

QStringList
TextAbbrev::elidePrefixes(const QStringList &texts,
                          const QFontMetrics &metrics,
                          int targetWidthReduction,
                          QString ellipsis)
{
    if (texts.empty()) return texts;
    int plen = getPrefixLength(texts);
    int fl = getFuzzLength(ellipsis);
    if (plen < fl) return texts;

    QString prefix = texts[0].left(plen);
    int pwid = metrics.width(prefix);
    int twid = pwid - targetWidthReduction;
    if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
    prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);

    QStringList results;
    for (int i = 0; i < texts.size(); ++i) {
        results.push_back
            (prefix + texts[i].right(texts[i].length() - plen));
    }
    return results;
}

static bool
havePrefix(QString prefix, const QStringList &texts)
{
    for (int i = 1; i < texts.size(); ++i) {
        if (!texts[i].startsWith(prefix)) return false;
    }
    return true;
}

int
TextAbbrev::getPrefixLength(const QStringList &texts)
{
    QString reference = texts[0];

    if (reference == "" || havePrefix(reference, texts)) {
        return reference.length();
    }

    int candidate = reference.length();
    QString splitChars(";:,./#-!()$_+=[]{}\\");

    while (--candidate > 1) {
        if (splitChars.contains(reference[candidate])) {
            if (havePrefix(reference.left(candidate), texts)) {
                break;
            }
        }
    }

//    std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
//    for (int i = 0; i < texts.size(); ++i) {
//        std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
//    }

    return candidate;
}