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