annotate src/textabbrev.cpp @ 571:012ba1b83328

Show cancel button with progress bar only when running an operation that it makes sense to cancel (we don't really want people cancelling e.g. initial folder scan because it would leave things in an inconsistent state)
author Chris Cannam
date Thu, 01 Mar 2012 22:53:54 +0000
parents 533519ebc0cb
children ae67ea0af696
rev   line source
Chris@50 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@50 2
Chris@50 3 /*
Chris@57 4 EasyMercurial
Chris@57 5
Chris@57 6 Based on HgExplorer by Jari Korhonen
Chris@57 7 Copyright (c) 2010 Jari Korhonen
Chris@560 8 Copyright (c) 2012 Chris Cannam
Chris@560 9 Copyright (c) 2012 Queen Mary, University of London
Chris@50 10
Chris@50 11 This program is free software; you can redistribute it and/or
Chris@50 12 modify it under the terms of the GNU General Public License as
Chris@50 13 published by the Free Software Foundation; either version 2 of the
Chris@50 14 License, or (at your option) any later version. See the file
Chris@50 15 COPYING included with this distribution for more information.
Chris@50 16 */
Chris@50 17
Chris@50 18 #include "textabbrev.h"
Chris@463 19 #include "debug.h"
Chris@50 20
Chris@50 21 #include <QFontMetrics>
Chris@50 22 #include <QApplication>
Chris@50 23
Chris@50 24 #include <iostream>
Chris@50 25
Chris@50 26 QString
Chris@50 27 TextAbbrev::getDefaultEllipsis()
Chris@50 28 {
Chris@50 29 return "...";
Chris@50 30 }
Chris@50 31
Chris@50 32 int
Chris@50 33 TextAbbrev::getFuzzLength(QString ellipsis)
Chris@50 34 {
Chris@50 35 int len = ellipsis.length();
Chris@50 36 if (len < 3) return len + 3;
Chris@50 37 else if (len > 5) return len + 5;
Chris@50 38 else return len * 2;
Chris@50 39 }
Chris@50 40
Chris@50 41 int
Chris@50 42 TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
Chris@50 43 {
Chris@50 44 int width = metrics.width(ellipsis);
Chris@50 45 return width * 2;
Chris@50 46 }
Chris@50 47
Chris@50 48 QString
Chris@50 49 TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
Chris@50 50 QString ellipsis)
Chris@50 51 {
Chris@50 52 switch (policy) {
Chris@50 53
Chris@50 54 case ElideEnd:
Chris@50 55 case ElideEndAndCommonPrefixes:
Chris@50 56 text = text.left(characters) + ellipsis;
Chris@50 57 break;
Chris@50 58
Chris@50 59 case ElideStart:
Chris@50 60 text = ellipsis + text.right(characters);
Chris@50 61 break;
Chris@50 62
Chris@50 63 case ElideMiddle:
Chris@50 64 if (characters > 2) {
Chris@50 65 text = text.left(characters/2 + 1) + ellipsis
Chris@50 66 + text.right(characters - (characters/2 + 1));
Chris@50 67 } else {
Chris@50 68 text = text.left(characters) + ellipsis;
Chris@50 69 }
Chris@50 70 break;
Chris@50 71 }
Chris@50 72
Chris@50 73 return text;
Chris@50 74 }
Chris@50 75
Chris@50 76 QString
Chris@50 77 TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
Chris@50 78 QString ellipsis)
Chris@50 79 {
Chris@50 80 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@50 81 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
Chris@50 82 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
Chris@50 83 if (text.length() <= maxLength + fl) return text;
Chris@50 84
Chris@50 85 int truncated = maxLength - ellipsis.length();
Chris@50 86 return abbreviateTo(text, truncated, policy, ellipsis);
Chris@50 87 }
Chris@50 88
Chris@50 89 QString
Chris@50 90 TextAbbrev::abbreviate(QString text,
Chris@50 91 const QFontMetrics &metrics, int &maxWidth,
Chris@53 92 Policy policy, QString ellipsis, int wrapLines)
Chris@50 93 {
Chris@50 94 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@50 95
Chris@463 96 // std::cerr << "TextAbbrev::abbreviate: text = " << text << std::endl;
Chris@463 97
Chris@50 98 int tw = metrics.width(text);
Chris@463 99 int tl = text.length();
Chris@463 100
Chris@463 101 // std::cerr << "\n*** TextAbbrev::abbreviate: tw = " << tw << "\n***\n" << std::endl;
Chris@50 102
Chris@50 103 if (tw <= maxWidth) {
Chris@50 104 maxWidth = tw;
Chris@50 105 return text;
Chris@50 106 }
Chris@50 107
Chris@50 108 QString original = text;
Chris@50 109
Chris@463 110 int acw = metrics.averageCharWidth();
Chris@463 111
Chris@53 112 if (wrapLines < 2) {
Chris@50 113
Chris@463 114 // std::cerr << "only " << wrapLines << " line(s) remaining, going by character..." << std::endl;
Chris@50 115
Chris@463 116 int truncateTo = tl;
Chris@463 117
Chris@463 118 while (tw > maxWidth && truncateTo > 1) {
Chris@463 119
Chris@464 120 if (tw > maxWidth + acw * (ellipsis.length() + 5)) {
Chris@463 121 float ratio = float(maxWidth) / float(tw);
Chris@463 122 truncateTo = int(truncateTo * ratio * 1.2) + 10;
Chris@463 123 if (truncateTo >= tl) truncateTo = tl - 1;
Chris@463 124 } else {
Chris@463 125 truncateTo--;
Chris@463 126 }
Chris@53 127
Chris@463 128 // std::cerr << "truncating from " << tl << " to " << truncateTo << std::endl;
Chris@463 129
Chris@463 130 if (truncateTo > ellipsis.length()) {
Chris@463 131 text = abbreviateTo(original, truncateTo, policy, ellipsis);
Chris@53 132 } else {
Chris@53 133 break;
Chris@53 134 }
Chris@53 135
Chris@53 136 tw = metrics.width(text);
Chris@463 137 tl = text.length() - ellipsis.length();
Chris@53 138 }
Chris@463 139
Chris@463 140 // std::cerr << "done, final tw = " << tw << std::endl;
Chris@53 141
Chris@464 142 maxWidth = std::max(maxWidth, tw);
Chris@53 143 return text;
Chris@53 144
Chris@53 145 } else {
Chris@53 146
Chris@53 147 QStringList words = text.split(' ', QString::SkipEmptyParts);
Chris@53 148 text = "";
Chris@53 149
Chris@53 150 tw = 0;
Chris@53 151 int i = 0;
Chris@53 152 QString good = "";
Chris@53 153 int lastUsed = 0;
Chris@463 154
Chris@463 155 // std::cerr << "have " << wrapLines << " lines remaining, going by word..." << std::endl;
Chris@463 156
Chris@53 157 while (tw < maxWidth && i < words.size()) {
Chris@53 158 if (text != "") text += " ";
Chris@53 159 text += words[i++];
Chris@53 160 tw = metrics.width(text);
Chris@53 161 if (tw < maxWidth) {
Chris@53 162 good = text;
Chris@53 163 lastUsed = i;
Chris@53 164 }
Chris@53 165 }
Chris@463 166
Chris@463 167 // std::cerr << "done" << std::endl;
Chris@53 168
Chris@53 169 if (tw < maxWidth) {
Chris@53 170 maxWidth = tw;
Chris@53 171 return text;
Chris@50 172 }
Chris@50 173
Chris@53 174 text = good;
Chris@53 175
Chris@53 176 QString remainder;
Chris@53 177 while (lastUsed < words.size()) {
Chris@53 178 if (remainder != "") remainder += " ";
Chris@53 179 remainder += words[lastUsed++];
Chris@53 180 }
Chris@53 181 remainder = abbreviate(remainder, metrics, maxWidth,
Chris@53 182 policy, ellipsis, wrapLines - 1);
Chris@53 183
Chris@53 184 maxWidth = std::max(maxWidth, tw);
Chris@53 185 text = text + '\n' + remainder;
Chris@53 186 return text;
Chris@50 187 }
Chris@50 188 }
Chris@50 189
Chris@50 190 QStringList
Chris@50 191 TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
Chris@50 192 Policy policy, bool fuzzy, QString ellipsis)
Chris@50 193 {
Chris@50 194 if (policy == ElideEndAndCommonPrefixes &&
Chris@50 195 texts.size() > 1) {
Chris@50 196
Chris@50 197 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@50 198 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
Chris@50 199 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
Chris@50 200
Chris@50 201 int maxOrigLength = 0;
Chris@50 202 for (int i = 0; i < texts.size(); ++i) {
Chris@50 203 int len = texts[i].length();
Chris@50 204 if (len > maxOrigLength) maxOrigLength = len;
Chris@50 205 }
Chris@50 206 if (maxOrigLength <= maxLength + fl) return texts;
Chris@50 207
Chris@50 208 return abbreviate(elidePrefixes
Chris@50 209 (texts, maxOrigLength - maxLength, ellipsis),
Chris@50 210 maxLength, ElideEnd, fuzzy, ellipsis);
Chris@50 211 }
Chris@50 212
Chris@50 213 QStringList results;
Chris@50 214 for (int i = 0; i < texts.size(); ++i) {
Chris@50 215 results.push_back
Chris@50 216 (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
Chris@50 217 }
Chris@50 218 return results;
Chris@50 219 }
Chris@50 220
Chris@50 221 QStringList
Chris@50 222 TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
Chris@50 223 int &maxWidth, Policy policy, QString ellipsis)
Chris@50 224 {
Chris@50 225 if (policy == ElideEndAndCommonPrefixes &&
Chris@50 226 texts.size() > 1) {
Chris@50 227
Chris@50 228 if (ellipsis == "") ellipsis = getDefaultEllipsis();
Chris@50 229
Chris@50 230 int maxOrigWidth = 0;
Chris@50 231 for (int i = 0; i < texts.size(); ++i) {
Chris@50 232 int w = metrics.width(texts[i]);
Chris@50 233 if (w > maxOrigWidth) maxOrigWidth = w;
Chris@50 234 }
Chris@50 235
Chris@50 236 return abbreviate(elidePrefixes(texts, metrics,
Chris@50 237 maxOrigWidth - maxWidth, ellipsis),
Chris@50 238 metrics, maxWidth, ElideEnd, ellipsis);
Chris@50 239 }
Chris@50 240
Chris@50 241 QStringList results;
Chris@50 242 int maxAbbrWidth = 0;
Chris@50 243 for (int i = 0; i < texts.size(); ++i) {
Chris@50 244 int width = maxWidth;
Chris@50 245 QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
Chris@50 246 if (width > maxAbbrWidth) maxAbbrWidth = width;
Chris@50 247 results.push_back(abbr);
Chris@50 248 }
Chris@50 249 maxWidth = maxAbbrWidth;
Chris@50 250 return results;
Chris@50 251 }
Chris@50 252
Chris@50 253 QStringList
Chris@50 254 TextAbbrev::elidePrefixes(const QStringList &texts,
Chris@50 255 int targetReduction,
Chris@50 256 QString ellipsis)
Chris@50 257 {
Chris@50 258 if (texts.empty()) return texts;
Chris@50 259 int plen = getPrefixLength(texts);
Chris@50 260 int fl = getFuzzLength(ellipsis);
Chris@50 261 if (plen < fl) return texts;
Chris@50 262
Chris@50 263 QString prefix = texts[0].left(plen);
Chris@50 264 int truncated = plen;
Chris@50 265 if (plen >= targetReduction + fl) {
Chris@50 266 truncated = plen - targetReduction;
Chris@50 267 } else {
Chris@50 268 truncated = fl;
Chris@50 269 }
Chris@50 270 prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
Chris@50 271
Chris@50 272 QStringList results;
Chris@50 273 for (int i = 0; i < texts.size(); ++i) {
Chris@50 274 results.push_back
Chris@50 275 (prefix + texts[i].right(texts[i].length() - plen));
Chris@50 276 }
Chris@50 277 return results;
Chris@50 278 }
Chris@50 279
Chris@50 280 QStringList
Chris@50 281 TextAbbrev::elidePrefixes(const QStringList &texts,
Chris@50 282 const QFontMetrics &metrics,
Chris@50 283 int targetWidthReduction,
Chris@50 284 QString ellipsis)
Chris@50 285 {
Chris@50 286 if (texts.empty()) return texts;
Chris@50 287 int plen = getPrefixLength(texts);
Chris@50 288 int fl = getFuzzLength(ellipsis);
Chris@50 289 if (plen < fl) return texts;
Chris@50 290
Chris@50 291 QString prefix = texts[0].left(plen);
Chris@50 292 int pwid = metrics.width(prefix);
Chris@50 293 int twid = pwid - targetWidthReduction;
Chris@50 294 if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
Chris@50 295 prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
Chris@50 296
Chris@50 297 QStringList results;
Chris@50 298 for (int i = 0; i < texts.size(); ++i) {
Chris@50 299 results.push_back
Chris@50 300 (prefix + texts[i].right(texts[i].length() - plen));
Chris@50 301 }
Chris@50 302 return results;
Chris@50 303 }
Chris@50 304
Chris@50 305 static bool
Chris@50 306 havePrefix(QString prefix, const QStringList &texts)
Chris@50 307 {
Chris@50 308 for (int i = 1; i < texts.size(); ++i) {
Chris@50 309 if (!texts[i].startsWith(prefix)) return false;
Chris@50 310 }
Chris@50 311 return true;
Chris@50 312 }
Chris@50 313
Chris@50 314 int
Chris@50 315 TextAbbrev::getPrefixLength(const QStringList &texts)
Chris@50 316 {
Chris@50 317 QString reference = texts[0];
Chris@50 318
Chris@50 319 if (reference == "" || havePrefix(reference, texts)) {
Chris@50 320 return reference.length();
Chris@50 321 }
Chris@50 322
Chris@50 323 int candidate = reference.length();
Chris@50 324 QString splitChars(";:,./#-!()$_+=[]{}\\");
Chris@50 325
Chris@50 326 while (--candidate > 1) {
Chris@50 327 if (splitChars.contains(reference[candidate])) {
Chris@50 328 if (havePrefix(reference.left(candidate), texts)) {
Chris@50 329 break;
Chris@50 330 }
Chris@50 331 }
Chris@50 332 }
Chris@50 333
Chris@50 334 // std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
Chris@50 335 // for (int i = 0; i < texts.size(); ++i) {
Chris@50 336 // std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
Chris@50 337 // }
Chris@50 338
Chris@50 339 return candidate;
Chris@50 340 }
Chris@50 341