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
|