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@50
|
4 Sonic Visualiser
|
Chris@50
|
5 An audio file viewer and annotation editor.
|
Chris@50
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@50
|
7 This file copyright 2006-2007 Chris Cannam and QMUL.
|
Chris@50
|
8
|
Chris@50
|
9 This program is free software; you can redistribute it and/or
|
Chris@50
|
10 modify it under the terms of the GNU General Public License as
|
Chris@50
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@50
|
12 License, or (at your option) any later version. See the file
|
Chris@50
|
13 COPYING included with this distribution for more information.
|
Chris@50
|
14 */
|
Chris@50
|
15
|
Chris@50
|
16 #include "textabbrev.h"
|
Chris@50
|
17
|
Chris@50
|
18 #include <QFontMetrics>
|
Chris@50
|
19 #include <QApplication>
|
Chris@50
|
20
|
Chris@50
|
21 #include <iostream>
|
Chris@50
|
22
|
Chris@50
|
23 QString
|
Chris@50
|
24 TextAbbrev::getDefaultEllipsis()
|
Chris@50
|
25 {
|
Chris@50
|
26 return "...";
|
Chris@50
|
27 }
|
Chris@50
|
28
|
Chris@50
|
29 int
|
Chris@50
|
30 TextAbbrev::getFuzzLength(QString ellipsis)
|
Chris@50
|
31 {
|
Chris@50
|
32 int len = ellipsis.length();
|
Chris@50
|
33 if (len < 3) return len + 3;
|
Chris@50
|
34 else if (len > 5) return len + 5;
|
Chris@50
|
35 else return len * 2;
|
Chris@50
|
36 }
|
Chris@50
|
37
|
Chris@50
|
38 int
|
Chris@50
|
39 TextAbbrev::getFuzzWidth(const QFontMetrics &metrics, QString ellipsis)
|
Chris@50
|
40 {
|
Chris@50
|
41 int width = metrics.width(ellipsis);
|
Chris@50
|
42 return width * 2;
|
Chris@50
|
43 }
|
Chris@50
|
44
|
Chris@50
|
45 QString
|
Chris@50
|
46 TextAbbrev::abbreviateTo(QString text, int characters, Policy policy,
|
Chris@50
|
47 QString ellipsis)
|
Chris@50
|
48 {
|
Chris@50
|
49 switch (policy) {
|
Chris@50
|
50
|
Chris@50
|
51 case ElideEnd:
|
Chris@50
|
52 case ElideEndAndCommonPrefixes:
|
Chris@50
|
53 text = text.left(characters) + ellipsis;
|
Chris@50
|
54 break;
|
Chris@50
|
55
|
Chris@50
|
56 case ElideStart:
|
Chris@50
|
57 text = ellipsis + text.right(characters);
|
Chris@50
|
58 break;
|
Chris@50
|
59
|
Chris@50
|
60 case ElideMiddle:
|
Chris@50
|
61 if (characters > 2) {
|
Chris@50
|
62 text = text.left(characters/2 + 1) + ellipsis
|
Chris@50
|
63 + text.right(characters - (characters/2 + 1));
|
Chris@50
|
64 } else {
|
Chris@50
|
65 text = text.left(characters) + ellipsis;
|
Chris@50
|
66 }
|
Chris@50
|
67 break;
|
Chris@50
|
68 }
|
Chris@50
|
69
|
Chris@50
|
70 return text;
|
Chris@50
|
71 }
|
Chris@50
|
72
|
Chris@50
|
73 QString
|
Chris@50
|
74 TextAbbrev::abbreviate(QString text, int maxLength, Policy policy, bool fuzzy,
|
Chris@50
|
75 QString ellipsis)
|
Chris@50
|
76 {
|
Chris@50
|
77 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
78 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
|
Chris@50
|
79 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
|
Chris@50
|
80 if (text.length() <= maxLength + fl) return text;
|
Chris@50
|
81
|
Chris@50
|
82 int truncated = maxLength - ellipsis.length();
|
Chris@50
|
83 return abbreviateTo(text, truncated, policy, ellipsis);
|
Chris@50
|
84 }
|
Chris@50
|
85
|
Chris@50
|
86 QString
|
Chris@50
|
87 TextAbbrev::abbreviate(QString text,
|
Chris@50
|
88 const QFontMetrics &metrics, int &maxWidth,
|
Chris@53
|
89 Policy policy, QString ellipsis, int wrapLines)
|
Chris@50
|
90 {
|
Chris@50
|
91 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
92
|
Chris@50
|
93 int tw = metrics.width(text);
|
Chris@50
|
94
|
Chris@50
|
95 if (tw <= maxWidth) {
|
Chris@50
|
96 maxWidth = tw;
|
Chris@50
|
97 return text;
|
Chris@50
|
98 }
|
Chris@50
|
99
|
Chris@50
|
100 int truncated = text.length();
|
Chris@50
|
101 QString original = text;
|
Chris@50
|
102
|
Chris@53
|
103 if (wrapLines < 2) {
|
Chris@50
|
104
|
Chris@53
|
105 while (tw > maxWidth && truncated > 1) {
|
Chris@50
|
106
|
Chris@53
|
107 truncated--;
|
Chris@53
|
108
|
Chris@53
|
109 if (truncated > ellipsis.length()) {
|
Chris@53
|
110 text = abbreviateTo(original, truncated, policy, ellipsis);
|
Chris@53
|
111 } else {
|
Chris@53
|
112 break;
|
Chris@53
|
113 }
|
Chris@53
|
114
|
Chris@53
|
115 tw = metrics.width(text);
|
Chris@53
|
116 }
|
Chris@53
|
117
|
Chris@53
|
118 maxWidth = tw;
|
Chris@53
|
119 return text;
|
Chris@53
|
120
|
Chris@53
|
121 } else {
|
Chris@53
|
122
|
Chris@53
|
123 QStringList words = text.split(' ', QString::SkipEmptyParts);
|
Chris@53
|
124 text = "";
|
Chris@53
|
125
|
Chris@53
|
126 tw = 0;
|
Chris@53
|
127 int i = 0;
|
Chris@53
|
128 QString good = "";
|
Chris@53
|
129 int lastUsed = 0;
|
Chris@53
|
130 while (tw < maxWidth && i < words.size()) {
|
Chris@53
|
131 if (text != "") text += " ";
|
Chris@53
|
132 text += words[i++];
|
Chris@53
|
133 tw = metrics.width(text);
|
Chris@53
|
134 if (tw < maxWidth) {
|
Chris@53
|
135 good = text;
|
Chris@53
|
136 lastUsed = i;
|
Chris@53
|
137 }
|
Chris@53
|
138 }
|
Chris@53
|
139
|
Chris@53
|
140 if (tw < maxWidth) {
|
Chris@53
|
141 maxWidth = tw;
|
Chris@53
|
142 return text;
|
Chris@50
|
143 }
|
Chris@50
|
144
|
Chris@53
|
145 text = good;
|
Chris@53
|
146
|
Chris@53
|
147 QString remainder;
|
Chris@53
|
148 while (lastUsed < words.size()) {
|
Chris@53
|
149 if (remainder != "") remainder += " ";
|
Chris@53
|
150 remainder += words[lastUsed++];
|
Chris@53
|
151 }
|
Chris@53
|
152 remainder = abbreviate(remainder, metrics, maxWidth,
|
Chris@53
|
153 policy, ellipsis, wrapLines - 1);
|
Chris@53
|
154
|
Chris@53
|
155 maxWidth = std::max(maxWidth, tw);
|
Chris@53
|
156 text = text + '\n' + remainder;
|
Chris@53
|
157 return text;
|
Chris@50
|
158 }
|
Chris@50
|
159 }
|
Chris@50
|
160
|
Chris@50
|
161 QStringList
|
Chris@50
|
162 TextAbbrev::abbreviate(const QStringList &texts, int maxLength,
|
Chris@50
|
163 Policy policy, bool fuzzy, QString ellipsis)
|
Chris@50
|
164 {
|
Chris@50
|
165 if (policy == ElideEndAndCommonPrefixes &&
|
Chris@50
|
166 texts.size() > 1) {
|
Chris@50
|
167
|
Chris@50
|
168 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
169 int fl = (fuzzy ? getFuzzLength(ellipsis) : 0);
|
Chris@50
|
170 if (maxLength <= ellipsis.length()) maxLength = ellipsis.length() + 1;
|
Chris@50
|
171
|
Chris@50
|
172 int maxOrigLength = 0;
|
Chris@50
|
173 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
174 int len = texts[i].length();
|
Chris@50
|
175 if (len > maxOrigLength) maxOrigLength = len;
|
Chris@50
|
176 }
|
Chris@50
|
177 if (maxOrigLength <= maxLength + fl) return texts;
|
Chris@50
|
178
|
Chris@50
|
179 return abbreviate(elidePrefixes
|
Chris@50
|
180 (texts, maxOrigLength - maxLength, ellipsis),
|
Chris@50
|
181 maxLength, ElideEnd, fuzzy, ellipsis);
|
Chris@50
|
182 }
|
Chris@50
|
183
|
Chris@50
|
184 QStringList results;
|
Chris@50
|
185 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
186 results.push_back
|
Chris@50
|
187 (abbreviate(texts[i], maxLength, policy, fuzzy, ellipsis));
|
Chris@50
|
188 }
|
Chris@50
|
189 return results;
|
Chris@50
|
190 }
|
Chris@50
|
191
|
Chris@50
|
192 QStringList
|
Chris@50
|
193 TextAbbrev::abbreviate(const QStringList &texts, const QFontMetrics &metrics,
|
Chris@50
|
194 int &maxWidth, Policy policy, QString ellipsis)
|
Chris@50
|
195 {
|
Chris@50
|
196 if (policy == ElideEndAndCommonPrefixes &&
|
Chris@50
|
197 texts.size() > 1) {
|
Chris@50
|
198
|
Chris@50
|
199 if (ellipsis == "") ellipsis = getDefaultEllipsis();
|
Chris@50
|
200
|
Chris@50
|
201 int maxOrigWidth = 0;
|
Chris@50
|
202 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
203 int w = metrics.width(texts[i]);
|
Chris@50
|
204 if (w > maxOrigWidth) maxOrigWidth = w;
|
Chris@50
|
205 }
|
Chris@50
|
206
|
Chris@50
|
207 return abbreviate(elidePrefixes(texts, metrics,
|
Chris@50
|
208 maxOrigWidth - maxWidth, ellipsis),
|
Chris@50
|
209 metrics, maxWidth, ElideEnd, ellipsis);
|
Chris@50
|
210 }
|
Chris@50
|
211
|
Chris@50
|
212 QStringList results;
|
Chris@50
|
213 int maxAbbrWidth = 0;
|
Chris@50
|
214 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
215 int width = maxWidth;
|
Chris@50
|
216 QString abbr = abbreviate(texts[i], metrics, width, policy, ellipsis);
|
Chris@50
|
217 if (width > maxAbbrWidth) maxAbbrWidth = width;
|
Chris@50
|
218 results.push_back(abbr);
|
Chris@50
|
219 }
|
Chris@50
|
220 maxWidth = maxAbbrWidth;
|
Chris@50
|
221 return results;
|
Chris@50
|
222 }
|
Chris@50
|
223
|
Chris@50
|
224 QStringList
|
Chris@50
|
225 TextAbbrev::elidePrefixes(const QStringList &texts,
|
Chris@50
|
226 int targetReduction,
|
Chris@50
|
227 QString ellipsis)
|
Chris@50
|
228 {
|
Chris@50
|
229 if (texts.empty()) return texts;
|
Chris@50
|
230 int plen = getPrefixLength(texts);
|
Chris@50
|
231 int fl = getFuzzLength(ellipsis);
|
Chris@50
|
232 if (plen < fl) return texts;
|
Chris@50
|
233
|
Chris@50
|
234 QString prefix = texts[0].left(plen);
|
Chris@50
|
235 int truncated = plen;
|
Chris@50
|
236 if (plen >= targetReduction + fl) {
|
Chris@50
|
237 truncated = plen - targetReduction;
|
Chris@50
|
238 } else {
|
Chris@50
|
239 truncated = fl;
|
Chris@50
|
240 }
|
Chris@50
|
241 prefix = abbreviate(prefix, truncated, ElideEnd, false, ellipsis);
|
Chris@50
|
242
|
Chris@50
|
243 QStringList results;
|
Chris@50
|
244 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
245 results.push_back
|
Chris@50
|
246 (prefix + texts[i].right(texts[i].length() - plen));
|
Chris@50
|
247 }
|
Chris@50
|
248 return results;
|
Chris@50
|
249 }
|
Chris@50
|
250
|
Chris@50
|
251 QStringList
|
Chris@50
|
252 TextAbbrev::elidePrefixes(const QStringList &texts,
|
Chris@50
|
253 const QFontMetrics &metrics,
|
Chris@50
|
254 int targetWidthReduction,
|
Chris@50
|
255 QString ellipsis)
|
Chris@50
|
256 {
|
Chris@50
|
257 if (texts.empty()) return texts;
|
Chris@50
|
258 int plen = getPrefixLength(texts);
|
Chris@50
|
259 int fl = getFuzzLength(ellipsis);
|
Chris@50
|
260 if (plen < fl) return texts;
|
Chris@50
|
261
|
Chris@50
|
262 QString prefix = texts[0].left(plen);
|
Chris@50
|
263 int pwid = metrics.width(prefix);
|
Chris@50
|
264 int twid = pwid - targetWidthReduction;
|
Chris@50
|
265 if (twid < metrics.width(ellipsis) * 2) twid = metrics.width(ellipsis) * 2;
|
Chris@50
|
266 prefix = abbreviate(prefix, metrics, twid, ElideEnd, ellipsis);
|
Chris@50
|
267
|
Chris@50
|
268 QStringList results;
|
Chris@50
|
269 for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
270 results.push_back
|
Chris@50
|
271 (prefix + texts[i].right(texts[i].length() - plen));
|
Chris@50
|
272 }
|
Chris@50
|
273 return results;
|
Chris@50
|
274 }
|
Chris@50
|
275
|
Chris@50
|
276 static bool
|
Chris@50
|
277 havePrefix(QString prefix, const QStringList &texts)
|
Chris@50
|
278 {
|
Chris@50
|
279 for (int i = 1; i < texts.size(); ++i) {
|
Chris@50
|
280 if (!texts[i].startsWith(prefix)) return false;
|
Chris@50
|
281 }
|
Chris@50
|
282 return true;
|
Chris@50
|
283 }
|
Chris@50
|
284
|
Chris@50
|
285 int
|
Chris@50
|
286 TextAbbrev::getPrefixLength(const QStringList &texts)
|
Chris@50
|
287 {
|
Chris@50
|
288 QString reference = texts[0];
|
Chris@50
|
289
|
Chris@50
|
290 if (reference == "" || havePrefix(reference, texts)) {
|
Chris@50
|
291 return reference.length();
|
Chris@50
|
292 }
|
Chris@50
|
293
|
Chris@50
|
294 int candidate = reference.length();
|
Chris@50
|
295 QString splitChars(";:,./#-!()$_+=[]{}\\");
|
Chris@50
|
296
|
Chris@50
|
297 while (--candidate > 1) {
|
Chris@50
|
298 if (splitChars.contains(reference[candidate])) {
|
Chris@50
|
299 if (havePrefix(reference.left(candidate), texts)) {
|
Chris@50
|
300 break;
|
Chris@50
|
301 }
|
Chris@50
|
302 }
|
Chris@50
|
303 }
|
Chris@50
|
304
|
Chris@50
|
305 // std::cerr << "TextAbbrev::getPrefixLength: prefix length is " << candidate << std::endl;
|
Chris@50
|
306 // for (int i = 0; i < texts.size(); ++i) {
|
Chris@50
|
307 // std::cerr << texts[i].left(candidate).toStdString() << "|" << texts[i].right(texts[i].length() - candidate).toStdString() << std::endl;
|
Chris@50
|
308 // }
|
Chris@50
|
309
|
Chris@50
|
310 return candidate;
|
Chris@50
|
311 }
|
Chris@50
|
312
|