annotate widgets/TextAbbrev.cpp @ 1614:c6f5c822b10d

Fix potential divide-by-zero (depending on a race elsewhere)
author Chris Cannam
date Tue, 30 Jun 2020 10:56:56 +0100
parents d39db4673676
children
rev   line source
Chris@376 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@376 2
Chris@376 3 /*
Chris@376 4 Sonic Visualiser
Chris@376 5 An audio file viewer and annotation editor.
Chris@376 6 Centre for Digital Music, Queen Mary, University of London.
Chris@376 7 This file copyright 2006-2007 Chris Cannam and QMUL.
Chris@376 8
Chris@376 9 This program is free software; you can redistribute it and/or
Chris@376 10 modify it under the terms of the GNU General Public License as
Chris@376 11 published by the Free Software Foundation; either version 2 of the
Chris@376 12 License, or (at your option) any later version. See the file
Chris@376 13 COPYING included with this distribution for more information.
Chris@376 14 */
Chris@376 15
Chris@376 16 #include "TextAbbrev.h"
Chris@376 17
Chris@376 18 #include <QFontMetrics>
Chris@376 19 #include <QApplication>
Chris@376 20
Chris@376 21 #include <iostream>
Chris@376 22
Chris@376 23 QString
Chris@376 24 TextAbbrev::getDefaultEllipsis()
Chris@376 25 {
Chris@376 26 return "...";
Chris@376 27 }
Chris@376 28
Chris@376 29 int
Chris@376 30 TextAbbrev::getFuzzLength(QString ellipsis)
Chris@376 31 {
Chris@376 32 int len = ellipsis.length();
Chris@376 33 if (len < 3) return len + 3;
Chris@376 34 else if (len > 5) return len + 5;
Chris@376 35 else return len * 2;
Chris@376 36 }
Chris@376 37
Chris@376 38 int
Chris@376 39 TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
Chris@376 40 {
Chris@1478 41 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1478 42 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1478 43 // which is too new for us
Chris@1478 44 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1478 45
Chris@376 46 int width = metrics.width(ellipsis);
Chris@376 47 return width * 2;
Chris@376 48 }
Chris@376 49
Chris@376 50 QString
Chris@376 51 TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
Chris@376 52 QString ellipsis)
Chris@376 53 {
Chris@376 54 switch (policy) {
Chris@376 55
Chris@376 56 case ElideEnd:
Chris@376 57 case ElideEndAndCommonPrefixes:
Chris@376 58 text = text.left(characters) + ellipsis;
Chris@376 59 break;
Chris@376 60
Chris@376 61 case ElideStart:
Chris@376 62 text = ellipsis + text.right(characters);
Chris@376 63 break;
Chris@376 64
Chris@376 65 case ElideMiddle:
Chris@376 66 if (characters > 2) {
Chris@376 67 text = text.left(characters/2 + 1) + ellipsis
Chris@376 68 + text.right(characters - (characters/2 + 1));
Chris@376 69 } else {
Chris@376 70 text = text.left(characters) + ellipsis;
Chris@376 71 }
Chris@376 72 break;
Chris@376 73 }
Chris@376 74
Chris@376 75 return text;
Chris@376 76 }
Chris@376 77
Chris@376 78 QString
Chris@376 79 TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
Chris@376 80 QString ellipsis)
Chris@376 81 {
Chris@376 82 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@376 83 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
Chris@376 84 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
Chris@376 85 if (text.length() <= maxLength + fl) return text;
Chris@376 86
Chris@376 87 int truncated = maxLength - ellipsis.length();
Chris@376 88 return abbreviateTo(text, truncated, policy, ellipsis);
Chris@376 89 }
Chris@376 90
Chris@376 91 QString
Chris@376 92 TextAbbrev::abbreviate(QString text,
Chris@376 93 const QFontMetrics &metrics, int &maxWidth,
Chris@376 94 Policy policy, QString ellipsis)
Chris@376 95 {
Chris@376 96 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@376 97
Chris@376 98 int tw = metrics.width(text);
Chris@376 99
Chris@376 100 if (tw <= maxWidth) {
Chris@376 101 maxWidth = tw;
Chris@376 102 return text;
Chris@376 103 }
Chris@376 104
Chris@376 105 int truncated = text.length();
Chris@376 106 QString original = text;
Chris@376 107
Chris@376 108 while (tw > maxWidth && truncated > 1) {
Chris@376 109
Chris@376 110 truncated--;
Chris@376 111
Chris@376 112 if (truncated > ellipsis.length()) {
Chris@376 113 text = abbreviateTo(original, truncated, policy, ellipsis);
Chris@376 114 } else {
Chris@376 115 break;
Chris@376 116 }
Chris@376 117
Chris@376 118 tw = metrics.width(text);
Chris@376 119 }
Chris@376 120
Chris@376 121 maxWidth = tw;
Chris@376 122 return text;
Chris@376 123 }
Chris@376 124
Chris@376 125 QStringList
Chris@376 126 TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
Chris@376 127 Policy policy, bool fuzzy, QString ellipsis)
Chris@376 128 {
Chris@376 129 if (policy == ElideEndAndCommonPrefixes &&
Chris@376 130 texts.size() > 1) {
Chris@376 131
Chris@376 132 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@376 133 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
Chris@376 134 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
Chris@376 135
Chris@376 136 int maxOrigLength = 0;
Chris@376 137 for (int i = 0; i < texts.size(); ++i) {
Chris@376 138 int len = texts[i].length();
Chris@376 139 if (len > maxOrigLength) maxOrigLength = len;
Chris@376 140 }
Chris@376 141 if (maxOrigLength <= maxLength + fl) return texts;
Chris@376 142
Chris@376 143 return abbreviate(elidePrefixes
Chris@376 144 (texts, maxOrigLength - maxLength, ellipsis),
Chris@376 145 maxLength, ElideEnd, fuzzy, ellipsis);
Chris@376 146 }
Chris@376 147
Chris@376 148 QStringList results;
Chris@376 149 for (int i = 0; i < texts.size(); ++i) {
Chris@376 150 results.push_back
Chris@376 151 (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
Chris@376 152 }
Chris@376 153 return results;
Chris@376 154 }
Chris@376 155
Chris@376 156 QStringList
Chris@376 157 TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
Chris@376 158 int &maxWidth, Policy policy, QString ellipsis)
Chris@376 159 {
Chris@376 160 if (policy == ElideEndAndCommonPrefixes &&
Chris@376 161 texts.size() > 1) {
Chris@376 162
Chris@376 163 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@376 164
Chris@376 165 int maxOrigWidth = 0;
Chris@376 166 for (int i = 0; i < texts.size(); ++i) {
Chris@376 167 int w = metrics.width(texts[i]);
Chris@376 168 if (w > maxOrigWidth) maxOrigWidth = w;
Chris@376 169 }
Chris@376 170
Chris@376 171 return abbreviate(elidePrefixes(texts, metrics,
Chris@376 172 maxOrigWidth - maxWidth, ellipsis),
Chris@376 173 metrics, maxWidth, ElideEnd, ellipsis);
Chris@376 174 }
Chris@376 175
Chris@376 176 QStringList results;
Chris@376 177 int maxAbbrWidth = 0;
Chris@376 178 for (int i = 0; i < texts.size(); ++i) {
Chris@376 179 int width = maxWidth;
Chris@376 180 QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
Chris@376 181 if (width > maxAbbrWidth) maxAbbrWidth = width;
Chris@376 182 results.push_back(abbr);
Chris@376 183 }
Chris@376 184 maxWidth = maxAbbrWidth;
Chris@376 185 return results;
Chris@376 186 }
Chris@376 187
Chris@376 188 QStringList
Chris@376 189 TextAbbrev::elidePrefixes(const QStringList &texts,
Chris@376 190 int targetReduction,
Chris@376 191 QString ellipsis)
Chris@376 192 {
Chris@376 193 if (texts.empty()) return texts;
Chris@376 194 int plen = getPrefixLength(texts);
Chris@376 195 int fl = getFuzzLength(ellipsis);
Chris@376 196 if (plen < fl) return texts;
Chris@376 197
Chris@376 198 QString prefix = texts[0].left(plen);
Chris@376 199 int truncated = plen;
Chris@376 200 if (plen >= targetReduction + fl) {
Chris@376 201 truncated = plen - targetReduction;
Chris@376 202 } else {
Chris@376 203 truncated = fl;
Chris@376 204 }
Chris@376 205 prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
Chris@376 206
Chris@376 207 QStringList results;
Chris@376 208 for (int i = 0; i < texts.size(); ++i) {
Chris@376 209 results.push_back
Chris@376 210 (prefix + texts[i].right(texts[i].length() - plen));
Chris@376 211 }
Chris@376 212 return results;
Chris@376 213 }
Chris@376 214
Chris@376 215 QStringList
Chris@376 216 TextAbbrev::elidePrefixes(const QStringList &texts,
Chris@376 217 const QFontMetrics &metrics,
Chris@376 218 int targetWidthReduction,
Chris@376 219 QString ellipsis)
Chris@376 220 {
Chris@376 221 if (texts.empty()) return texts;
Chris@376 222 int plen = getPrefixLength(texts);
Chris@376 223 int fl = getFuzzLength(ellipsis);
Chris@376 224 if (plen < fl) return texts;
Chris@376 225
Chris@376 226 QString prefix = texts[0].left(plen);
Chris@376 227 int pwid = metrics.width(prefix);
Chris@376 228 int twid = pwid - targetWidthReduction;
Chris@376 229 if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
Chris@376 230 prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
Chris@376 231
Chris@376 232 QStringList results;
Chris@376 233 for (int i = 0; i < texts.size(); ++i) {
Chris@376 234 results.push_back
Chris@376 235 (prefix + texts[i].right(texts[i].length() - plen));
Chris@376 236 }
Chris@376 237 return results;
Chris@376 238 }
Chris@376 239
Chris@376 240 static bool
Chris@376 241 havePrefix(QString prefix, const QStringList &texts)
Chris@376 242 {
Chris@376 243 for (int i = 1; i < texts.size(); ++i) {
Chris@376 244 if (!texts[i].startsWith(prefix)) return false;
Chris@376 245 }
Chris@376 246 return true;
Chris@376 247 }
Chris@376 248
Chris@376 249 int
Chris@376 250 TextAbbrev::getPrefixLength(const QStringList &texts)
Chris@376 251 {
Chris@376 252 QString reference = texts[0];
Chris@376 253
Chris@376 254 if (reference == "" || havePrefix(reference, texts)) {
Chris@376 255 return reference.length();
Chris@376 256 }
Chris@376 257
Chris@376 258 int candidate = reference.length();
Chris@376 259 QString splitChars(";:,./#-!()$_+=[]{}\\");
Chris@376 260
Chris@376 261 while (--candidate > 1) {
Chris@376 262 if (splitChars.contains(reference[candidate])) {
Chris@376 263 if (havePrefix(reference.left(candidate), texts)) {
Chris@376 264 break;
Chris@376 265 }
Chris@376 266 }
Chris@376 267 }
Chris@376 268
Chris@587 269 // SVDEBUG << "TextAbbrev::getPrefixLength: prefix length is " << candidate << endl;
Chris@376 270 // for (int i = 0; i < texts.size(); ++i) {
Chris@682 271 // cerr << texts[i].left(candidate) << "|" << texts[i].right(texts[i].length() - candidate) << endl;
Chris@376 272 // }
Chris@376 273
Chris@376 274 return candidate;
Chris@376 275 }
Chris@376 276